Особенности представления чисел в ЭВМ
Вещественные числа
Обсуждаемые в данной секции вопросы значительно более полно рассмотрены в четвертой главе классической книги Кнута [7].
Для представления вещественных чисел в языке Java используют переменные и константы типов float и double. Величины первого из них занимают четыре байта, а второго — восемь.
В отличие от множества целых чисел
вещественные
числа
обладают свойством полноты: между любыми двумя
различными
числами всегда найдется отличное от них третье. Понятно, что любой из
способов представления бесконечного множества вещественных чисел с помощью
некоторого конечного множества
не даст возможности
сохранить
свойство полноты. Наиболее распространенным способом реализации вещественных
чисел на ЭВМ является использование чисел с плавающей точкой.
При этом множество
оказывается состоящим из
элементов вида

где целые числа
для всех
из диапазона от
1 до
удовлетворяют
соотношению
, а величина
лежит
в диапазоне от
до
(
). Число
является при этом основанием системы счисления (чаще всего это 2), константа
задает точность представления чисел (для типа double она
больше,
чем для float ), а диапазон
определяет область
значений
экспоненты (для типа double он также больше, чем для float ).
Число
принято называть мантиссой числа
с
плавающей
точкой. Часто требуют, чтобы для всех чисел
величина
была ненулевой. Такие числа с плавающей точкой называют нормализованными.
В языке Java для кодирования величин типов float и double
также используют числа с плавающей точкой. При этом часть из имеющегося
множества бит используют для размещения экспоненты
, а
остальные
биты — для размещения мантиссы.
Для того чтобы хорошо понять, что же представляет из себя множество
нормализованных чисел с плавающей точкой, полезно
изобразить
его на числовой прямой для случая небольших
,
и
. Подобная задача
приведена в конце параграфа. Сейчас же нам будет достаточно весьма
качественного описания этого множества.
Так как
симметрично относительно начала
координат, то можно
разобраться только с неотрицательными числами. Нуль, конечно же, принадлежит
искомому множеству. Ближайшая к нулю следующая точка получается при
,
и
.
Это число для чисел
типов float
и double определено, как MIN_VALUE в классах java.lang.Float и java.lang.Double соответственно.
Правее располагается множество точек, следующих друг за другом с шагом
. Затем шаг
увеличивается. Потом еще. И еще. При
расстояние между двумя
соседними
точками множества
достигает
.
Самая правая точка
множества получается при
и
. Для типов float и double это число определено, как MAX_VALUE
в классах java.lang.Float и java.lang.Double.
Кроме перечисленных (и симметричных им отрицательных) значений в результате выполнения некоторых операций могут получиться также следующие особые значения: плюс бесконечность ( POSITIVE\_INFINITY ), минус бесконечность ( NEGATIVE\_INFINITY ), минус ноль и не число ( NaN ). Например, при делении единицы на минус ноль получается минус бесконечность.
Описанные особенности множества машинных вещественных чисел
приводят к тому, что не для всех его элементов верны следующие соотношения,
всегда справедливые для множества настоящих вещественных чисел
:
-
; -
существует; -
; -
; -
; -
.
Приведем несколько примеров, иллюстрирующих эти особенности множества
.
Задача 4.1. Предъявите действительное (типа double ) число
такое, что
, а
. Воспользуйтесь тем, что класс java.lang.Double определяет константу MAX_VALUE.
Текст программы
public class DblMaxVal {
public static void main(String[] args) {
double x = Double.MAX_VALUE;
double y = x + 1.0;
if (x == y) {
Xterm.print("Для числа ");
Xterm.print("x = " + x, Xterm.Blue);
Xterm.print(" величины x и (x+1) ");
Xterm.print("равны!\n", Xterm.Red);
}
y = 2.0 * x;
double z = y / 2.0;
if (x != z) {
Xterm.print("Для числа ");
Xterm.print("x = " + x, Xterm.Blue);
Xterm.print(" величины x и (2.0*x)/2.0 ");
Xterm.print("различны\n", Xterm.Red);
Xterm.print("и равны соответственно");
Xterm.print(" " + x, Xterm.Red);
Xterm.print(" и");
Xterm.print(" " + z + "\n", Xterm.Red);
}
}
}Задача 4.2.Предъявите такие действительные (типа double ) числа
,
и
такие,
что
.
Достаточно вспомнить, что точки множества
расположены
на числовой прямой неравномерно.
Текст программы
public class DblNoAssociative {
public static void main(String[] args) {
double x = 1.0e-16;
double y = 1. + (x + x);
double z = (1. + x) + x ;
if (y != z) {
Xterm.print("Для числа ");
Xterm.print("x = " + x, Xterm.Blue);
Xterm.print(" величины 1.+(x+x) и (1.+x)+x ");
Xterm.print("различны\n", Xterm.Red);
Xterm.print("и равны соответственно");
Xterm.print(" " + y, Xterm.Red);
Xterm.print(" и");
Xterm.print(" " + z + "\n", Xterm.Red);
}
}
}Иногда даже работа с казалось бы не слишком маленькими или огромными по абсолютной величине числами может привести к удивительным результатам. Рассмотрим задачу о решении квадратного уравнения.
Задача Напишите программу, вводящую действительные коэффициенты
,
и
квадратного уравнения
с положительным дискриминантом,
находящую оба корня этого уравнения.
Вот вполне естественное решение этой задачи, которое может быть написано любым человеком, обладающим минимальными знаниями языка Java.
Текст программы
public class SquareEquation {
public static void main(String[] args) throws Exception {
double a = Xterm.inputDouble("Введите число a -> ");
double b = Xterm.inputDouble("Введите число b -> ");
double c = Xterm.inputDouble("Введите число c -> ");
if (a == 0.) {
Xterm.println("Уравнение не квадратное", Xterm.Red);
System.exit(0);
}
if (b*b - 4.*a*c <= 0.) {
Xterm.println("Дискриминант неположителен", Xterm.Red);
return;
}
double x1 = ( -b - Math.sqrt(b*b - 4.*a*c) ) / (2.*a);
double x2 = ( -b + Math.sqrt(b*b - 4.*a*c) ) / (2.*a);
Xterm.println("Корни уравнения:");
Xterm.println("x1 = " + x1, Xterm.Blue);
Xterm.println("x2 = " + x2, Xterm.Blue);
}
}Для извлечения квадратного корня здесь используется метод sqrt класса Math. Вызов метода System.exit и применение оператора return, которые в теле метода main почти эквивалентны и приводят к немедленному завершению выполнения программы, позволяет обойтись без ветви else оператора if-else. Остальная часть программы комментариев не требует.
Попробуйте, однако, выполнить эту программу при a=0.2E-16 и b=c=1.0 — первый корень уравнения x1 окажется равным -5.0E16, а второй x2 — нулю.
Если подставить эти значения в выражение
, то
и для x1 и для x2 его значение окажется равным единице, а не нулю.
Подобная ошибка для первого, весьма большого по величине корня,
представляется вполне естественной, но вот соглашаться с тем, что нуль
является корнем данного уравнения, совсем не хочется.
Этой проблеме посвящена одна из приведенных ниже задач.