当我执行这一行时:
double dParsed = double.Parse("0.00000002036");
dparsed实际得到的值是:0.000000020360000000002
与这条线相比,
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);
}
这当然会失败,因为:
机器.规格.规格异常
“”:
期望值:[2.036E-08]
但是是:[2.036E-08]
在我的产品代码中,“已分析”的双精度数是从数据文件中读取的,而比较值是硬编码为对象初始值设定项的。超过数百张唱片,其中有4或5张不匹配。原始数据显示在文本文件中,如下所示:
0.00000002036 0.90908165072 6256.77753019160
所以被解析的值只有11个小数位。有什么解决这种矛盾的办法吗?
虽然我承认比较doubles的相等性是有风险的,但是当文本用作对象初始值设定项时,编译器可以得到精确的表示,这让我很惊讶,但是double.parse在分析完全相同的文本时不能得到精确的表示。如何将解析的双精度数限制为小数点后11位?
最佳答案
与这条线相比,
double dInitialized = 0.00000002036;
在这种情况下,dinitialized的值正好是0.00000002036
如果您有任何远程类似于商品计算机的东西,
dInitialized
不会被精确初始化为0.00000002036。不能是因为基数10 0.00000002036在基数2中没有有限表示。你的错误是期望两个双打比较相等。这通常不是个好主意。除非你有很好的理由并且知道你在做什么,否则最好不要为了平等或不平等而比较两个双倍。取而代之的是测试两者之间的差异是否在0的某个小epsilon范围内。
正确计算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非常接近于是否应该使用舍入或舍入来产生一个可表示的值,该值在所需值(0.00000002036)的半个ulp内。“正确”值是指在所需值的半ULP范围内的值。在这种情况下,编译器选择舍入值的决定是正确的,而解析器选择舍入值的决定是错误的。值0.00000002036是一个糟糕的角落情况。它不是一个完全可表示的值。可以精确表示为ieee双精度的两个最接近的值是61534221838462/2^78和6153434221838463/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年
在理想的计算机上,所有这些都是一样的。在使用有限精度算法的计算机上,不要指望会出现这种情况。