库/编程语言如何将浮点数转换为字符串

时间:2019-06-01 17:28:44

标签: javascript string algorithm type-conversion programming-languages

这是我想知道自己15岁时的谜,但我失败了。我仍然不知道答案。

这是一个幼稚且有缺陷的解决方案(就像我在Stack Overflow上看到的其他失败尝试一样):

Job for mysql.service failed because the control process exited with error code.
See "systemctl status mysql.service" and "journalctl -xe" for details.

此处的语言为Javascript,但问题是与语言无关。但是,请尽可能对现有代码进行改进。

如果此方法的工作方式取决于语言,那么我将对一些见解在各种编程语言(例如Javascript)中的外观有所了解。

2 个答案:

答案 0 :(得分:0)

(我没有足够的声誉来发表评论,所以诉诸于使用答案...)

我注意到您将精度超出了300个数字,远远超出了浮点数的精度,因此结果不准确。如果您正在寻找一种可以进行高精度计算的方法,则可以使用BigInt,并相应地扩展数字。例如,可以通过以下功能处理计算1000/17。 (请注意,这只是一个用于处理两个整数之间的高精度除法的概念函数,但可以通过按比例放大 dividend divisor 直到它们成为非整数的基础)是整数,并相应地调整数字。此外,您可能需要增加一些额外的“隐藏”精度来处理舍入)...

function divideN(dividend, divisor, digits) {
  dividend = dividend * 10n ** (BigInt(digits) * 2n);
  divisor = divisor * 10n ** BigInt(digits);
  var s = (dividend/divisor).toString();
  if (s.length < digits) {
    s = "0".repeat(digits - s.length) + s;
  }
  s = s.slice(0, s.length - digits) + "." + s.slice(-digits);
  return s;
}

BigInt要求数字以“ n”结尾,因此需要按以下方式调用该函数...

divideN(1000n,17n,100)

...在这种情况下返回...

"58.8235294117647058823529411764705882352941176470588235294117647058823529411764705882352941176470588235"

请注意,在这种情况下,由于除数(1000)对除数(17)的相对大小,返回的精度是102位而不是100位。

答案 1 :(得分:0)

我不是 JAVASCRIPT 编码器,所以我坚持使用 C ++ ...

将数字转换为十进制数的字符串比使用二进制数或幂的基数(bin,oct,hex)要复杂得多,这是因为通常计算机上的所有数字都以二进制数存储,而不是十年的如果转换整数或小数部分,则也不相同。假设我们有数字x,并且想要用ASCII编码字符串s,因此这是基本转换的工作方式:

  1. 句柄sign

    s="+";
    if (x<0.0)  { x=-x; s="-"; }
    

    如您所见,它很简单。某些数字格式具有单独的符号位(通常是msb),因此在这种情况下,代码可以转换为位操作,例如32位float

    DWORD* dw=(DWORD*)(&x); // allow bit manipulation
    s="+";
    s[0]+=(((*dw)>>30)&2);  // ASCII +,- codes are 2 apart
    (*dw)&=0x7FFFFFFF;      // x=abs(x)
    

    因此我们为字符串提取了符号字符,并使x无符号。

  2. 处理x的整数部分

    通过将

    整数除以打印基数,将

    整数转换为字符串,这样:

    y=floor(x); // integer part
    if (y)
     for (;y;) // until number is nonzero
     {
     s+='0'+(y%10); // works only for up to 10 base
     y/=10;
     }
    else s+='0'; // handle y=0 separately 
    

    因此,每个除法的其余部分都是字符串的期望数字,但顺序相反。因此,在转换后,可以通过单个for循环将字符串中的数字反转,也可以直接按相反的顺序存储数字。但是对于tat,您需要知道数字整数部分的位数。这是通过

    digits = ceil(log(y)/log(base)) + 1
    

    所以十进制:

    digits = ceil(log10(y)) + 1
    
  3. 处理x的小数部分

    通过乘以乘以转换基础进行转换。

    z=x-floor(x); // fractional part
    if (z)
     for (s+='.';z;) // until number is nonzero here you can limit to number of digits
     {
     z*=10.0;
     s+='0'+int(floor(z)); // works only for up to 10 base
     z-=floor(z);
     }
    

    这将按顺序返回数字,所以这次不会反转...

我直接在SO编辑器中对所有代码进行了编码,因此可能存在隐藏的语法错误。

