为什么不同的语言显示数字不同9223372036854775807
,即使它们都使用相同的 8 字节双精度格式来表示数字?
9223372036854775807
- 在代码中
9223372036854775808
- C++ http://ideone.com/PV5iPg和http://codepad.org/vhQzDMqT
9223372036854776000
- Javascript https://jsfiddle.net/5ugL4rqh/
9223372036854776000
- Java http://ideone.com/QtXRWi
9223372036854780000
- C# http http://ideone.com/36Lzzi
在每种环境/语言中,这里有两种转换:
C++
C++ 问题中代码的影响:
volatile double x = 9223372036854775807.;
类似于 gcc 的-ffloat-store
选项,让您忘记 可能的额外位,只考虑相关实现中使用的 64 位 IEEE 754 双精度数(IEEE 754 是可选的,但浮点数的具体实现必须记录在案)。9223372036854775807.
源代码中 的常量被转换为9223372036854775808.
双精度(正如预期的这种类型,请参见下面的位表示演示)。同样的事情发生在 CPython 中:也就是说,
9223372036854775807.
不能在 IEEE 754 double 中精确表示,因此使用了近似值9223372036854775808.
(2 63 ),在这种情况下已经通过以下方式准确推断:cout << fixed << x;
作为 ascii 字符串:("9223372036854775808.000000"
在 C 语言环境中)。如何在内存中表示 double 并在 IEEE 754 中表示为位,以及如何在 C 中进行打印,在问题printf 作为在C中打印变量的方法的答案中有详细说明。
在这种情况下,由于数字是 2 的幂,因此很容易找到它的 IEEE 754 表示形式:
d = ±符号 (1 + 尾数 / 2 52 )二阶 − 1023
знак
新位为零,因为数字是正数порядок
= (63 + 1023) 10 = 10000111110 2得到 2 63мантисса
隐式第 53 位始终为 1)一个数的所有位在一起:
Python 中的计算证实了这一点:
反过来:
示例中数字在内存中的字节顺序显示为从高到低(big-endian),但实际上可以是从低到高(little-endian):
您可以通过向尾数减去/添加一位来看到可表示的数字不在一行中:
对于这种数量级的数字,一位的差异会导致十进制表示形式的差异超过一千:..
4784
, ..5808
, ..7856
。您可以使用C99 函数
nextafter()
:结果与之前相同:
javascript
JavaScript 中的数字以一种有趣的方式表示 - 整数表示为 IEEE 754 双精度数。例如,最大数字 (
Number.MAX_SAFE_INTEGER
) 是 2 53。9223372036854775807
大了三个数量级,MAX_SAFE_INTEGER
所以不能保证n
和n+1
是可表示的。9223372036854776000
(导致document.write(9223372036854775807)
其中一个 javascript 实现)被 标准允许作为字符串表示形式9223372036854775807
(它仍然是一个 binary64 数字:)0x1.0000000000000p+63
。按位运算的结果一般限于32 位有符号数。您可以查看要重现用 javascript 实现的散列函数的结果必须采用哪些技巧:如何将字符串散列函数从 Javascript 转换为 Python。
爪哇
在 Java中,double 是一种类型,其值包括 64 位 IEEE 754 浮点数。
为什么为 binary64 数字
9223372036854776000
选择, 而不是9223372036854775808.
十进制表示的可能逻辑是,通常它允许为小数打印更少的数字 - 不显示尾随零(这是推测 - 我没有深入研究这个问题)。0x1.0000000000000p+63
C#
msdn 指出 C# 中的 double 符合 IEEE 754。
9223372036854780000.0
提示它Console.WriteLine("{0:0.0}", x);
在打印时四舍五入到 15 位数字。打印的数字不同于x
:这可能出于类似的原因而发生,打印时
0.1
显示为0.1
,而不是0.10000000000000001
或通常显示为0.1000000000000000055511151231257827021181583404541015625
(0.1 .hex() == '0x1.999999999999ap-4'
)。此外,binary64 表示不同(2 63与 2 63 +2 12)(问题中提供的示例中唯一一个默认情况下不输出等效表示的示例)。也许优先级是四舍五入到 15 位数字,不管这是否足以获得等效的binary64 表示。不仅 C# 有这种行为,例如,
numpy.array
在 Python 中它默认显示 8 位数字:使用标准
"{0:R}"
格式在 C# 中显示 17 位数字是可能 的,它输出9.2233720368547758E+18
,即再次收到相同的原始 2 63功率:double
邻域中以 IEEE 754 格式表示的序号9223372036854775807
是最接近的是
9223372036854775808
。并且输出的差异仅出现在形成 IEEE 754 二进制浮点数的十进制表示的阶段。在内部,上述所有语言实现都使用 IEEE 754
double
,它在所有情况下都是9223372036854775808
,可以通过从存储的数字中减去其最高有效位来轻松验证。例如,在您的 C# 示例中,只需
x
打印而不是x - 9223000000000000000
“丢失”的低位数字将立即“出现”的方式就足够了:http: //ideone.com/H5eKPe完全相同的事情发生在 Javascript 中。
为什么 0.1 + 0.1 并不总是恰好是 0.2 有点令人费解。如果您查看维基百科,就会清楚双精度数有52 位的螳螂。52 位给出 15-16 位数字。而你还有更多。
更新程序
我找到了一个很棒的查看双精度数字的服务http://www.binaryconvert.com/convert_double.html
我们将9223372036854775807和9223372036854775808驱动到其中,甚至驱动9223372036854775809并看到它们都具有相同的二进制表示 - 0x43E0000000000000。它的反向转换给出9.223372036854775808E18。也就是说,这种格式无法区分三个给定的数字。这回答了问题的第一部分。
为什么不同的语言对这个数字的显示方式不同?首先,每种语言都使用某种默认系统进行输出格式化。我们不知道语言/编译器/平台的开发人员在那一刻到底在吃什么/喝什么。其次,每个开发人员都试图制作自己的、唯一真实和正确的解析器-转换器。
让我们看看 java 输出并将其传递给转换器9223372036854776000 => 0x43E0000000000000 - 我们看到相同的表示。显然java算法是不同的。它显然也被 javascript 使用(的确,并不是所有的事情都是显而易见的 - 有许多不同的实现,但我在 visible / Linux 上测试的所有选项都给出了相同的结果)。
但是夏普这里好像有个bug:9223372036854780000 => 0x43E0000000000002(最后两个)。但后来他们显然纠正了自己并引入了一种特殊的格式 -往返格式
在这种情况下,我们有 9.2233720368547758E+18,相当于 9223372036854775800。在二进制表示中,它将是相同的 0x43E0000000000000。不可能以正确的形式立即强制输出:(
结论。除了 Sharpe 之外的所有内容都被正确推导。完全正确。简单地说,根据历史(显然是专利/自行车规则),他们使用不同的转换算法。夏普脱颖而出,但我认为这有历史原因(速度优化或人为错误)。