Манипулирование и сравнение плавающих точек в java

В Java арифметика с плавающей запятой точно не представлена. Например, этот код Java:

float a = 1.2; float b= 3.0; float c = a * b; if(c == 3.6){ System.out.println("c is 3.6"); } else { System.out.println("c is not 3.6"); } 

Печать «c не 3,6».

Меня не интересует точность, превышающая 3 десятичных знака (#. ###). Как я могу справиться с этой проблемой, чтобы умножить поплавки и сравнить их с надежностью?

Общее правило состоит в том, что число с плавающей запятой никогда не должно сравниваться как (a == b) , а скорее (Math.abs(ab) < delta) где delta - небольшое число.

Значение с плавающей запятой, имеющее фиксированное число цифр в десятичной форме, не обязательно имеет фиксированное число цифр в двоичной форме.

Дополнение для ясности:

Хотя строгое сравнение чисел с плавающей запятой имеет очень мало практического смысла, строгое сравнение < и > , напротив, является допустимым прецедентом (пример - логическое срабатывание, когда определенное значение превышает пороговое значение: (val > threshold) && panic(); )

Если вас интересуют фиксированные числа точности, вы должны использовать фиксированный тип точности, такой как BigDecimal , а не по своей сути приблизительный (хотя и высокоточный) тип типа float . Существует множество подобных вопросов о переполнении стека, которые более подробно рассматриваются на многих языках.

Я думаю, что это не имеет ничего общего с Java, это происходит на любом номере с плавающей запятой IEEE 754. Это связано с природой представления с плавающей запятой. Все языки, использующие формат IEEE 754, будут сталкиваться с одной и той же проблемой.

Как было предложено Дэвидом выше, вы должны использовать метод abs classа java.lang.Math для получения абсолютного значения (оставьте положительный / отрицательный знак).

Вы можете прочитать это: http://en.wikipedia.org/wiki/IEEE_754_revision, а также хороший текстовый метод, позволяющий решить проблему достаточно.

 public static void main(String[] args) { float a = 1.2f; float b = 3.0f; float c = a * b; final float PRECISION_LEVEL = 0.001f; if(Math.abs(c - 3.6f) < PRECISION_LEVEL) { System.out.println("c is 3.6"); } else { System.out.println("c is not 3.6"); } } 

Это слабость всех представлений с плавающей запятой, и это происходит потому, что некоторые числа, которые, как представляется, имеют фиксированное число десятичных знаков в десятичной системе, на самом деле имеют бесконечное число десятичных знаков в двоичной системе. И так, то, что вы думаете, это 1.2, на самом деле что-то вроде 1.199999999997, потому что, представляя его в двоичном формате, он должен отрубить десятичные числа после определенного числа, и вы теряете некоторую точность. Тогда умножение на 3 фактически дает 3.5999999 …

http://docs.python.org/py3k/tutorial/floatingpoint.html <- это может объяснить это лучше (даже если это для python, это общая проблема представления с плавающей запятой)

Как и другие авторы писали:

Сравните float с: if (Math.abs(a - b) < delta)

Вы можете написать хороший метод для этого:

 public static int compareFloats(float f1, float f2, float delta) { if (Math.abs(f1 - f2) < delta) { return 0; } else { if (f1 < f2) { return -1; } else { return 1; } } } /** * Uses 0.001f for delta. */ public static int compareFloats(float f1, float f2) { return compareFloats(f1, f2, 0.001f); } 

Таким образом, вы можете использовать его следующим образом:

 if (compareFloats(a * b, 3.6f) == 0) { System.out.println("They are equal"); } else { System.out.println("They aren't equal"); } 

Существует class apache для сравнения парных: org.apache.commons.math3.util.Precision

Он содержит некоторые интересные константы: SAFE_MIN и EPSILON , которые являются максимальными возможными отклонениями при выполнении арифметических операций.

Он также предоставляет необходимые методы для сравнения, равные или круглые удваивания.

Я использую этот бит кода в модульных тестах для сравнения, если результат двух разных вычислений один и тот же, за исключением математических ошибок с плавающей запятой.

Он работает, рассматривая двоичное представление числа с плавающей запятой. Большая часть осложнений связана с тем, что знак чисел с плавающей запятой не является дополнением к двум. После компенсации за это в основном сводится к простому вычитанию, чтобы получить разницу в ULP (поясняется в комментарии ниже).

 /** * Compare two floating points for equality within a margin of error. * * This can be used to compensate for inequality caused by accumulated * floating point math errors. * * The error margin is specified in ULPs (units of least precision). * A one-ULP difference means there are no representable floats in between. * Eg 0f and 1.4e-45f are one ULP apart. So are -6.1340704f and -6.13407f. * Depending on the number of calculations involved, typically a margin of * 1-5 ULPs should be enough. * * @param expected The expected value. * @param actual The actual value. * @param maxUlps The maximum difference in ULPs. * @return Whether they are equal or not. */ public static boolean compareFloatEquals(float expected, float actual, int maxUlps) { int expectedBits = Float.floatToIntBits(expected) < 0 ? 0x80000000 - Float.floatToIntBits(expected) : Float.floatToIntBits(expected); int actualBits = Float.floatToIntBits(actual) < 0 ? 0x80000000 - Float.floatToIntBits(actual) : Float.floatToIntBits(actual); int difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits; return !Float.isNaN(expected) && !Float.isNaN(actual) && difference <= maxUlps; } 

Вот версия для плавания с double точностью:

 /** * Compare two double precision floats for equality within a margin of error. * * @param expected The expected value. * @param actual The actual value. * @param maxUlps The maximum difference in ULPs. * @return Whether they are equal or not. * @see Utils#compareFloatEquals(float, float, int) */ public static boolean compareDoubleEquals(double expected, double actual, long maxUlps) { long expectedBits = Double.doubleToLongBits(expected) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(expected) : Double.doubleToLongBits(expected); long actualBits = Double.doubleToLongBits(actual) < 0 ? 0x8000000000000000L - Double.doubleToLongBits(actual) : Double.doubleToLongBits(actual); long difference = expectedBits > actualBits ? expectedBits - actualBits : actualBits - expectedBits; return !Double.isNaN(expected) && !Double.isNaN(actual) && difference <= maxUlps; } 

Чтобы сравнить два поплавка, f1 и f2 пределах точности #.### Я считаю, что вам нужно будет сделать вот так:

 ((int) (f1 * 1000 + 0.5)) == ((int) (f2 * 1000 + 0.5)) 

f1 * 1000 лифтов 3.14159265... до 3141.59265 , + 0.5 результат в 3142.09265 а (int) отбивает десятичные знаки, 3142 . То есть он включает 3 десятичных знака и округляет последнюю цифру должным образом.

Давайте будем гением компьютера.