- •Тема 3. Лексические структуры языка. Примитивные типы данных. Декларация и инициализация переменных. Основные типы операторов.
- •3.1 Примитивные типы данных
- •3.2 Лексические структуры языка
- •3.2.1 Пробелы
- •3.2.2 Идентификаторы
- •3.2.3 Константы
- •3.2.4 Комментарии
- •3.2.5 Разделители
- •3.2.6 Ключевые слова Java
- •4 Операторы
- •4.1 Операция присваивания
- •4.2 Унарные операции
- •4.3 Арифметические бинарные операции
- •4.6 Операции сравнения
- •4.6.1 Логические операции
- •4.7 Условная операция
- •4.8 Приоритет операций
- •4.9 Преобразование и приведение типов при выполнении операций
- •4.10 Переполнение целого числа
- •4.11 Операции с дробными типами
- •4.12 Операция конкатенации строк
- •5 Классы-обертки
- •6 Уловки и ловушки, связанные с плавающей точкой и десятичными числами
- •6.1 Плавающая точка ieee
- •6.2 Специальные числа
- •6.3 Непредвиденные обстоятельства использования плавающей точки
- •6.4 Ошибки округления
- •6.5 Рекомендации по сравнению чисел с плавающей точкой
- •6.6 Не используйте числа с плавающей точкой для точных значений
- •6.7 Большие десятичные дроби для маленьких чисел
- •6.8 Все методы сравнения не созданы равными
- •6.9 Используйте BigDecimal в качестве типа обмена
- •6.10 Построение чисел BigDecimal
6.1 Плавающая точка ieee
Язык Java поддерживает два простых типа с плавающей точкой: float и double, и их прототип - класс-оболочка Float и Double. Они основаны на стандарте IEEE 754, определяющем двоичный стандарт для двоично-десятичных чисел с 32-битовой плавающей точкой и 64-битовой плавающей точкой удвоенной точности.
IEEE 754 представляет числа с плавающей точкой как десятичные числа с основанием 2 в экспоненциальном формате. В IEEE числе с плавающей точкой выделяется 1 бит на знак, 8 бит на порядок и 23 бита на мантиссу, или дробную часть числа. Порядок расшифровывается как целое число со знаком, допускающее как положительный, так и отрицательный экспоненты. Дробь представляется как двоично-десятичное (основание 2) число, где самый старший бит соответствует значению ½ (2-1), следующий бит ¼ (2-2), и так далее. Для плавающей точки с удвоенной точностью на порядок выделяется 11 бит, а на мантиссу - 52 бит. Формат значений плавающей точки IEEE показан на рисунке 1.
Рисунок 1. Формат плавающей точки IEEE 754
Так как любое заданное число может быть представлено в экспоненциальном формате различными способами, то числа с плавающей точкой нормализуются таким образом, чтобы они могли быть представлены как десятичные числа с основанием 2, с 1 слева от десятичной точки, подбирая порядок таким образом, чтобы соблюсти данное требование. Следовательно, например, число 1.25 будет представлено с мантиссой 1.01 и порядком 0:
(-1)
Число 10.0 будет представлено с мантиссой 1.01 и порядком 3:
(-1)
6.2 Специальные числа
Помимо стандартного множества значений, разрешенного кодированием (от 1.4e-45 до 3.4028235e+38 для float), существуют и специальные значения, представляющие бесконечность, минус бесконечность, -0, и NaN (обозначает "не число"). Эти значения существуют для того, чтобы в случае возникновения ошибок, например, арифметического переполнения, извлечения квадратного корня из отрицательного числа и деления на 0, можно было получить результат, представленный в диапазоне значений с плавающей точкой.
У таких специальных чисел есть несколько необычных характеристик. Например, 0 и -0 являются четко различимыми значениями, но при сравнении на тождественность они считаются равными. Деление ненулевого числа на бесконечность дает 0. Специальное число NaN является неупорядоченным, а любое сравнение между NaN и другими значениями с плавающей точкой, с использованием операций ==, < и > выдаст неверно. Даже (f == f) выдаст ложь, если f является NaN. Если Вы хотите сравнить значение с плавающей точкой с NaN, то используйте метод Float.isNaN(). В таблице 1 показаны некоторые особенности бесконечности и NaN.
Таблица 1. Особенности специальных значений с плавающей точкой
Выражение |
Результат |
Math.sqrt(-1.0) |
-> NaN |
0.0 / 0.0 |
-> NaN |
1.0 / 0.0 |
-> бесконечность |
-1.0 / 0.0 |
-> -бесконечность |
NaN + 1.0 |
-> NaN |
бесконечность + 1.0 |
-> бесконечность |
бесконечность + бесконечность |
-> бесконечность |
NaN > 1.0 |
-> ложь |
NaN == 1.0 |
-> ложь |
NaN < 1.0 |
-> ложь |
NaN == NaN |
-> ложь |
0.0 == -0.01 |
-> истина |
Простой тип с плавающей точкой и класс-оболочка с плавающей точкой по-разному выполняют сравнение
Еще более усугубляет положение еще и то, что правила сравнения NaN и -0 у простого типа с плавающей точкой и класса-оболочки Float различаются. Для значений float, сравнение на тождественность двух значений NaN выдаст ложь, но при сравнении двух NaN объектов Float при использовании Float.equals(), будет выдано истина. Обоснованием этому может служить то, что иначе будет невозможно использовать NaN объект Float в качестве ключа в HashMap. Аналогично, хотя 0 и -0 и считаются равными, но в случае представления их в качестве значений с плавающей точкой, сравнение 0 и -0 как объектов Float при помощи Float.compareTo() указывает на то, что -0 является меньшей величиной, чем 0.