Я сильно подозреваю, что это потому, что арифметика с двойными числами не является действительно ассоциативной. Информация может быть потеряна при суммировании значений, и в точности какая информация будет потеряна, будет зависеть от порядка операций.
Вот пример, показывающий этот эффект:
using System;
class Test
{
static void Main()
{
double d1 = 0d;
for (int i = 0; i < 10000; i++)
{
d1 += 0.00000000000000001;
}
d1 += 1;
Console.WriteLine(d1);
double d2 = 1d;
for (int i = 0; i < 10000; i++)
{
d2 += 0.00000000000000001;
}
Console.WriteLine(d2);
}
}
В первом случае мы можем добавлять очень маленькие числа много раз, пока они не станут достаточно большими, чтобы оставаться актуальными при добавлении в 1.
Во втором случае добавление 0,00000000000000001 к 1 всегда приводит только к 1, так как в двойном коде недостаточно информации для представления 1.00000000000000001 - поэтому конечный результат все еще равен 1.
РЕДАКТИРОВАТЬ: Я думал о другом аспекте, который может сбить с толку. Для локальных переменных JIT-компилятор может (и может) использовать 80-битные регистры FP, что означает, что арифметика может выполняться с меньшими потерями информации. Это не случай переменных экземпляра, которые обязательно должны быть 64-битными. В вашем случае Parallel.For переменная total
будет фактически переменной экземпляра в сгенерированном классе, потому что она захвачена лямбда-выражением. может изменить результаты, но вполне может зависеть от архитектуры компьютера, версии CLR и т. Д.