Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3 - Лексические структуры языка. Типы данных. И...docx
Скачиваний:
9
Добавлен:
19.11.2019
Размер:
93.32 Кб
Скачать

6.3 Непредвиденные обстоятельства использования плавающей точки

В связи с особым поведением бесконечности, NaN и 0 определенные трансформации и оптимизации могут показаться безвредными, но при применении к числам с плавающей точкой будут приводить к ошибкам. Например, несмотря на то, что равенство 0.0-f и -f кажется очевидным, но оно ложно, если f равно 0. Существуют и другие подобные проблемы, некоторые из которых показаны в таблице 2.

Таблица 2. Ошибочные представления о числах с плавающей точкой

Данное выражение...

не обязательно тождественно...

при условии, что...

0.0 - f

-f

f равно 0

f < g

! (f >= g)

f или g являются NaN

f == f

верно

f является NaN

f + g - g

f

g является бесконечностью или NaN

6.4 Ошибки округления

Арифметика чисел с плавающей точкой не отличается особой точностью. Тогда как некоторые числа, например, 0.5, можно точно представить как двоично-десятичные (основание 2) (поскольку 0.5 равно 2-1), но другие числа, например, 0.1 - невозможно. В итоге операции над числами с плавающей точкой могут привести к ошибкам округления, выдавая результат, близкий - но не равный - тому результату, который можно было ожидать. Например, простое вычисление, приведенное ниже, равняется 2.600000000000001, а не 2.6:

double s=0;

for (int i=0; i<26; i++)

s += 0.1;

System.out.println(s);

Аналогично, умножение .1*26 выдает результат, отличный от прибавления .1 к самому себе 26 раз. Ошибки округления могут оказаться даже более серьезными при преобразовании типа от вещественного к целому, поскольку при преобразовании к целому типу нецелая часть отбрасывается, даже для вычислений, которые "выглядят похожими", они должны иметь целые значения. Например, следующие выражения:

double d = 29.0 * 0.01;

System.out.println(d);

System.out.println((int) (d * 100));

на выходе дадут:

0.29

28

что не совсем соответствует первоначально ожидаемому результату.

6.5 Рекомендации по сравнению чисел с плавающей точкой

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

Лучше всего вообще стараться избегать сравнений чисел с плавающей точкой. Конечно же, это не всегда возможно, но Вы должны понимать ограничения сравнения чисел с плавающей точкой. Если Вам нужно сравнить числа с плавающей точкой для того, чтобы узнать, тождественны ли они, то вместо этого следует сравнивать абсолютное значение их разности с каким-либо предварительно выбранным значением эпсилон. То есть, таким образом, Вы проверяете насколько они "близки". (Если Вам не известен коэффициент масштабирования основных измерений, то использование проверки "abs(a/b - 1) < эпсилон", вероятно, будет более надежным, чем простое сравнение разности.) Даже проверка значения для того, чтобы узнать, является ли оно большим или меньшим, чем ноль, является рискованной - вычисления, в результате которых "предполагается" получить значение чуть больше нуля, на самом деле могут привести к числам чуть меньше нуля вследствие суммарных ошибок округления.

Неупорядоченность NaN увеличивает вероятность появления ошибок при сравнении чисел с плавающей точкой. Хорошим практическим приемом для предотвращения многих сбоев, связанных с бесконечностью и NaN при сравнении чисел с плавающей точкой, является явная проверка валидности значения, вместо попыток исключения невалидных значений. В листинге 1 присутствуют две возможных реализации схемы настройки для свойства, которое может принимать только неотрицательные значения. В первом случае будет допустим NaN, во втором - нет. Вторая форма является более предпочтительной, так как она явно проверяет ту область значений, которую Вы считаете валидной.

Листинг 1. Лучшие и худшие способы для обеспечения неотрицательного плавающего значения

// Trying to test by exclusion - this doesn't catch NaN or infinity

public void setFoo(float foo) {

if (foo < 0)

throw new IllegalArgumentException(Float.toString(f));

this.foo = foo;

}

// Testing by inclusion - this does catch NaN

public void setFoo(float foo) {

if (foo >= 0 && foo < Float.INFINITY)

this.foo = foo;

else

throw new IllegalArgumentException(Float.toString(f));

}