执行此行时:
double dParsed = double.Parse("0.00000002036");
dParsed实际获得的值:0.000000020360000000000002
与此行相比,
double dInitialized = 0.00000002036;
在这种情况下,dInitialized的值恰好是0.00000002036
这里他们在调试器中:
这种不一致很麻烦,因为我想按照以下方式运行测试:
[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]
在我的生产代码中,从数据文件中读取“已解析”的双精度数,而将比较值硬编码为对象初始值设定项。超过数百条记录,其中4或5条不匹配。原始数据显示在文本文件中,如下所示:
0.00000002036 0.90908165072 6256.77753019160
因此,正在解析的值只有11个小数位。解决这种不一致的任何想法?
虽然我接受比较双精度的等式是有风险的,但我很惊讶编译器在文本用作对象初始值设定项时可以获得精确的表示,但是double.Parse在解析时无法得到精确的表示完全相同的文字。如何将解析后的双精度数限制为11位小数?
答案 0 :(得分:4)
与此行相比,
double dInitialized = 0.00000002036;
在这种情况下,dInitialized的值恰好是0.00000002036
如果您有任何与商用计算机非常相似的内容,则dInitialized
不初始化为0.00000002036。它不能是因为基数10 0.00000002036在基数2中没有有限的表示。
你的错误是期待两个双打相等。这通常不是一个好主意。除非你有充分的理由并知道自己在做什么,否则最好不要将两个双打比作平等或不平等。而是测试两者之间的差异是否在零的小ε内。
正确获取该epsilon的大小有点棘手。如果你的两个数字都很小(例如,少于一个),则1e-15的epsilon可能是合适的。如果数字很大(例如,大于10),那么小的epsilon值就相当于测试相等性。
编辑:我没有回答这个问题。
如何将解析后的双打限制为11位小数?
如果您不必担心非常小的值,
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非常接近于是否应该使用向上舍入或向下舍入来产生可表示的值,该值在所需值的一半ULP(0.00000002036)内。 “正确”值是在期望值的ULP的一半内的值。在这种情况下,编译器做出正确的选择舍入值的决定,而解析器做出错误的选择舍入值的决定。
值0.00000002036是一个讨厌的角落案例。它不是一个可以代表的值。可以精确表示为IEEE双精度的两个最接近的值是6153432421838462/2 ^ 78和6153432421838463/2 ^ 78。这两者之间的值是12306864843676925/2 ^ 79,这非常非常接近0.00000002036。这就是为什么这是一个极端的案例。我怀疑你发现编译值与double.Parse
中的值不完全相同的所有值都是极端情况,其中所需值几乎介于两个最接近的可精确表示的值之间。
编辑#3:
以下是解释0.00000002036的多种不同方法:
在理想的计算机上,所有这些都是相同的。不要指望在使用有限精度算术的计算机上就是这种情况。