提问人:makigjuro 提问时间:5/14/2021 更新时间:5/14/2021 访问量:221
除法 C 后双精度和小数的数值精度#
Numerical Precision of doubles and decimals after division C#
问:
我正在处理很多数学运算,主要是除法,在完成所有计算后,所有数字都与初始状态匹配,这一点非常重要。 例如,我正在 C# 中测试以下双精度代码:
double total = 1000;
double numberOfParts = 6;
var sumOfResults = 0.0;
for (var i = 0; i < numberOfParts; i++)
{
var result = total / numberOfParts;
sumOfResults += result;
}
此代码给出了 sumOfResults 的结果:999.9999999999999,我预计为 1000。 使用小数时也会发生类似的问题,只是更精确:
decimal total = 1000m;
decimal numberOfParts = 6m;
var sumOfResults = decimal.Zero;
for (var i = 0; i < numberOfParts; i++)
{
var result = total / numberOfParts;
sumOfResults += result;
}
预期的 sumOfResults 为 1000M,但发现 1000.00000000000000000000001M。因此,即使在这里,当我需要与1000的初始状态进行比较时,我也永远不会拥有与数字除法之前相同的状态。
我知道数值分析字段和所有内容,但是是否有一些库可以帮助我在所有除法结果的总和后获得 1000 的确切数字?
答:
如果你正在寻找确切的结果,你必须使用有理数;有很多实现类型的程序集;比如说,你可以尝试使用我自己的 HigherArithmetics(它适用于 .Net 5):BigRational
using HigherArithmetics.Numerics;
...
BigRational total = 1000;
BigRational numberOfParts = 6;
BigRational sumOfResults = 0;
for (var i = 0; i < numberOfParts; i++) {
var result = total / numberOfParts;
sumOfResults += result;
}
Console.Write(sumOfResults);
结果:
1000
但是,如果要使用标准或类型,则必须与公差进行比较:double
decimal
double tolerance = 1e-6;
double total = 1000;
double numberOfParts = 6;
double sumOfResults = 0;
for (var i = 0; i < numberOfParts; i++) {
var result = total / numberOfParts;
sumOfResults += result;
}
sumOfResults = Math.Abs(total, sumOfResults) <= tolerance
? total
: sumOfResults;
Console.Write(sumOfResults);
最后,还有一种可能性是四舍五入的答案:
sumOfResults = Math.Round(sumOfResults, 6);
评论
1ulp
x
eps(x) = 2^(floor(log(x,2))-52)
eps
1ulp
1ulp
u1lp
这更像是人类的感知,而不是真正的数字问题。由于机器精度的原因,几乎每个浮点数都不准确。从数学上讲,1000.0 和 999.999999999999997 之间的差值对于大多数操作来说已经足够了。
这个解决方案对你来说可能看起来很奇怪,但它可以解决计算读数带来的不准确焦虑。
double total = 1000;
double numberOfParts = 6;
var sumOfResults = 0.0;
for (var i = 0; i < numberOfParts; i++)
{
var result = total / numberOfParts;
sumOfResults += result;
}
Console.WriteLine((float)sumOfResults);
// 1000
只需降低人类可读输出的精度 您看到提高精度如何使事情变得更糟,因此请反其道而行之。系统在一定程度上执行此操作,因为函数会舍入最低有效位。double.ToString()
或者,您可以使用“gXXX”格式说明符控制要显示的有效位数。
Console.WriteLine($"{sumOfResults:G5}");
// 1000
总之,您看到的“问题”在所有具有 IEEE 754 浮点类型的计算机中都很常见,并且大多数数字都未准确表示。
例如,下面的定义如下所示,如下所示Math.PI
C#
环境 | 价值 |
---|---|
π | 3.141592653589793238462643383279502884... |
.NET 5 |
3.141592653589793 |
Framework v4.8 |
3.141592653589791 |
评论
Match.Abs(actual - expected) < epsilon
epsilon
say, BigRational
double
decimal
bool equal = Math.Abs(expected - actual) <= tolerance;