现在通常的打印功能还具有格式化功能,该格式可以添加零或空格填充或截取高于某个值的小数位数,等等...

如果您有一个x,那么这会慢很多,因为您不能再像+,-,*,/那样处理基本的O(1)操作,并且通常更快地创建hex字符串,然后使用8位算术将字符串转换为十进制,或使用bignum所存储的已用DATA WORD中的最大10的幂。 hex -> dec转换可以像这样完成:

但是对于非常大的字符串,它将再次变慢。在这种情况下,可以使用类似于Schönhage-Strassen乘法 FFT / NTT 方法来加快速度,但是我以前从未尝试过将其用于打印,因此我对这种方法。

还请注意,确定数值的位数对于数字的小数部分而言并不规则(请参见上面的链接),因此您需要注意的是,可以用1-2位数字来代替。

[Edit1]舍入字符串

简单地讲,如果在小数部分(任何非零数字之后)中检测到n所导致的零或9,则需要停止打印并舍入。零只是被切掉的,而九也需要切掉,其余的要在字符串中增加一个。这样的操作可能溢出到字符串中没有的1位数字,因此在这种情况下,只需插入1

当我将所有内容放在一起时,会想到以下 C ++ / VCL 代码(基于 VCL AnsiString数据类型):

AnsiString print(double x)
    {
    char c;
    int i,j;
    double y,a;
    AnsiString s;

    const int B=10;                 // chose base 2...16
    const double b=B;               // base
    const double _b=1.0/b;          // 1/base
    const char digit[16]="0123456789ABCDEF";

    #define _enable_rounding

    #ifdef _enable_rounding
    const int round_digits=5;       // min consequent 0s or B-1s to triger rounding
    int cnt0=0,cnt1=0;              // consequent digit counters
    int ena=0;                      // enabled consequent digit counters? after first nonzero digit
    #endif

    // here you should handle NaN and Inf cases

    // handle sign
    s="+";
    if (x<0.0)  { x=-x; s="-"; }
    // integer part
    y=floor(x);
    if (y) for (;y>0.0;)        // until number is nonzero
        {
        a=y; y=floor(y*_b);     // the same as y/=10 on integers
        a-=y*b;                 // the same as a=y%10 on integers
        i=int(a);
        s+=digit[i];
        #ifdef _enable_rounding
        ena|=i;
        #endif
        }
    else s+='0';                // handle y=0 separately
    // reverse string skipping +/- sign (beware AnsiString is indexed from 1 up to its length included!!!)
    for (i=2,j=s.Length();i<j;i++,j--){ c=s[i]; s[i]=s[j]; s[j]=c; }
    // fractional part
    y=x-floor(x);
    if (y) for (s+='.';y>0.0;)      // until number is nonzero here you can limit to number of digits
        {
        y*=b;
        a=floor(y);
        y-=a;
        i=int(a);
        s+=digit[i];
        #ifdef _enable_rounding
        ena|=i;
        // detect consequent rounding digits
        if (ena)
            {
                 if (i==  0){ cnt0++; cnt1=0; }
            else if (i==B-1){ cnt1++; cnt0=0; }
            else            { cnt0=0; cnt1=0; }
            }
        // round down .???00000000 by cut of zeros
        if (cnt0>=round_digits)
            {
            s=s.SubString(1,s.Length()-cnt0);       // by cut of zeros
            break;
            }
        // round up .???999999999 by increment and cut of zeros (only base 10) !!!
        if (cnt1>=round_digits)
            {
            s=s.SubString(1,s.Length()-cnt1);       // cut off nines
            for (j=1,i=s.Length();(i>=2)&&(j);i--)
                {
                c=s[i];
                if (c=='.') continue;
                if (c=='9'){ s[i]='0'; continue; }
                j=0; s[i]++;
                }
            if (j) s=s.Insert("1",i+1); // overflow -> insert "1" after sign
            if (s[s.Length()]=='.')     // cut off decimal point if no fractional part left
             s=s.SubString(1,s.Length()-1);
            break;
            }
        #endif
        }
    return s;
    }

您可以选择基础B=<2,16>。您可以通过使用/注释#define _enable_rounding来禁用舍入。请注意,舍入例程仅适用于基数10,因为对于不同的基数,增量例程将具有一些不同的代码/常量,并且太懒了以至于无法通用地进行处理(这将是更长和更难理解的代码)。 round_digits常数是触发舍入的随后零个或九个的阈值。