Would casting a calculated double number into decimal correct the precision error?
我正在开发一个计算PPM并检查它是否大于某个阈值的应用程序。我最近发现了浮点计算的精度误差。
1 2 3 4 5 6 7 8 9 10 11 12 | double threshold = 1000.0; double Mass = 0.000814; double PartMass = 0.814; double IncorrectPPM = Mass/PartMass * 1000000; double CorrectedPPM = (double)((decimal)IncorrectPPM); Console.WriteLine("Mass = {0:R}", Mass); Console.WriteLine("PartMass = {0:R}", PartMass); Console.WriteLine("IncorrectPPM = {0:R}", IncorrectPPM); Console.WriteLine("CorrectedPPM = {0:R}", CorrectedPPM); Console.WriteLine("Is IncorrectPPM over threshold?" + (IncorrectPPM > threshold) ); Console.WriteLine("Is CorrectedPPM over threshold?" + (CorrectedPPM > threshold) ); |
上述代码将产生以下输出:
1 2 3 4 5 6 |
如您所见,计算的ppm
我注意到,如果我将计算出的双精度数转换成十进制,然后再将它转换回双精度数,那么
问题:有人知道在这种情况下,计算机如何知道当转换为十进制时,它应该将
Does anyone know how the computer know in this case that it should change the
1000.0000000000002 value to1000 when casting to decimal?
首先是演员:
1 | (decimal)IncorrectPPM |
相当于构造函数调用,请参见以下内容:
1 |
如果您在msdn页面上阅读有关decimal构造函数的内容,您会发现以下注释:
This constructor rounds value to 15 significant digits using rounding to nearest. This is done even if the number has more than 15 digits and the less significant digits are zero.
那意味着
1 2 3 | 1000.0000000000002 ^ ^ 15th 17th significant digit |
将四舍五入为:
1 2 3 | 1000.00000000000 ^ 15th significant digit |
Can I rely on this trick to avoid the precision issue of double calculation?
不,在计算
1 2 3 | 1000.000000000006 ^ 15th significant digit |
将四舍五入为:
1 2 3 | 1000.00000000001 ^ 15th significant digit |
要解决与阈值比较的问题,通常有两种可能性。
在你的
1 | double threshold = 1000.0001; |
将你的
1 | double CorrectedPPM = (double)((decimal)IncorrectPPM); |
到:
1 2 | /* 1000.000000000006 will be rounded to 1000.0000 */ double CorrectedPPM = Math.Round(IncorrectPPM, 4); |
使用
要么你的阈值太小,要么你把结果四舍五入到一定数量的小数。小数越多,评估就越精确。
1 2 3 4 5 | double threshold = 1000.0; double Mass = 0.000814; double PartMass = 0.814; double IncorrectPPM = Mass/PartMass * 1000000; double CorrectedPPM = Math.Round(IncorrectPPM,4); // 1000.0000 will output 1000 |
你可以随心所欲地做到精确。
decimal 是一个固定的点号,这意味着它提供了一个固定的小数位数。使用固定点号进行计算时,需要非常频繁地执行舍入操作。例如,如果将数字100.0006除以10,则必须将该值四舍五入到10.0001,因为小数点固定为四位小数。这种舍入误差在经济计算中通常是很好的,因为您有足够的小数,并且该点固定为小数点(即以10为底)。double 是一个浮点数,存储方式不同。它有一个符号(定义数字是正数还是负数)、一个尾数和一个解释词。尾数是一个介于0和1之间的数字,带有一定数量的有效数字(即我们所说的实际精度)。指数定义小数点在尾数中的位置。所以小数点在尾数中移动(因此是浮点)。浮点数对于计算测量值和进行科学计算非常有用,因为通过计算精度保持不变,并且可以最小化舍入误差。
您所面临的基本问题是,您只能依赖其精度范围内的浮点数的值。这包括尾数的实际精度和通过计算累积的舍入误差。另外,由于尾数是以二进制格式存储的,当您将尾数与
在实践中,这意味着当您比较浮点数时,您必须始终知道有多少位数是重要的。请注意,这是指总位数(即小数点前后的位数)。一旦知道了精度(或选择一个适合您的精度并提供足够的误差范围),您就可以决定需要多少位数字来舍入您的值以进行比较。假设根据您的数据,六位十进制数字的精度是适当的,您可以这样与阈值进行比较:
1 | bool isWithinThreshold = Math.Round(PPM, 6) > 1000D; |
请注意,您只进行舍入比较,而不舍入值。
在转换到