首先,这不是浮点新手问题。我知道浮点运算的结果(更不用说超越函数)通常无法准确表示,并且大多数终止小数不能完全表示为二进制浮点数。
也就是说,每个可能的浮点值完全对应于一个二元有理数(有理数p/q
,其中q
是2的幂),而这又有一个精确的十进制表示。
我的问题是:您如何有效地找到这个精确的十进制表示? sprintf
和类似的函数通常只指定多个有效数字来唯一地确定原始浮点值;它们不一定打印精确的十进制表示。我知道我使用过的一种算法,但它很慢,O(e^2)
其中e
是指数。这是一个大纲:
这真的是最好的吗?我对此表示怀疑,但我不是浮点专家,我无法找到一种方法对数字的浮点表示进行基数10计算,而不会遇到不精确结果的可能性(乘以或除以除了你知道你有免费比特工作之外,除了2的幂之外的任何东西都是对浮点数的有损操作。)
答案 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 multiplication或Toom-Cook multiplication,之后Schonhage-Strassen的变体是最好的不仅在理论上,而且在实践中。
实际上,大整数包GMP已经具有Õ(N)时基数转换,以及选择乘法算法的良好启发式算法。他们的解决方案和我的解决方案之间的唯一区别是,它不是在基数10中进行任何大算术,而是在基数2中计算10的大功率。在这个解决方案中,它们还需要快速除法,但这可以从任何快速乘法中获得。几种方式。
答案 1 :(得分:16)
我看到你已经接受了一个答案,但这里有一些你可能想看的转换的开源实现:
David Gay在dtoa()
dtoa.c
中的___printf_fp()
函数。{/ p>
glibc文件/stdio-common/printf_fp.c
中的函数%f
(例如http://www.netlib.org/fp/dtoa.c)。
两者都会在printf
类型{{1}}中打印您要求的数字(正如我在此处所写的那样:http://ftp.gnu.org/gnu/glibc/glibc-2.11.2.tar.gz和http://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)
有三种方式
在bin
或hex
这是最精确的方法。我更喜欢hex
因为它更像基座10
,因为阅读/感觉就像F.8h = 15.5
这里没有精度损失。
在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
),但在较大的位数可能会出错,因为使用的模式不完全适合<=52
或log10(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
,那么你必须转到前一个数字,所以上...
打印准确值
要打印小数二进制数的精确值,只需打印小数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)
你没有。你最接近的就是转储字节。