2010-05-24 2 views
8

В 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 десятичных знаков (#. ###). Как я могу справиться с этой проблемой, чтобы умножить поплавки и сравнить их с надежностью?

+1

Заявить float как: 'float a = 1.2f;' и удваивается как 'double d = 1.2d;' Также в вашем if-statement: 'if (c == 3.6f)' –

+1

В дополнение к @bobah ' Ответ, я рекомендую посмотреть на функцию 'Math.ulp()'. – aeracode

ответ

6

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

16

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

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

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

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

+2

Рекомендация по сравнению с использованием толерантности является нецелесообразным советом, поскольку она уменьшает ложные сообщения о неравенстве за счет увеличения ложных сообщений о равенстве, и вы не можете знать, приемлемо ли это для приложения, о котором вы ничего не знаете. Приложение может быть «более заинтересовано» в поиске неравенства, чем поиск равенства или может иметь другие спецификации, которые ему необходимо выполнить. –

+2

@ Эрик. При работе с числами с плавающей запятой нет понятия идентичности или неравенства, существует только понятие расстояния. Если в формуле, которую я дал в ответе, вы заменяете «<' with '>», вы получите критерии для сравнения чисел с плавающей запятой для несправедливости с точки зрения расстояния. Побитовое представление чисел чисел с плавающей запятой в памяти компьютера не представляет интереса для большинства практических приложений. – bobah

+0

Речь идет не о битах, представляющих число; это касается их ценностей. Арифметика с плавающей точкой имеет равенство. Стандарт IEEE 754 определяет объекты с плавающей точкой, чтобы точно представлять конкретные числа, а не представлять интервалы. –

4

Я думаю, что это не имеет ничего общего с 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"); 
    } 
} 
0

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

+0

Сравнение с использованием epsilon лучше: подумайте, что произойдет, если 'f1 == 3.1414999999999' и' f2 == 3.1415000000001'. –

+0

Дерьмо. Я, хотя у меня было это :-) уверен. Я с тобой согласен. Сравнение с использованием epsilon намного лучше. Но точно ли он сравнивает два поплавка с его тремя первыми десятичными знаками? – aioobe

2

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

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

+1

+1 - * все * системы с плавающей запятой с конечной точностью страдают от этой проблемы. Не важно, какую базу вы выберете, некоторые рациональности не могут быть точно представлены. –

2

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

Сравнить поплавки с: 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

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

Он работает, рассматривая двоичное представление числа с плавающей запятой. Большая часть осложнений связана с тем, что знак чисел с плавающей запятой не является дополнением к двум. После компенсации за это в основном сводится к простому вычитанию, чтобы получить разницу в 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. 
*/ 
public static void 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. 
* @see Utils#compareFloatEquals(float, float, int) 
*/ 
public static void 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

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

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

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

Смежные вопросы