如何打印浮点数的EXACT值?

时间:2010-07-09 17:50:18

标签: c algorithm math floating-point

首先,这不是浮点新手问题。我知道浮点运算的结果(更不用说超越函数)通常无法准确表示,并且大多数终止小数不能完全表示为二进制浮点数。

也就是说,每个可能的浮点值完全对应于一个二元有理数(有理数p/q,其中q是2的幂),而这又有一个精确的十进制表示。

我的问题是:您如何有效地找到这个精确的十进制表示? sprintf和类似的函数通常只指定多个有效数字来唯一地确定原始浮点值;它们不一定打印精确的十进制表示。我知道我使用过的一种算法,但它很慢,O(e^2)其中e是指数。这是一个大纲:

  1. 将尾数转换为十进制整数。您可以通过拉开位直接读取尾数来执行此操作,也可以编写一个凌乱的浮点循环,首先将该值乘以2的幂,将其置于1 <= x <10的范围内,然后拉出通过转换为int,减去并乘以10来一次关闭一个数字。
  2. 通过重复乘以或除以2来应用指数。这是对您生成的十进制数的字符串的操作。每次~3次乘法将在左侧添加一个额外的数字。每一个人都会在右边添加一个额外的数字。
  3. 这真的是最好的吗?我对此表示怀疑,但我不是浮点专家,我无法找到一种方法对数字的浮点表示进行基数10计算,而不会遇到不精确结果的可能性(乘以或除以除了你知道你有免费比特工作之外,除了2的幂之外的任何东西都是对浮点数的有损操作。)

10 个答案:

答案 0 :(得分:34)

这个问题有一个官僚部分和一个算法部分。浮点数在内部存储为(2 e ×m),其中e是指数(本身为二进制),m是尾数。问题的官僚部分是如何访问这些数据,但R.似乎对问题的算法部分更感兴趣,即将(2 e ×m)转换为分数(a / b)十进制形式。几种语言的官僚问题的答案是“frexp”(这是我今天之前不知道的一个有趣的细节)。

乍一看,O(e 2 )只需要用十进制写2 e ,尾数的时间更长。但是,由于Schonhage-Strassen快速乘法算法的神奇之处,你可以在Õ(e)时间内完成它,其中波浪号意味着“最多记录因子”。如果你认为Schonhage-Strassen是神奇的,那么想想该做什么并不难。如果e是偶数,则可以递归计算2 e / 2 ,然后使用快速乘法对其进行平方。另一方面,如果e是奇数,你可以递归计算2 e-1 ,然后加倍。您必须小心检查基础10中是否存在Schonhage-Strassen的版本。虽然没有广泛记录,但可以在任何基础上完成。

将一个很长的尾数从二进制转换为十进制并不是完全相同的问题,但它有一个类似的答案。您可以将尾数分成两半,m = a 2 k + b。然后递归地将a和b转换为基数10,将2 ^ k转换为基数10,并进行另一次快速乘法以计算基数10中的m。

所有这些背后的抽象结果是你可以在Õ(N)时间内将整数从一个基数转换为另一个基数。

如果问题是关于标准的64位浮点数,那么它对于花哨的Schonhage-Strassen算法来说太小了。在此范围内,您可以使用各种技巧来保存工作。一种方法是将2 e 的所有2048个值存储在查找表中,然后在具有不对称乘法的尾数中工作(在长乘法和短乘法之间)。另一个技巧是在基数10000(或10的更高功率,取决于架构)而不是基数10工作。但是,正如R.在评论中指出的那样,128位浮点数已经允许足够大的指数调用询问查找表和标准长乘法。实际上,长乘法是最快的数字,然后在一个重要的中等范围内可以使用Karatsuba multiplicationToom-Cook multiplication,之后Schonhage-Strassen的变体是最好的不仅在理论上,而且在实践中。

实际上,大整数包GMP已经具有Õ(N)时基数转换,以及选择乘法算法的良好启发式算法。他们的解决方案和我的解决方案之间的唯一区别是,它不是在基数10中进行任何大算术,而是在基数2中计算10的大功率。在这个解决方案中,它们还需要快速除法,但这可以从任何快速乘法中获得。几种方式。

