Управление и сравнение чисел с плавающей точкой в ​​Java - PullRequest
10 голосов
/ 24 мая 2010

В 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 is not 3.6".

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

Ответы [ 8 ]

19 голосов
/ 24 мая 2010

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

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

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

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

7 голосов
/ 24 мая 2010

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

4 голосов
/ 24 мая 2010

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

Как было предложено Дэвидом выше, вы должны использовать метод abs класса 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");
    }
}
3 голосов
/ 26 июля 2013

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

Работает, глядя на двоичное представление числа с плавающей запятой. Большая часть осложнений связана с тем, что знак чисел с плавающей запятой не является дополнением к двум. После компенсации это сводится к простому вычитанию, чтобы получить разницу в 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.
 * E.g. 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;
}
2 голосов
/ 19 мая 2015

Существует класс apache для сравнения значений типа double: org.apache.commons.math3.util.Precision

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

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

2 голосов
/ 24 мая 2010

Как и другие писали:

Сравнить число с: 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 <code>0.001f</code> 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");
}
2 голосов
/ 24 мая 2010

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

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

0 голосов
/ 24 мая 2010

Чтобы сравнить два числа с плавающей точкой, 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 десятичных знака и правильно округляет последнюю цифру.

...