我有一个案例,我在使用多线程时看到每次运行的计算结果都不同。我在Visual Studio 2017中创建了单元测试,演示了该问题(Windows 10上的.NET 4.7.1):
[TestMethod, TestCategory("Simulator Search")]
public void MultiThreadTest ()
{
Random rand = new Random(1701);
List<Double> list = new List<Double>();
for (Int32 i = 0; i < 1000000; ++i)
list.Add(rand.NextDouble());
Double singlethreadsum = list.Sum();
Assert.AreEqual(500164.78615913482, singlethreadsum, "Single thread sum is different");
Object lockobj = new Object();
Double multithreadsum1 = 0.0;
System.Threading.Tasks.Parallel.ForEach(list, new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = 1 }, x =>
{
lock (lockobj)
multithreadsum1 += x;
});
Assert.AreEqual(500164.78615913482, multithreadsum1, "Multithread thread sum (max parallelism=1) is different");
Double multithreadsum2 = 0.0;
System.Threading.Tasks.Parallel.ForEach(list, x =>
{
lock (lockobj)
multithreadsum2 += x;
});
Assert.AreEqual(500164.78615913482, multithreadsum2, "Multithread thread sum is different");
}
这不会为multithreadsum2产生一致的结果。我知道双精度数字不是精确的表示,这是由于四舍五入的问题。我想知道是否有人知道如何缓解这个问题?请记住,这是一个简化的示例 - 真正的代码需要多线程,因此解决这个问题不是解决方案。
答案 0 :(得分:3)
问题是在多线程情况下,求和的顺序不同。在两个double
个数的总和中,由于精度误差,结果的一小部分可能会丢失。如果您以相同的顺序添加数字,则此具体损失将始终相同。但是如果你有效地随机化了求和的顺序,那么实际损失将随着程序的每次运行而不同。
正如一个指示,尝试以相反的顺序对所有数字求和 - 我的赌注是结果会再次不同。
如果这种效果有重要区别,那么你将不得不使用非平凡的算术方法来处理它。否则,通常的方法是将某些精度作为目标,例如precision=1e-6
,并考虑两个double
个数,只要它们的绝对差值小于precision
。
在相关的说明中,是极端情况,其中求和错误可能会累积并变得太大。特别是,当添加非常小和非常大的数字时,少数甚至可能完全丢失。在这种情况下,有一种算法可以节省精度,它是这样的:
当应用该算法时,总是在具有相似性的数字之间进行求和,因此精度损失会更小。