Что мы увидим, когда нажмем кнопку? Разумеется, не 0.1, иначе не было бы смысла писать этот пример. Мы увидим 0.100000001490116, т. е. расхождение в девятой значащей цифре. Из справки по Delphi мы знаем, что точность типа Single — 7–8 десятичных разряда, так что нас, по крайнем мере, никто не обманывает. В чем же причина? Просто число 0,1 не представимо в виде конечной двоичной дроби, оно равно 0,0(0011). И эта бесконечная двоичная дробь обрубается на 24-х знаках; мы получаем не 0,1, а некоторое приближенное число (какое именно — см. выше). А если мы присвоим переменной R не 0.1, а 0.5? Тогда мы получим на экране 0.5, потому что 0.5 предоставляется в виде конечной двоичной дроби. Немного поэкспериментировав с различными числами, мы заметим, что точно представляются те числа, которые выражаются в виде Single
). В качестве упражнения предлагаем доказать, что любое целое число, для записи которого хватает 24-х двоичных разряда, может быть точно передано типом Single
.
Если в этом примере изменить тип переменной R
с Single
на Double
или на Extended
, на экран будет выведено 0.1. Но это не значит, что в переменную будет записано ровно 0.1 — это просто особенности работы функции FloatToStr
, которая не учитывает столь малую разницу между 0,1 и переданным ей числом.
3.2.7. Сравнение
Теперь попробуем сравнить значение переменной и константы, которую мы ей присвоили (листинг 3.10, пример Compare1 на компакт-диске).
procedure TForm1.Button1Click(Sender: TObject);
var
R: Single;
begin
R:= 0.1;
if R = 0.1 then Label1.Caption:= 'Равно'
else Label1.Caption:= 'He равно';
end;
При нажатии кнопки мы увидим надпись Не равно. На первый взгляд это кажется абсурдом. Действительно, мы уже знаем, что переменная R
получает значение 0.100000001490116 вместо 0.1. Но ведь "0.1" в правой части равенства тоже должно преобразоваться по тем же законам, т. к. работает аналогичный алгоритм. Тут самое время вспомнить, что FPU работает только с 10-байтным типом Extended
, поэтому и левая, и правая часть равенства сначала преобразуется в этот тип, и лишь потом производится сравнение. То число, которое оказалось в переменной R
вместо 0.1, хотя и выглядит страшно, но зато представляется в виде конечной двоичной дроби. Информация же о том, что это на самом деле должно означать "0.1", нигде не сохранилась. При преобразовании этого числа в Extended
младшие, избыточные по сравнению с типом Single
разряды мантиссы просто заполняются нулями, и мы снова получим то же самое число, только записанное в формате Extended
. А "0.1" из правой части равенства преобразуется в Extended
без промежуточного превращения в Single
. Поэтому некоторые из младших разрядов мантиссы будут содержать единицы. Другими словами, мы получим хоть и не точное представление числа 0.1, но все же более близкое к истине, чем 0.100000001490116.
Из-за таких хитрых преобразований оказывается, что мы сравниваем два близких, но все же не равных числа. Отсюда — закономерный результат в виде надписи Не равно.
Тут уместна аналогия с десятичными дробями. Допустим, в одном случае мы делим 1 на три с точностью до трех знаков и получаем 0,333. Потом мы делим 1 на три с точностью до четырех знаков и получаем 0,3333. Теперь мы хотим сравнить эти два числа. Для этого приводим их к точности в четыре разряда. Получается, что мы сравниваем 0,3330 и 0,3333. Очевидно, что это разные числа.
Если попробовать заменить число 0,1 на 0,5, то мы увидим надпись Равно. Полагаем, что читатели уже догадались, почему, но все же приведем объяснение. Число 0,5 — это конечная двоичная дробь. При прямом приведении ее к типу Extended
в младших разрядах оказываются нули. Точно такие же нули оказываются в этих разрядах при превращении числа 0,5 типа Single
в тип Extended
. Поэтому в результате мы сравниваем два равных числа. Это похоже на процесс деления 1 на 4 с точностью до трех и до четырех значащих цифр. В первом случае получили бы 0,250, во втором — 0,2500. Приведя оба значения к точности в четыре знака, получим сравнение 0,2500 и 0,2500. Очевидно, что эти числа равны.
3.2.8. Сравнение разных типов
Теперь попытаемся сравнить переменную не с константой, а с другой переменной (листинг 3.11, пример Compare2 на компакт-диске).
procedure TForm1.Button1Click(Sender: TObject);
var
R1: Single;
R2: Double;
begin
R1:= 0.1;
R2:= 0.1;