Pull to refresh

Фатальные ошибки двоичной арифметики при работе с числами с плавающей точкой

Reading time 6 min
Views 50K
Среди всего разнообразия форматов представления действительных чисел в компьютерной технике особое место отведено формату чисел с плавающей точкой (ЧПТ), который запротоколирован в стандарте IEEE754. Главные достоинства чисел с плавающей точкой, как известно, заключаются в том, что они позволяют производить вычисления в большом диапазоне значений и при этом вычисления организуются инструментарием двоичной арифметики, легко реализуемой на вычислительном устройстве. Однако последнее обстоятельство таит в себе подводные камни, которые являются причиной того, что расчеты, сделанные с использованием этого формата, могут приводить к совершенно непредвиденным результатам.

Ниже мы приводим примеры арифметических операций над некоторыми числами с плавающей точкой, которые приводят к неверным результатам. Эти результаты не зависят от платформы, на которой реализованы вычисления.

В настоящей статье мы не приводим теоретических выкладок, которые объясняют причину появления этих ошибок. Это тема следующего топика. Здесь мы только постараемся привлечь внимание специалистов к проблеме катастрофической неточности вычислений, возникающей при проведении арифметических операций над десятичными числами при использовании двоичной арифметики. Рассматриваемые здесь примеры неумолимо наталкивают на мысль о целесообразности использования формата с плавающей точкой в том виде, как его трактует стандарт 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, можно сделать вывод, что использование двоичной арифметики для десятичных чисел с плавающей точкой может приводить к совершенно непредсказуемым результатам.
Tags:
Hubs:
-7
Comments 229
Comments Comments 229

Articles