为什么C#和JavaScript处理具有许多有效数字的数字不同?

8
如果JavaScript的Number和C#的double被指定为相同的(IEEE 754), 为什么具有许多有效数字的数字会被不同地处理?
var x = (long)1234123412341234123.0; // 1234123412341234176   - C#
var x =       1234123412341234123.0; // 1234123412341234200   - JavaScript

我并不关心IEEE 754不能表示数字1234123412341234123的事实。我关心的是对于无法完全精确表示的数字,两个实现的行为不同。

这可能是因为IEEE 754规范不够明确,其中一个或两个实现有误,或者它们实现了不同的IEEE 754变体。

这个问题与C#中浮点输出格式化的问题无关。我正在输出64位整数。请考虑以下内容:

long x = 1234123412341234123;
Console.WriteLine(x); // Prints 1234123412341234123
double y = 1234123412341234123;
x = Convert.ToInt64(y);
Console.WriteLine(x); // Prints 1234123412341234176

同一个变量因为值不同而打印出不同的字符串。

为什么要强制转换为长整型? - Alex Sikilinda
你正在比较一个双精度浮点数和一个单精度浮点数。如果你想要能够进行大数计算,我建议你使用http://mikemcl.github.io/decimal.js/,并提供字符串类型的数字来获取你想要的位数。这并没有回答你的问题,但如果你遇到了问题,可能会有所帮助。大数通常被保存为公式值,由此可能导致舍入误差。这就是为什么浮点数不精确的原因。你可能在JavaScript中遇到了一个大数舍入不准确的问题。至于原因,你可以参考https://code.google.com/p/v8/。 - Tschallacka
这里还有一个有趣的问题...... .NET通常只显示15位精度,而不是完整的17位。 - xanatos
(1234123412341234123.0).ToString("F") 可以更准确地展示这个问题(输出 1234123412341230000.00)。但正如 这个回答 所描述的,C# 会先将数字四舍五入到15位有效数字。 - Sayse
我认为这个问题与浮点数输出格式无关。长转换已经解决了这个问题。当双倍字面值被解析为一个双倍临时变量时,精度的损失发生了。考虑 (long)1123412341234125.0 的输出。即使它是一个作为双倍数开始生命的 16 位数字,这里也没有四舍五入。 - Hans Malherbe
1
@Sayse 那个答案部分是错误的... 试试 (1234123412341234123.0).ToString("G17") (https://ideone.com/xdfQD6)。是 F 格式化程序出了问题。 - xanatos
3个回答

3
这里存在多个问题...
你正在使用 long 而不是 double。你需要写成以下形式:
double x = 1234123412341234123.0;

或者

var x = 1234123412341234123.0;

另一个问题是.NET在将double转换为string之前会将其四舍五入到15位数字(例如在使用Console.ToString()打印时)。
例如:
string str = x.ToString("f"); // 1234123412341230000.00

例如,参见https://dev59.com/4nM_5IYBdhLWcg3wUxjF#1658420

实际上,这个数字仍然是17位的,只是显示为15位。

如果你执行以下操作,你就可以看到它:

string str2 = x.ToString("r"); // 1.2341234123412342E+18

2
数字的处理方式并没有不同,只是显示方式不同。
.NET 显示数字时具有 15 个有效数字,而 JavaScript 具有 17 个有效数字。双精度浮点数的表示可以容纳 15-17 个有效数字,这取决于它包含的数字。.NET 只显示数字始终支持的位数,而 JavaScript 显示所有数字,但精度限制可能会显示出来。
当指数为 15 时,.NET 开始使用科学计数法,而当指数为 21 时,JavaScript 开始使用科学计数法。这意味着 JavaScript 将在末尾填充零的情况下显示具有 18 到 20 位数字的数字。
在您的示例中将 double 转换为 long 将规避 .NET 显示双精度浮点数的方式。该数字将被转换,而不会隐藏精度限制的四舍五入,因此在这种情况下,您可以看到实际值。之所以第 17 位后不仅是零,是因为该值以二进制形式存储,而不是十进制形式。

1
免责声明:此内容基于wikipedia页面的标准。
根据该标准的维基百科页面,十进制64应具有16位数字精度。
除此之外,对于额外的数字,实现者可以自行决定如何处理。
因此,您可能会说该标准规范不足,但这些数字本来就不是为符合标准规范而设计的。两种语言都有处理更大数字的方法,因此这些选项可能更适合您。

强制使用16位精度似乎意味着.NET和Chrome都有问题,因为数字9111234123412341在两种实现中都会变成9111234123412340。 - Hans Malherbe
@HansMalherbe - 但是你是基于它们的字符串表示而不是实际数字来进行判断的(至少在C#中,它们被显示为15 sf)。 - Sayse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接