首页 文章

如何打印浮点数的EXACT值?

提问于
浏览
30

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

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

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

  • 将尾数转换为十进制整数 . 你可以通过拉开这些位来直接读取尾数,或者你可以编写一个凌乱的浮点循环,首先将该值乘以2的幂,使其在1 <= x <10的范围内,然后拉通过转换为int,减去并乘以10,一次关闭一个数字 .

  • 通过重复乘以或除以2来应用指数 . 这是对您生成的 string 十进制数字的运算 . 每次~3次乘法将在左侧添加一个额外的数字 . 每个单独的dividion将在右侧添加一个额外的数字 .

这真的是最好的吗?我对此表示怀疑,但我不是浮点专家,我无法找到一种方法对数字的浮点表示进行基数10计算,而不会遇到不精确结果的可能性(乘以或除以除了你知道你有空闲位之外,除了2的幂之外的任何东西都是浮点数的有损操作 .

10 回答

  • 0

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

  • 3

    sprintf和类似函数通常只指定多个有效数字来唯一确定原始浮点值;它们不一定打印精确的十进制表示 .

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

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

    打印 0.1000000000000000055511151231257827021181583404541015625 .

  • 0

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

  • 5

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

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

    然后总结:

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

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

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

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

  • -2

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

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

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

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

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

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

  • 34

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

    两者都会打印 %f 类型 printf 中所要求的数字(正如我在这里所写的那样:http://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too/) .

  • 0

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

  • 16

    虽然它是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 .

  • 1

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

    GNU MPFR是一个很好的 .

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

  • 2

    有3种方式

    • printing numbers in bin or hex

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

    • printing in dec but only the relevant digits

    有了这个,我的意思是只有数字 1 在您的数字中尽可能接近 .

    num integer digits 简单而精确(无精度损失):

    // 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--
    

    numfractional digits 更加棘手,凭经验我发现了这个:

    // 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.10.85 - 1.15 ) . 因为基数 2 的负指数,依赖关系不是线性的,而是模式 . 此代码适用于较小的位计数( <=52 )但在较大的位计数时可能会出错,因为使用的模式不完全适合 log10(2)1/log2(10) .

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

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

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

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

    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 ...并且如果它已经是 9 ,那么你必须转到前一个数字,依此类推......

    • print exact value

    要打印 fractional binary number 的精确值,只需打印小数 n 位数,其中 n 是小数位数,因为表示的值是此值的总和,因此 fractional decimals 的数量不能大于 LSB 的小数位 num . 上面的内容(项目符号 #2 )与将十进制数存储到 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
    

    现在 10 的负功率由64位 doubles 的精确值样式打印:

    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的相关十进制数字仅打印样式(我更习惯于此)为64位 doubles

    10^+ -1 = 0.1
    10^+ -2 = 0.01
    10^+ -3 = 0.001
    10^+ -4 = 0.0001
    10^+ -5 = 0.00001
    10^+ -6 = 0.000001
    10^+ -7 = 0.0000001
    10^+ -8 = 0.00000001
    10^+ -9 = 0.000000001
    10^+-10 = 0.0000000001
    10^+-11 = 0.00000000001
    10^+-12 = 0.000000000001
    10^+-13 = 0.0000000000001
    10^+-14 = 0.00000000000001
    10^+-15 = 0.000000000000001
    10^+-16 = 0.0000000000000001
    10^+-17 = 0.00000000000000001
    10^+-18 = 0.000000000000000001
    10^+-19 = 0.0000000000000000001
    10^+-20 = 0.00000000000000000001
    

    希望能帮助到你 :)

相关问题