在 C# (.Net 7) 中:
float a = 13082987520.00f;
Console.WriteLine(a);
Console.WriteLine($"{a:N2}");
Console.WriteLine($"{a:0.00}");
我得到:
1.3082988E+10
13,082,987,520.00
13082990000.00
特别要注意最后两行之间的数字差异:
13082987520
vs
13082990000
当我尝试将此类数字写入 CSV 文件时,这会引起很多混乱。 在这种预期的行为中? 我们该如何解释呢?
注意这个问题与 ToString("N2") 和 ToString("0.00") 之间的差异非常不同。我知道格式方面的差异,但在这里我指出浮点数打印的一个潜在的意外实现细节(并且可能是一个错误?)。如上例所示,显然“N2”不仅仅是添加千位分隔符,而“0.00”则不会。
对于 double 则不存在同样的问题:
double b = 13082987520.00;
Console.WriteLine(b);
Console.WriteLine($"{b:N2}");
Console.WriteLine($"{b:0.00}");
13082987520
13,082,987,520.00
13082987520.00
这里的问题是:
a.ToString("N2").Replace(",", string.Empty)
?来自 ECMA-334:2022 C# 语言标准,第 1 章8.3.7:
浮点类型
...
类型可以表示值......精度为7位数字。float
(更多关于this问题中的浮点近似,简单的32位IEEE浮点数不能提供超过7位的精度)
因此这两个值都是正确的近似值,四舍五入到 7 位的值提供相等的四舍五入值:
13082987520
13082990000
^
|
1234567
浮点数适合近似值。对于精确值(例如,用于货币计算),您应该使用
decimal
代替。来自相同的标准通道。 8.3.8:
类型是适合金融和货币计算的128位数据类型。decimal
比较在 .NET Framework 4.8.1 和 .NET 7 下运行的代码的行为很有启发性:
// .NET FX 4.8.1 | .NET 7
Console.WriteLine(a); // 1.308299E+10 | 1.3082988E+10
Console.WriteLine(a.ToString("N2")); // 13,082,990,000.00 | 13,082,987,520.00
Console.WriteLine(a.ToString("0.00")); // 13082990000.00 | 13082990000.00
可以说,它们都是“正确的”。他们同意
a
的值精确到小数点后七位,这是 float
数据类型承诺支持的全部内容。
.NET Core 3.0 改进了浮点格式化代码,使其符合 IEEE 754-2008 标准。
ToString()
现在返回最短的可往返字符串,而 ToString("N2")
返回精确值中的所有可用数字。
float
值进行舍入?显然,此行为是为了向后兼容:
// SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that
// custom format strings return the same string as in previous releases when the format
// would return x digits or less (where x is the value of the corresponding constant).
private const int SinglePrecisionCustomFormat = 7;
尝试使用“F2”定点格式:
Console.WriteLine($"{a:F2}"); // 13082987520.00