双精度在不同语言中是不同的
我正在试验各种编程语言中双精度值的精度。
我的节目
主文件
#include <stdio.h>
int main() {
for (double i = 0.0; i < 3; i = i + 0.1) {
printf("%.17lfn", i);
}
return 0;
}
主程序
#include <iostream>
using namespace std;
int main() {
cout.precision(17);
for (double i = 0.0; i < 3; i = i + 0.1) {
cout << fixed << i << endl;
}
return 0;
}
主文件
i = 0.0
while i < 3:
print(i)
i = i + 0.1
主程序
public class Main {
public static void main(String[] args) {
for (double i = 0.0; i < 3; i = i + 0.1) {
System.out.println(i);
}
}
}
输出
主文件
0.00000000000000000
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.50000000000000000
0.59999999999999998
0.69999999999999996
0.79999999999999993
0.89999999999999991
0.99999999999999989
1.09999999999999990
1.20000000000000000
1.30000000000000000
1.40000000000000010
1.50000000000000020
1.60000000000000030
1.70000000000000040
1.80000000000000050
1.90000000000000060
2.00000000000000040
2.10000000000000050
2.20000000000000060
2.30000000000000070
2.40000000000000080
2.50000000000000090
2.60000000000000100
2.70000000000000110
2.80000000000000120
2.90000000000000120
主程序
0.00000000000000000
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.50000000000000000
0.59999999999999998
0.69999999999999996
0.79999999999999993
0.89999999999999991
0.99999999999999989
1.09999999999999987
1.19999999999999996
1.30000000000000004
1.40000000000000013
1.50000000000000022
1.60000000000000031
1.70000000000000040
1.80000000000000049
1.90000000000000058
2.00000000000000044
2.10000000000000053
2.20000000000000062
2.30000000000000071
2.40000000000000080
2.50000000000000089
2.60000000000000098
2.70000000000000107
2.80000000000000115
2.90000000000000124
主文件
0.0
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
1.2
1.3
1.4000000000000001
1.5000000000000002
1.6000000000000003
1.7000000000000004
1.8000000000000005
1.9000000000000006
2.0000000000000004
2.1000000000000005
2.2000000000000006
2.3000000000000007
2.400000000000001
2.500000000000001
2.600000000000001
2.700000000000001
2.800000000000001
2.9000000000000012
0.0
0.1
0.2
0.30000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
1.2
1.3
1.4000000000000001
1.5000000000000002
1.6000000000000003
1.7000000000000004
1.8000000000000005
1.9000000000000006
2.0000000000000004
2.1000000000000005
2.2000000000000006
2.3000000000000007
2.400000000000001
2.500000000000001
2.600000000000001
2.700000000000001
2.800000000000001
2.9000000000000012
主程序
我的问题
我知道double类型本身存在一些错误,我们可以从博客中了解更多关于为什么你不应该使用浮点数和双精度数进行货币计算以及每个计算机科学家应该知道的关于浮点运算的知识。
但这些错误不是随机的!每次错误都是相同的,因此我的问题是为什么不同的编程语言会有不同的错误?
其次,为什么 Java 和 Python 中的精度错误相同?[Java 的 JVM 是用 C++ 编写的,而 Python 解释器是用 C 编写的]
但令人惊讶的是,它们的错误是相同的,但与 C 和 C++ 中的错误不同。为什么会这样?
回答
输出的差异是由于将浮点数转换为数字的差异。(数字是指代表数字的字符串或其他文本。“20”、“20.0”、“2e+1”和“2•10 2 ”是同一个数字的不同数字。)
作为参考,我i在下面的注释中显示了 的确切值。
在 C 中,%.17lf您使用的转换规范要求小数点后 17 位,因此产生小数点后 17 位。但是,C 标准允许在这方面有所松懈。它只需要计算足够的数字,就可以区分实际的内部值。1其余的可以用零(或其他“不正确”的数字)填充。看来您使用的 C 标准库只完全计算 17 位有效数字,并用零填充您请求的其余数字。这解释了为什么你得到“2.90000000000000120”而不是“2.90000000000000124”。(注意“2.9000000000000120”有18位:小数点前1位,16位有效数字,1位无意义“0”。“0.10000000000000001”小数点前有美感“0”,后有17位有效数字. 17 位有效数字的要求是为什么“0.10000000000000001”末尾必须有“1”而“2.90000000000000120”可能有“0”。)
相比之下,您的 C++ 标准库似乎做了完整的计算,或者至少更多(这可能是由于 C++ 标准2 中的规则),所以您得到“2.90000000000000124”。
Python 3.1 添加了一个算法来转换与 Java 相同的结果(见下文)。在此之前,对于显示的转换是松懈的。(据我所知,在算术运算中使用的浮点格式和符合 IEEE-754 仍然是松懈的;具体的 Python 实现可能在行为上有所不同。)
Java 要求从double到字符串的默认转换产生与区分数字与相邻double值所需的数字一样多(也在这里)。因此它生成“.2”而不是“0.20000000000000001”,因为最接近 .2 的双精度值是i该迭代中的值。相比之下,在下一次迭代中,算术中的舍入误差给出i的值与最接近的双精度 0.3 略有不同,因此 Java 为它生成了“0.30000000000000004”。在下一次迭代中,新的舍入误差恰好抵消了累积误差,因此又回到了“0.4”。
笔记
使用iIEEE-754 binary64 时的确切值是:
0 0.1000000000000000055511151231257827021181583404541015625 0.200000000000000011102230246251565404236316680908203125 0.3000000000000000444089209850062616169452667236328125 0.40000000000000002220446049250313080847263336181640625 0.5 0.5999999999999997779553950749686919152736663818359375 0.6999999999999999555910790149937383830547332763671875 0.7999999999999993338661852249060757458209991455078125 0.89999999999999911182158029987476766109466552734375 0.99999999999999988897769753748434595763683319091796875 1.0999999999999998667732370449812151491641998291015625 1.1999999999999999555910790149937383830547332763671875 1.3000000000000000444089209850062616169452667236328125 1.4000000000000001332267629550187848508358001708984375 1.50000000000000022204460492503130808472633336181640625 1.6000000000000003108624468950438313186168670654296875 1.7000000000000003996802888650563545525074005126953125 1.8000000000000004884981308350688777863979339599609375 1.9000000000000005773159728050814010202884674072265625 2.000000000000000444089209850062616169452667236328125 2.10000000000000053290705182007513940334320068359375 2.200000000000000621724893790087662637233734130859375 2.300000000000000710542735760100185871124267578125 2.400000000000000799360577730112709105014801025390625 2.50000000000000088817841970012523233890533447265625 2.600000000000000976996261670137755572795867919921875 2.7000000000000010658141036401502788066864013671875 2.800000000000001154631945610162802040576934814453125 2.90000000000000124344978758017532527446746826171875
这些与将 0, .1, .2, .3,... 2.9 从十进制转换为 binary64 所得到的值并不完全相同,因为它们是通过算术产生的,因此初始转换和连续加法存在多个舍入误差.
脚注
1 C 2018 7.21.6.1 只要求结果DECIMAL_DIG数字在指定意义上精确到数字。DECIMAL_DIG是这样的位数,对于实现中任何浮点格式的任何数字,将其转换为具有DECIMAL_DIG有效数字的十进制数,然后再返回浮点数会产生原始值。如果 IEEE-754 binary64 是您的实现支持的最精确格式,那么它DECIMAL_DIG至少是 17。
2我在 C++ 标准中没有看到这样的规则,除了 C 标准的合并,所以可能是你的 C++ 库只是使用与你的 C 库不同的方法作为选择问题。
回答
您看到的差异在于打印数据的方式,而不是数据本身。
在我看来,我们有两个问题。一是当您以每种语言打印数据时,您并没有始终如一地指定相同的精度。
第二个是您将数据打印到 17 位精度,但至少在通常实现的情况下(double64 位数字和 53 位有效数)double实际上只有大约 15 位十进制数字的精度。
因此,虽然(例如)C 和 C++ 都要求“正确”舍入您的结果,但一旦超出了它应该支持的精度限制,它们就不能保证在每种可能的情况下都能产生真正相同的结果。
但这只会影响打印出来的结果的外观,而不影响它内部实际存储的方式。