除法时的十进制舍入误差(C#)

5
我基本上有四个数字(比如100、200、300、400),我需要计算的概率是100 /(100 + 200 + 300 + 400),200 /(100 + 200 + 300 + 400),依此类推。
当我使用十进制数据类型存储这些概率时,由于舍入问题,它们不会加起来等于1。有没有最好的方法可以解决这个问题而不会使概率太不准确?基本上,我要进行这个计算很多次,所以我不想把所有的除法都改成Math.Round之类的东西。:|

2
你确定你正在使用 decimial 吗?应该是精确的 decimal。你可能在使用 double,或者使用字面量,例如 1.0(应该是 1.0M 表示 decimal)。 - Marc Gravell
2
请发布您的代码,以使此问题与我们相关。 - V4Vendetta
1
如果您想保留精确性,请查看“分数”类/结构。 - leppie
我正在使用decimal。这是我的代码示例: probabilities[i, j] = ((decimal)Count[i, j]) / ((decimal)(NUMBER_OF_TIMESTEPS - numberOfTimestepsToSkip)); 双重数组probabilities是decimal类型。 - Matt
1
@Matt,@Marc:Matt,你的例子有误导性;因为分母加起来是1000,是10的幂,所以Marc是正确的;这将被十进制精确地表示。如果您有不同的分母,它们不会加起来成为10的幂,则会出现舍入误差,因为小数总是四舍五入为可以用小数表示的内容。 - Eric Lippert
抱歉,是的,那个例子不太好。通常情况下,这些数字并不那么好看。 - Matt
2个回答

7
解决方案很简单:如果做某事会疼,那就不要做那件事
如果你有理性的概率,即概率是整数比例,并且你希望它们加起来恰好为一,则不要在一开始将其转换为十进制或双精度。使用任意精度有理数类型来表示任意精度的有理数。
Microsoft Solver Foundation中包含了任意精度有理数类型;你可以下载并使用它。或者,你可以通过创建一个具有两个BigIntegers作为分子和分母的不可变结构体,然后编写所需运算符的实现来轻松编写自己的有理数类型。

1

没有任何一种 Math.Round 方法可以解决您想要的问题。问题在于所有的舍入必须共同工作--即意识到它们各自是正确的,但放在一起却是错误的。

处理除法舍入误差的一种方法是调整数值以适应它:

List<decimal> decimals = new List<decimal>() { 100m, 200m, 300m, 300m };
decimal total = decimals.Sum();

List<decimal> probabilities = decimals.Select(x => x / total).ToList();
decimal sum = probabilities.Sum();
decimal error = 1.0m - sum;
Console.WriteLine("{0}, {1}", sum, error);

probabilities[0] += error; //put all of the error into the first item.
decimal newSum = probabilities.Sum();

Console.WriteLine(newSum);

更复杂的方法是将误差分散到数值中。例如,将0.0000000000000000000000000004分散到4个值而不是1个值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接