Если ваша программа распараллелена, так как она может работать на четырехъядерном ядре, то она вполне может быть недетерминированной.
Представьте, что у вас есть 4 процессора, добавляющие значение с плавающей запятой в одну и ту же ячейку памяти. Тогда вы можете получить
(((InitialValue+P1fp)+P2fp)+P3fp)+P4fp
или
(((InitialValue+P2fp)+P3fp)+P1fp)+P4fp
или любой другой возможный заказ.
Черт, вы можете даже получить
InitialValue+(P2fp+P3fp)+(P1fp+P4fp)
если компилятор достаточно хорош.
К сожалению, сложение с плавающей точкой не является коммутативным или ассоциативным. Арифметика действительных чисел есть, а с плавающей запятой - нет, из-за округления, переполнения и недостаточного значения.
Из-за этого параллельное вычисление FP часто недетерминировано. «Часто», потому что программы, которые выглядят как
on each processor
while( there is work to do ) {
get work
calculate result
add to total
}
будет недетерминированным, потому что количество времени, которое требуется каждому, может сильно различаться - вы не можете предсказать порядок операций. (Хуже, если потоки взаимодействуют.)
Но не всегда, потому что существуют стили параллельного программирования, которые являются детерминированными.
Конечно, многие люди, которым небезразличен детерминизм, работают с целым числом или с фиксированной точкой, чтобы избежать проблемы. Мне особенно нравятся супераккумуляторы, 512, 1024 или 2048-битные числа, к которым можно добавлять числа с плавающей запятой, без ошибок округления.
Что касается однопоточного приложения: компилятор может переставлять код. Разные сборники могут давать разные ответы. Но любой конкретный двоичный файл должен быть детерминированным.
Если ... вы не работаете на динамическом языке. Это выполняет оптимизацию, которая переупорядочивает вычисления FP, которые меняются со временем.
Или, если ... действительно длинный выстрел: у Itanium были некоторые особенности, такие как ALAT, которые делали даже однопоточный код недетерминированным. Это вряд ли повлияет на вас.