Ответ 1
Ваша проблема связана с ограниченной доступной точностью чисел с плавающей запятой.
Пока
1.0f + 1.0f == 2.0f,
Вы обнаружите, что
16777216.0f + 1.0f == 16777216.0f
Дополнительный 1.0f просто выброшен, так как 16777217 не может быть представлен в формате float
.
Глядя на ваш результат - 1.67772e + 007 - ясно, что это именно то, что произошло.
Ожидаемый результат, 100000000.0, довольно много (6x) больше, чем 16777216.0f, но как только сумма достигает 16777216.0f, он остается там для остальных 8327884 дополнений 1.0f.
Решение: попробуйте использовать double
вместо float
, который доходит до 9007199254740992.0
, прежде чем ударить эту проблему.
Почему?
В плавающей точке с одинарной точностью доступно только 24 бита точности, а 2 ^ 24 - 16777216. Невозможно кодировать 16777217 в 24 бит, поэтому он просто остается на уровне 16777216, исходя из того, что он достаточно близко к реальный ответ. Реальная проблема возникает, когда вы добавляете большое количество очень маленьких чисел в большое число, где сумма небольших чисел значима по отношению к большой, но индивидуально они не являются.
Обратите внимание, что 16777216.0f не является наибольшее число, которое может быть представлено в
float
, но просто представляет предел точности. Например, 16777216.0f x 2 ^ 4 + 2 ^ 4 = > 16777216.0f x 2 ^ 4
double
имеет 53 бит точности, поэтому он может кодировать до 2 ^ 53 или 9007199254740992.0
, прежде чем попасть в точку, в которой сбой 1.0d
не выполняется.
Эта проблема также представляет собой еще одну опасность для параллельных операций с плавающей запятой - добавление с плавающей запятой не является ассоциативным, другими словами, ваш последовательный алгоритм:
Sum(A) = (...((((A1 + A2) + A3) + A4) ... A10000)
Может выдавать другой результат из параллелизированной версии:
Sum(A) = (...((((A1 + A2) + A3) + A4) ... A1000)
+ (...((((A1001 + A1002) + A1003) + A1004) ... A2000)
+ (...((((A2001 + A2002) + A2003) + A2004) ... A3000)
...
+ (...((((A9001 + A9002) + A9003) + A9004) ... A10000)
поскольку любой данный шаг может потерять точность в разной степени.
Это не означает, что это более правильно, но вы можете получить неожиданные результаты.
Если вам действительно нужно использовать float
, попробуйте следующее:
- сортируйте свои номера от большинства отрицательных до самых положительных (порядок (N log N))
- добавить каждую пару по очереди: B1: = A1 + A2, B2: = A3 + A4, B3: = A5 + A6 это создает новый список B, наполовину длинный начальный
- повторите эту процедуру на B, чтобы получить C, C, чтобы получить D и т.д.
- остановитесь, когда осталось только один номер.
Обратите внимание, что это изменяет вашу алгоритмическую сложность от операции O (N) до операции O (N log N), но скорее дает правильное число. Это довольно параллелизуемо. Вы можете объединить операции сортировки и суммы, если вы умны в этом.