答案 1 :(得分:16)

我看到你已经接受了一个答案,但这里有一些你可能想看的转换的开源实现:

  1. David Gay在dtoa() dtoa.c中的___printf_fp()函数。{/ p>

  2. glibc文件/stdio-common/printf_fp.c中的函数%f(例如http://www.netlib.org/fp/dtoa.c)。

  3. 两者都会在printf类型{{1}}中打印您要求的数字(正如我在此处所写的那样:http://ftp.gnu.org/gnu/glibc/glibc-2.11.2.tar.gzhttp://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/)。

答案 2 :(得分:5)

在打印浮点数方面已经做了很多工作。黄金标准是打印出最小长度的十进制等效值,这样当读回十进制等效值时,无论回读期间的舍入模式是什么,都会得到相同的浮点数。您可以在优秀的paper by Burger and Dybvig中了解该算法。

答案 3 :(得分:3)

虽然它是C#并且你的问题用C标记,但Jon Skeet有代码将double转换为其精确表示形式的字符串:http://www.yoda.arachsys.com/csharp/DoubleConverter.cs

从快速浏览一下,移植到C似乎并不太难,甚至更容易用C ++编写。

经过进一步的反思,似乎Jon的算法也是O(e ^ 2),因为它也遍及指数。但是,这意味着算法是O(log(n)^ 2)(其中n是浮点数),我不确定你可以在比对数平方时间更好的基础上从基数2转换到基数10。

答案 4 :(得分:2)

我自己也不是浮点专家,我会推迟使用经过良好测试的开源库。

GNU MPFR是一个很好的。

  

MPFR库是一个C库   多精度浮点数   具有正确舍入的计算。   MPFR的主要目标是提供一个   用于多精度的库   浮点计算即   既高效又有明确的定义   语义。

答案 5 :(得分:1)

  

sprintf和类似的功能   通常只指定一个数字   有效的有效数字   确定原始浮点   值;他们不一定打印   确切的十进制表示。

您可以要求比默认值更多的有效数字:

printf("%.100g\n", 0.1);

打印0.1000000000000000055511151231257827021181583404541015625

答案 6 :(得分:0)

如果您想要更精确的结果,为什么不使用定点数学呢?转换很快。错误是已知的,可以解决。不是你问题的确切答案,而是你的不同想法。

答案 7 :(得分:0)

在我的脑海中,为什么不首先将指数分解为二进制指数的总和,然后你的所有操作都是无损的。

10^2 = 2^6 + 2^5 + 2^2

然后总结:

mantissa<<6 + mantissa<<5 + mantissa<<2

我认为将其分解为位数上的O(n),移位为O(1),求和为O(n)位......

你必须有一个足够大的整数类来存储结果,当然......

让我知道 - 我对此感到好奇,这真让我思考。 : - )

答案 8 :(得分:0)

有三种方式

  1. binhex

    中打印数字

    这是最精确的方法。我更喜欢hex因为它更像基座10,因为阅读/感觉就像F.8h = 15.5这里没有精度损失。

  2. dec中打印,但只打印相关数字

    有了这个,我的意思是只有数字1的数字尽可能接近。

    整数位

    num简单而精确(无精确丢失):

    // n10 - base 10 integer digits
    // n2  - base 2 integer digits
    n10=log10(2^n2)
    n10=log2(2^n2)/log2(10)
    n10=n2/log2(10)
    n10=ceil(n2*0.30102999566398119521373889472449)
    // if fist digit is 0 and n10 > 1 then n10--
    
    小数位

    num更加棘手,凭经验我发现了这一点:

    // n10 - base 10 fract. digits
    // n2  - base 2 fract. digits >= 8
    n10=0; if (n02==8) n10=1;
    else if (n02==9) n10=2;
    else if (n02> 9)
        {
        n10=((n02-9)%10);
             if (n10>=6) n10=2;
        else if (n10>=1) n10=1;
        n10+=2+(((n02-9)/10)*3);
        }
    

    如果你创建一个依赖表n02 <-> n10,那么你会看到常量0.30102999566398119521373889472449仍然存在,但是从8位开始,因为less不能以令人满意的精度表示0.1 {{1 }})。因为基数0.85 - 1.15的负指数,依赖关系不是线性的,而是模式。此代码适用于较小的位数(2),但在较大的位数可能会出错,因为使用的模式不完全适合<=52log10(2)

    对于更大的位数,我使用它:

    1/log2(10)

    但该公式是32位对齐!还有更大的位数广告错误

    P.S。进一步分析十进制数的二进制表示

    n10=7.810+(9.6366363636363636363636*((n02>>5)-1.0));
    

    可能会显示确切的模式重复,这会导致任何位数的确切相关位数。

    为清晰起见:

    0.1
    0.01
    0.001
    0.0001
    ...
    

    最后不要忘记绕过数字!这意味着如果最后一个相关数字后面的数字在十进制中的8 bin digits -> 1 dec digits 9 bin digits -> 2 dec digits 10 bin digits -> 3 dec digits 11 bin digits -> 3 dec digits 12 bin digits -> 3 dec digits 13 bin digits -> 3 dec digits 14 bin digits -> 3 dec digits 15 bin digits -> 4 dec digits 16 bin digits -> 4 dec digits 17 bin digits -> 4 dec digits 18 bin digits -> 4 dec digits 19 bin digits -> 5 dec digits 20 bin digits -> 6 dec digits 21 bin digits -> 6 dec digits 22 bin digits -> 6 dec digits 23 bin digits -> 6 dec digits 24 bin digits -> 6 dec digits 25 bin digits -> 7 dec digits 26 bin digits -> 7 dec digits 27 bin digits -> 7 dec digits 28 bin digits -> 7 dec digits 29 bin digits -> 8 dec digits 30 bin digits -> 9 dec digits 31 bin digits -> 9 dec digits 32 bin digits -> 9 dec digits 33 bin digits -> 9 dec digits 34 bin digits -> 9 dec digits 35 bin digits -> 10 dec digits 36 bin digits -> 10 dec digits 37 bin digits -> 10 dec digits 38 bin digits -> 10 dec digits 39 bin digits -> 11 dec digits 40 bin digits -> 12 dec digits 41 bin digits -> 12 dec digits 42 bin digits -> 12 dec digits 43 bin digits -> 12 dec digits 44 bin digits -> 12 dec digits 45 bin digits -> 13 dec digits 46 bin digits -> 13 dec digits 47 bin digits -> 13 dec digits 48 bin digits -> 13 dec digits 49 bin digits -> 14 dec digits 50 bin digits -> 15 dec digits 51 bin digits -> 15 dec digits 52 bin digits -> 15 dec digits 53 bin digits -> 15 dec digits 54 bin digits -> 15 dec digits 55 bin digits -> 16 dec digits 56 bin digits -> 16 dec digits 57 bin digits -> 16 dec digits 58 bin digits -> 16 dec digits 59 bin digits -> 17 dec digits 60 bin digits -> 18 dec digits 61 bin digits -> 18 dec digits 62 bin digits -> 18 dec digits 63 bin digits -> 18 dec digits 64 bin digits -> 18 dec digits 比最后一个相关数字应该是>=5 ...如果它已经是+1,那么你必须转到前一个数字,所以上...

  3. 打印准确值

    要打印小数二进制数的精确值,只需打印小数9位数,其中n是小数位数,因为表示的值是此值的总和,因此小数小数的数量不能大于 LSB 小数位的n。上面的内容(项目符号#2 )与将十进制数存储到num(或仅打印相关的小数)相关。

    两个确切值的负幂...

    float

    现在2^- 1 = 0.5 2^- 2 = 0.25 2^- 3 = 0.125 2^- 4 = 0.0625 2^- 5 = 0.03125 2^- 6 = 0.015625 2^- 7 = 0.0078125 2^- 8 = 0.00390625 2^- 9 = 0.001953125 2^-10 = 0.0009765625 2^-11 = 0.00048828125 2^-12 = 0.000244140625 2^-13 = 0.0001220703125 2^-14 = 0.00006103515625 2^-15 = 0.000030517578125 2^-16 = 0.0000152587890625 2^-17 = 0.00000762939453125 2^-18 = 0.000003814697265625 2^-19 = 0.0000019073486328125 2^-20 = 0.00000095367431640625 的负幂以64位10的精确值样式打印:

    doubles

    现在,对于64位10^+ -1 = 0.1000000000000000055511151231257827021181583404541015625 = 0.0001100110011001100110011001100110011001100110011001101b 10^+ -2 = 0.01000000000000000020816681711721685132943093776702880859375 = 0.00000010100011110101110000101000111101011100001010001111011b 10^+ -3 = 0.001000000000000000020816681711721685132943093776702880859375 = 0.000000000100000110001001001101110100101111000110101001111111b 10^+ -4 = 0.000100000000000000004792173602385929598312941379845142364501953125 = 0.000000000000011010001101101110001011101011000111000100001100101101b 10^+ -5 = 0.000010000000000000000818030539140313095458623138256371021270751953125 = 0.000000000000000010100111110001011010110001000111000110110100011110001b 10^+ -6 = 0.000000999999999999999954748111825886258685613938723690807819366455078125 = 0.000000000000000000010000110001101111011110100000101101011110110110001101b 10^+ -7 = 0.0000000999999999999999954748111825886258685613938723690807819366455078125 = 0.0000000000000000000000011010110101111111001010011010101111001010111101001b 10^+ -8 = 0.000000010000000000000000209225608301284726753266340892878361046314239501953125 = 0.000000000000000000000000001010101111001100011101110001000110000100011000011101b 10^+ -9 = 0.0000000010000000000000000622815914577798564188970686927859787829220294952392578125 = 0.0000000000000000000000000000010001001011100000101111101000001001101101011010010101b 10^+-10 = 0.00000000010000000000000000364321973154977415791655470655996396089904010295867919921875 = 0.00000000000000000000000000000000011011011111001101111111011001110101111011110110111011b 10^+-11 = 0.00000000000999999999999999939496969281939810930172340963650867706746794283390045166015625 = 0.00000000000000000000000000000000000010101111111010111111111100001011110010110010010010101b 10^+-12 = 0.00000000000099999999999999997988664762925561536725284350612952266601496376097202301025390625 = 0.00000000000000000000000000000000000000010001100101111001100110000001001011011110101000010001b 10^+-13 = 0.00000000000010000000000000000303737455634003709136034716842278413651001756079494953155517578125 = 0.00000000000000000000000000000000000000000001110000100101110000100110100001001001011101101000001b 10^+-14 = 0.000000000000009999999999999999988193093545598986971343290729163921781719182035885751247406005859375 = 0.000000000000000000000000000000000000000000000010110100001001001101110000110101000010010101110011011b 10^+-15 = 0.00000000000000100000000000000007770539987666107923830718560119501514549256171449087560176849365234375 = 0.00000000000000000000000000000000000000000000000001001000000011101011111001111011100111010101100001011b 10^+-16 = 0.00000000000000009999999999999999790977867240346035618411149408467364363417573258630000054836273193359375 = 0.00000000000000000000000000000000000000000000000000000111001101001010110010100101111101100010001001101111b 10^+-17 = 0.0000000000000000100000000000000007154242405462192450852805618492324772617063644020163337700068950653076171875 = 0.0000000000000000000000000000000000000000000000000000000010111000011101111010101000110010001101101010010010111b 10^+-18 = 0.00000000000000000100000000000000007154242405462192450852805618492324772617063644020163337700068950653076171875 = 0.00000000000000000000000000000000000000000000000000000000000100100111001001011101110100011101001001000011101011b 10^+-19 = 0.000000000000000000099999999999999997524592683526013185572915905567688179926555402943222361500374972820281982421875 = 0.000000000000000000000000000000000000000000000000000000000000000111011000001111001001010011111011011011010010101011b 10^+-20 = 0.00000000000000000000999999999999999945153271454209571651729503702787392447107715776066783064379706047475337982177734375 = 0.00000000000000000000000000000000000000000000000000000000000000000010111100111001010000100001100100100100100001000100011b ,只有相关的十进制数字打印的负数10(我更习惯于此):

    doubles

    希望它有所帮助:)

答案 9 :(得分:-2)

你没有。你最接近的就是转储字节。