c# - 为什么解析后的 double 不等于假定具有相同值的初始化 double ?

标签 c# math double mspec

当我执行这一行时:

double dParsed = double.Parse("0.00000002036");

dParsed实际得到的值:0.000000020360000000000002

相比这条线,

double dInitialized = 0.00000002036;

在这种情况下,dInitialized 的值正好是 0.00000002036

它们在调试器中: Difference between double.Parse and an initializer

这种不一致有点烦人,因为我想按照以下方式运行测试:

[Subject("parsing doubles")]
public class when_parsing_crazy_doubles
    {
    static double dInitialized = 0.00000002036;
    static double dParsed;
    Because of = () => dParsed = double.Parse("0.00000002036");
    It should_match = () =>  dParsed.ShouldBeLike(dInitialized);
    }

这当然失败了:

Machine.Specifications.SpecificationException
"":
  Expected: [2.036E-08]
  But was:  [2.036E-08]

在我的生产代码中,“已解析” double 是从数据文件中读取的,而比较值则被硬编码为对象初始值设定项。在数百条记录中,有 4 或 5 条不匹配。原始数据出现在文本文件中是这样的:

0.00000002036 0.90908165072 6256.77753019160

因此被解析的值只有 11 位小数。解决这种不一致的任何想法?

虽然我承认比较 double 是否相等是有风险的,但令我惊讶的是,当文本用作对象初始值设定项时,编译器可以获得准确的表示,但 double.Parse 在解析时无法获得准确的表示完全一样的文字。如何将解析的 double 限制为小数点后 11 位?

最佳答案

Compared to this line,

double dInitialized = 0.00000002036;

in which case the value of dInitialized is exactly 0.00000002036


如果您有任何类似于商用计算机的东西,dInitialized 不会初始化为 0.00000002036。不可能是因为 10 进制数 0.00000002036 在 2 进制中没有有限表示。

您的错误是期望两个 double 比较相等。这通常不是一个好主意。除非你有很好的理由并且知道你在做什么,否则最好不要比较两个 double 是否相等。而是测试两者之间的差异是否在零的某个小 epsilon 范围内。

正确获取 epsilon 的大小有点棘手。如果您的两个数字都很小(例如,小于一个),则 1e-15 的 epsilon 可能很合适。如果数字很大(例如,大于 10),那么小的 epsilon 值相当于测试相等性。


编辑:我没有回答问题。

How can I limit the parsed doubles to 11 decimal places?

如果您不必担心非常小的值,

static double epsilon = 1e-11;
if (Math.Abs(dParsed-dInitialized) > epsilon*Math.Abs(dInitialized)) {
    noteTestAsFailed();
}

您应该能够安全地将 epsilon 更改为 4e-16。


编辑 #2: 为什么编译器和 double.Parse 为同一文本生成不同的内部表示?

这很明显,不是吗?编译器和 double.Parse 使用不同的算法。所讨论的数字 0.00000002036 非常接近是否应使用向上舍入或向下舍入来产生在所需值 (0.00000002036) 半个 ULP 范围内的可表示值的风口浪尖。 “正确”值是在所需值的半个 ULP 范围内的值。在这种情况下,编译器做出正确的选择舍入值的决定,而解析器做出错误的选择舍入值的决定。

值 0.00000002036 是一个讨厌的极端情况。它不是一个完全可表示的值。可以精确表示为 IEEE double 的两个最接近的值是 6153432421838462/2^78 和 6153432421838463/2^78。这两者之间的中间值是 12306864843676925/2^79,非常非常接近 0.00000002036。这就是使它成为极端情况的原因。我怀疑您找到的所有编译值与 double.Parse 中的值不完全相同的值都是极端情况,即所需值几乎位于两个最接近的可精确表示值之间的一半的情况。


编辑#3:

这里有许多不同的方式来解释 0.00000002036:

  • 2/1e8 + 3/1e10 + 6/1e11
  • 2*1e-8 + 3*1e-10 + 6*1e-11
  • 2.036 * 1e-8
  • 2.036/1e8
  • 2036 * 1e-11
  • 2036/1e11

在理想的计算机上,所有这些都是相同的。不要指望在使用有限精度算术的计算机上会出现这种情况。

关于c# - 为什么解析后的 double 不等于假定具有相同值的初始化 double ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21484761/

相关文章:

c# - FormsAuthentication.SetAuthCookie 不起作用

c# - 如何在控制台打印类属性?

javascript - 小数幂的计算方法

c++ - 如何在 C++ 中以字符串形式递增数字?

c - 雅可比方法适用于 double 型,但适用于浮点型。怎么了?

c# - Xamarin Forms 观看来自 .xaml.cs 类的 ViewModel 属性

c# - 在 C# 中处理货币金额的最佳实践

c - 斐波那契数列代码

machine-learning - 如何更新神经网络反向传播中的偏差?

java - Java 中具有双值的有限机