Среди всего разнообразия форматов представления действительных чисел в компьютерной технике особое место отведено формату чисел с плавающей точкой (ЧПТ), который запротоколирован в стандарте IEEE754. Главные достоинства чисел с плавающей точкой, как известно, заключаются в том, что они позволяют производить вычисления в большом диапазоне значений и при этом вычисления организуются инструментарием двоичной арифметики, легко реализуемой на вычислительном устройстве. Однако последнее обстоятельство таит в себе подводные камни, которые являются причиной того, что расчеты, сделанные с использованием этого формата, могут приводить к совершенно непредвиденным результатам.
Ниже мы приводим примеры арифметических операций над некоторыми числами с плавающей точкой, которые приводят к неверным результатам. Эти результаты не зависят от платформы, на которой реализованы вычисления.
В настоящей статье мы не приводим теоретических выкладок, которые объясняют причину появления этих ошибок. Это тема следующего топика. Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики. Рассматриваемые здесь примеры неумолимо наталкивают на мысль о целесообразности использования формата с плавающей точкой в том виде, как его трактует стандарт IEEE754.
Отметим, что причина ошибочных вычислений с ЧПТ, главным образом, обусловлена ни ошибками округления, устранению которых в стандарте уделяется большое внимание, а самой природой конвертации десятичных и двоичных чисел.
Будем рассматривать два основных формата для ЧПТ — float и double. Напомним, что формат float позволяет представлять до 7 верных десятичных цифр в двоичной мантиссе, содержащей 24 разряда. Формат double представляетдо 15 верных десятичных цифр в мантиссе, содержащей 53 двоичных разряда.
Непредставимые в двоичном машинном слове десятичные числа, после приведения их к десятичному виду, содержат как верные цифры так и «хвосты» из неверных цифр. Эти «хвосты» и являются источником ошибочных вычислений десятичных действительных ЧПТ с помощью двоичной арифметики. Покажем это на примерах.
Итак, сначала рассмотрим сумму двух следующих действительных чисел, представленных в формате float, каждое из которых имеет по 7 верных значащих цифр:
Все вычисления будем проводить в формате float. Для наглядности будем использовать числа в распакованном виде. Представим наши десятичные числа в нормализованном двоичном виде:
Если полученные двоичные коды наших чисел опять представить в десятичном виде, то получим следующие значения:
Здесь каждое число, округленное до 7 верных цифр мы отделили от «хвоста» пробелом. Эти «хвосты» получились в результате обратной конвертации чисел из двоичного кода, записанного в машинной мантиссе, в десятичный код.
Сумма наших чисел в двоичном 24-х разрядном виде даст следующий результат:
Тот же результат будет получен, если просуммировать десятичные числа с «хвостами»:
Как мы видим, после округления до 7 значащих цифр, здесь получен результат, отличный от того, который получился при суммировании десятичных чисел на калькуляторе. Правила округления двоичного представления чисел, заложенные в стандарте IEEE754, не решают проблему точного вычисления, рассмотренных здесь чисел. В нашем случае причина ошибки кроется в сочетании цифр, стоящих после последних верных цифр десятичных слагаемых, о которых, априори, ничего не известно.
Приведем еще один пример сложения. Просуммируем на калькуляторе два следующих действительных числа, каждый из которых содержит по 7 верных десятичных значащих цифр:
Приведем наши десятичные слагаемые к двоичному нормализованному виду:
Сумма этих слагаемых в двоичном виде даст следующее двоичное число:
Здесь мы снова получили результат, отличающийся от того, который вычислен на калькуляторе для неконвертированных десятичных чисел.
Такая же проблема неточных вычислений возникает при нахождении произведения некоторых ЧПТ, представленных в двоичном коде в формате float. Для примера рассмотрим произведение следующих действительных чисел:
Представим в этом выражении сомножители в нормализованном двоичном виде:
Результатом перемножения наших чисел в двоичном виде будет число:
Мы получили ошибку в младшем разряде произведения, которую невозможно исправить путем округления числа, представленного в двоичном коде. Такие ошибки принято называть фатальными.
Аналогично умножению, операция деления в формате float для некоторых ЧПТ также приводит к фатальным ошибкам. Рассмотрим следующий пример:
Представим делимое и делитель в двоичном формате, в нормализованном виде:
Частное от деления наших чисел будет следующим:
Мы видим, что полученный здесь результат не соответствует правильному значению, вычисленному на калькуляторе или вручную.
Вычитание — это та операция, которая полностью дискредитирует идею использования двоичной арифметики для вычисления десятичных значений ЧПТ. В результате этой операции, в некоторых случаях, можно получить результат даже близко не соответствующий действительности. Продемонстрируем это на примере.
Пусть уменьшаемое у нас будет равно 105.3256. Вычтем из него число 105.32. Разность этих чисел, вычисленная вручную, будет равна:
Представим десятичное уменьшаемое и десятичное вычитаемое в нормализованном двоичном виде:
Найдем разность этих чисел в двоичном виде:
После преобразования этого числа в десятичный вид получим:
Мы получили результат, существенно отличающийся от того, который ожидали.
ОШИБКИ В ФОРМАТЕ ДАБЛ
Ситуацию с фатальными ошибками не спасает и формат более высокой точности, например double. Как мы выше уже отмечали, это объясняется самой природой конвертации чисел из одной системы счисления в другую. Покажем это на примерах.
В качестве инструмента для проверки правильности наших рассуждений будем использовать Excel 2009, в котором реализованы вычисления в строгом соответствии со спецификацией стандарта IEEE754.
Проведем следующие вычисления, используя средства Excel 2009. Формат ячеек выберем числовой, с 18 знаками после запятой. Для нахождения суммы запишем в ячейки таблицы Excel следующие числа:
В ячейке А3Excel получим сумму этих чисел:
Если посчитать эту сумму вручную или на калькуляторе, то получится число:
Которое в младшем разряде не совпадает с тем, что получено в Excel.
Посмотрим, к чему приводит операция вычитания в Excel. Запишем в ячейки следующие числа:
В ячейке А3 найдем разность этих чисел. Она будет равна:
А мы ожидали получить число:
В заключении приведем пример того, как быстро может расти ошибка, если использовать двоичную арифметику для вычисления десятичных действительных чисел даже без операции вычитания.
Запишем в ячейки таблицы Excel следующие числа:
В ячейке А6 запишем формулу =A1/5+A2. После чего в ней будет получен результат.
В ячейке A7 произведем следующие вычисления:
А теперь вычислим
Проведем те же вычисления на калькуляторе. Вычисления будем производить с точностью до 15 десятичных значащих цифр. Цифры, которые стоят правее младшего значащего разряда, согласно правилам арифметики, будем округлять до ближайшего целого. В результате будем иметь:
Сравнив результат, полученный при вычислениях с соблюдением правил арифметических операций с тем, что получился в Excel, можно сделать вывод, что использование двоичной арифметики для десятичных чисел с плавающей точкой может приводить к совершенно непредсказуемым результатам.
Ниже мы приводим примеры арифметических операций над некоторыми числами с плавающей точкой, которые приводят к неверным результатам. Эти результаты не зависят от платформы, на которой реализованы вычисления.
В настоящей статье мы не приводим теоретических выкладок, которые объясняют причину появления этих ошибок. Это тема следующего топика. Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики. Рассматриваемые здесь примеры неумолимо наталкивают на мысль о целесообразности использования формата с плавающей точкой в том виде, как его трактует стандарт IEEE754.
Отметим, что причина ошибочных вычислений с ЧПТ, главным образом, обусловлена ни ошибками округления, устранению которых в стандарте уделяется большое внимание, а самой природой конвертации десятичных и двоичных чисел.
Будем рассматривать два основных формата для ЧПТ — float и double. Напомним, что формат float позволяет представлять до 7 верных десятичных цифр в двоичной мантиссе, содержащей 24 разряда. Формат double представляетдо 15 верных десятичных цифр в мантиссе, содержащей 53 двоичных разряда.
Непредставимые в двоичном машинном слове десятичные числа, после приведения их к десятичному виду, содержат как верные цифры так и «хвосты» из неверных цифр. Эти «хвосты» и являются источником ошибочных вычислений десятичных действительных ЧПТ с помощью двоичной арифметики. Покажем это на примерах.
СУММА
Итак, сначала рассмотрим сумму двух следующих действительных чисел, представленных в формате float, каждое из которых имеет по 7 верных значащих цифр:
0,6000006 + 0,03339874=0,6333993 4≈0,6333993
Все вычисления будем проводить в формате float. Для наглядности будем использовать числа в распакованном виде. Представим наши десятичные числа в нормализованном двоичном виде:
0.6000006 ≈ 1.001100110011001101001*2^(-1)
0.03339874≈1.00010001100110100011110*2^(-5)
Если полученные двоичные коды наших чисел опять представить в десятичном виде, то получим следующие значения:
0.6000006 ≈ 0.6000006 198883056640625≈0.6000006 2
0.03339874≈0.03339873 9993572235107421875≈0.0333987 4
Здесь каждое число, округленное до 7 верных цифр мы отделили от «хвоста» пробелом. Эти «хвосты» получились в результате обратной конвертации чисел из двоичного кода, записанного в машинной мантиссе, в десятичный код.
Сумма наших чисел в двоичном 24-х разрядном виде даст следующий результат:
1.001100110011001101001*2^(-1) + 1.00010001100110100011110*2^(-5)≈ 1.0100010001001100111011*2^(-1) ≈0,6333994
Тот же результат будет получен, если просуммировать десятичные числа с «хвостами»:
0,6000006 2+ 0,0333987 4= 0,6333993 6≈ 0,6333994
Как мы видим, после округления до 7 значащих цифр, здесь получен результат, отличный от того, который получился при суммировании десятичных чисел на калькуляторе. Правила округления двоичного представления чисел, заложенные в стандарте IEEE754, не решают проблему точного вычисления, рассмотренных здесь чисел. В нашем случае причина ошибки кроется в сочетании цифр, стоящих после последних верных цифр десятичных слагаемых, о которых, априори, ничего не известно.
Приведем еще один пример сложения. Просуммируем на калькуляторе два следующих действительных числа, каждый из которых содержит по 7 верных десятичных значащих цифр:
6543.455+12.34548=6555.80048≈6555.800
Приведем наши десятичные слагаемые к двоичному нормализованному виду:
6543.455=1.10011000111101110100100*2^12=6543.455 078
12.3454810 = 1.10001011000011100010110*2^3 ≈ 12.345 48
Сумма этих слагаемых в двоичном виде даст следующее двоичное число:
1.10011001101111001101*2^12≈6555.80078125≈6555.801
Здесь мы снова получили результат, отличающийся от того, который вычислен на калькуляторе для неконвертированных десятичных чисел.
УМНОЖЕНИЕ
Такая же проблема неточных вычислений возникает при нахождении произведения некоторых ЧПТ, представленных в двоичном коде в формате float. Для примера рассмотрим произведение следующих действительных чисел:
0.06543455*139=9.095402 45≈9.095402
Представим в этом выражении сомножители в нормализованном двоичном виде:
0.06543455=1.00001100000001010001101*2^(-4)
139=1.0001011*2^10
Результатом перемножения наших чисел в двоичном виде будет число:
1.00001100000001010001101*2^(-4) × 1.0001011*2^10 = 1001.00011000011011000101 ≈9.095403
Мы получили ошибку в младшем разряде произведения, которую невозможно исправить путем округления числа, представленного в двоичном коде. Такие ошибки принято называть фатальными.
ДЕЛЕНИЕ
Аналогично умножению, операция деления в формате float для некоторых ЧПТ также приводит к фатальным ошибкам. Рассмотрим следующий пример:
131/0.066≈1984.848
Представим делимое и делитель в двоичном формате, в нормализованном виде:
13110 = 1.0000011*2^7
0.066= 1.00001110010101100000010*2^(-4)
Частное от деления наших чисел будет следующим:
1.0000011*2^7/1.00001110010101100000010*2^(-4)=
= 1.11110000001101100100111*2^10 = 1984.848 5107421875≈1984.849
Мы видим, что полученный здесь результат не соответствует правильному значению, вычисленному на калькуляторе или вручную.
ВЫЧИТАНИЕ
Вычитание — это та операция, которая полностью дискредитирует идею использования двоичной арифметики для вычисления десятичных значений ЧПТ. В результате этой операции, в некоторых случаях, можно получить результат даже близко не соответствующий действительности. Продемонстрируем это на примере.
Пусть уменьшаемое у нас будет равно 105.3256. Вычтем из него число 105.32. Разность этих чисел, вычисленная вручную, будет равна:
105.3256-105.32=0.0056
Представим десятичное уменьшаемое и десятичное вычитаемое в нормализованном двоичном виде:
105.3256 = 1.10100101010011010110101*2^6≈105.3255 997 041015625
105.32= 1.10100101010001111010111*2^6≈105.32
Найдем разность этих чисел в двоичном виде:
1.10100101010011010110101*2^6-1.10100101010001111010111*2^6= 1.01101111*2^(-8)
После преобразования этого числа в десятичный вид получим:
1.01101111*2^(-8)= 0.005599976
Мы получили результат, существенно отличающийся от того, который ожидали.
ОШИБКИ В ФОРМАТЕ ДАБЛ
Ситуацию с фатальными ошибками не спасает и формат более высокой точности, например double. Как мы выше уже отмечали, это объясняется самой природой конвертации чисел из одной системы счисления в другую. Покажем это на примерах.
В качестве инструмента для проверки правильности наших рассуждений будем использовать Excel 2009, в котором реализованы вычисления в строгом соответствии со спецификацией стандарта IEEE754.
Проведем следующие вычисления, используя средства Excel 2009. Формат ячеек выберем числовой, с 18 знаками после запятой. Для нахождения суммы запишем в ячейки таблицы Excel следующие числа:
A1= 0,6236
A2= 0,00661666666070646
В ячейке А3Excel получим сумму этих чисел:
А3=А1+А2=0,6236+0,00661666666070646≈0,630216666660707
Если посчитать эту сумму вручную или на калькуляторе, то получится число:
0,6236+0,00661666666070646≈0,630216666660706
Которое в младшем разряде не совпадает с тем, что получено в Excel.
Посмотрим, к чему приводит операция вычитания в Excel. Запишем в ячейки следующие числа:
А1= 123456,789012345
А2= 123456
В ячейке А3 найдем разность этих чисел. Она будет равна:
А3=А1-А2=0,789012345005176
А мы ожидали получить число:
123456,789012345-123456=0, 789012345
В заключении приведем пример того, как быстро может расти ошибка, если использовать двоичную арифметику для вычисления десятичных действительных чисел даже без операции вычитания.
Запишем в ячейки таблицы Excel следующие числа:
А1= 0,500000000660006
А2 = 0,0000213456548763
А3 = 0,00002334565487363
А4 = 0,000013345654873263
В ячейке А6 запишем формулу =A1/5+A2. После чего в ней будет получен результат.
A6= A1/5+A2= 0,100021345786878
В ячейке A7 произведем следующие вычисления:
A7=A6/3+A3=0,0333637942504995
А теперь вычислим
A8=A7/4+A4=0,00835429421749813
Проведем те же вычисления на калькуляторе. Вычисления будем производить с точностью до 15 десятичных значащих цифр. Цифры, которые стоят правее младшего значащего разряда, согласно правилам арифметики, будем округлять до ближайшего целого. В результате будем иметь:
A1/5=0,500000000660006/5=0,100000000132001 2≈0,100000000132001
A1/5+A2=0,100000000132001+0,0000213456548763≈ 0,100021345786877
( A1/5+A2)/3=0,100021345786877/3≈0,0333404485956257
( A1/5+A2)/3+A3=0,0333404485956257+0,00002334565487363≈ 0,0333637942504993
[( A1/5+A2)/3+A3]/4=0,0333637942504993/4≈0,00834094856262483
[( A1/5+A2)/3+A3]/4+A4=0,00834094856262483+0,000013345654873263=0,00835429421749809
Сравнив результат, полученный при вычислениях с соблюдением правил арифметических операций с тем, что получился в Excel, можно сделать вывод, что использование двоичной арифметики для десятичных чисел с плавающей точкой может приводить к совершенно непредсказуемым результатам.