Я использую Java в приложении, запущенном на нескольких машинах, и все машины должны получать одинаковые результаты для математических операций. Безопасно ли использовать примитивы Java с плавающей запятой? Или я должен просто использовать математическую библиотеку с фиксированной точкой?
Будут ли операции с плавающей запятой на JVM давать одинаковые результаты на всех платформах?
Ответ 1
В общем, нет. Однако вы можете использовать strictfp
выражения:
В рамках строгого выражения FP все промежуточные значения должны быть элементами установленного значения float или значения двойного значения, подразумевая, что результаты всех строго выраженных FP-выражений должны быть предсказаны арифметикой IEEE 754 для операндов, представленных с использованием одиночных и двойные форматы.
В выражении, которое не является FP-строгим, предоставляется некоторая свобода реализации для использования расширенного диапазона экспонентов для представления промежуточных результатов; грубо говоря, чистый эффект заключается в том, что расчет может привести к "правильному ответу" в ситуациях, когда исключительное использование установленного значения с плавающей запятой или двойного значения может привести к переполнению или переполнению.
Ответ 2
В дополнение к strictfp
также StrictMath
, который требует, чтобы результаты были предсказуемыми для трансцендентных и других функций.
Ответ 3
JVM должен последовательно выполнять спецификацию IEEE, и эта спецификация является очень технической и точной. Примитивы с плавающей запятой float и double одинаковы на всех платформах.
Разница заключается только в обработке промежуточных результатов и что реализация виртуальной машины может использовать форматы с расширением float-extended-exponent и double-extended-exponent при оценке выражений с участием локальных пользователей в одном и том же кадре исполнения.
Итак, если у вас есть код вроде:
double d1 = 0.342;
double d2 = 1.328479;
double d3 = 4.99384728796;
System.out.println(d1 * d2 / d3);
и это не в контексте strictfp, возможно, что во время выполнения у вас будут различия между разными JVM. Это связано с тем, что при оценке выражения d1 * d2/d3 промежуточный результат d1 * d2 используется в выражении "промежуточный результат" /d 3, а JVM может использовать форматы с расширением float-extended-exponent и double-extended-exponent для хранения "промежуточного результата".
Чтобы обойти это, вы можете использовать strictfp или StrictMath, как другие ответили здесь, или избегать использования промежуточных результатов в ваших выражениях. Один из способов сделать это - сохранить любой промежуточный результат в куче. Например, следующим образом:
class MyClass {
static double d1 = 0.342;
static double d2 = 1.328479;
static double d3 = 4.99384728796;
static double intermediate_result = 0d;
public static void main(String[] args) {
intermediate_result = d1 * d2;
System.out.println(intermediate_result / d3);
}
}