allasm.ru

    Меню

 

Тебя Мы помним, как спасенье.
Ты появился, что Абак.
На то был чисел свыше знак -
Дать запятой освобожденье.

1. От автора

Это руководство навеяно вопросом форума WASM.RU. Сейчас вы можете обнаружить множество статей связанных с FPU, все они наперебой долго рассказывают о его архитектуре, командах - обо всем, что можно найти в руководстве Интел. Однако программист чаще сталкивается с другой проблемой. Её сложно назвать. Но вопрос поднимается обычно не об особенностях формата и не о выполнении команд, а "как писать". Собственно, этому и посвящена эта подборка статей.

Начнём с теории: немного почитав, отложим её в сторону, приступив к....

2. Введение

В то время (уже далёкое) числа типа REAL, как это было заведено математиками на Фортране, отображались при помощи "целых". И сколько тут было хитроумных алгоритмов, и сколько было придумано ухищрений. Для простых задач, требующих невысокой точности вполне хватало 32-bits-16-bits представления числа, где одна половина отображала целую часть, а другая - дробную. Например:

0000000 01110001B = 113D

Если представить, что двоичная точка находится после третьего разряда справа, то:

0000000 01110.001B = 14.125D

Правило перевода дробной части просто и подобно переводу целой части:

S10 = 2-n + 2-n + 2-n + 2-n + 2-n + .. .. ..

Где n - положение ненулевого бита от точки, считая слева направо.

При этом арифметические операции над такой переменной как над целым числом будут давать правильные результаты, если считать, что двоичная точка фиксирована.

Такое соглашение представления вещественного числа называется "представлением с фиксированной точкой". Его основной недостаток - малый диапазон чисел.

Конечно, можно придумать некоторые усложнения, что позволят улучшить ситуацию. Например, можно хранить коэффициент масштаба, на который следует умножить число, чтобы получить его истинное значение. Именно эта идея и положена в основу представления вещественных чисел как чисел "с плавающей точкой".

Пусть у нас есть число: 12345. Это число в соответствии с правилами научной нотации можно записать: 1.2345*104. При этом выделим несколько характерных частей:

  1. "1.2345" - мантисса
  2. "4" - порядок

Можно придумать несколько вариантов представления числа в данном виде xx*10xx . Например:

 
 1.2345*104
 12.345*103
 0.12345*105
 0.012345*106

Однако два последних варианта явно не имеют смысла. В их случае число порядка увеличивается, когда мантисса остаётся неизменной. То есть: в первом и втором случае мы наблюдаем баланс в хранении информации, а в двух последних явно выраженный излишек. Таким образом, можно утверждать, что первые два варианта выражают минимальный объём для хранения, отображения чисел. Поскольку вариантов всё равно несколько, мы договоримся только об одном, а именно: "выражать число таким образом, чтобы мантисса не содержала "левых" или старших нулей". Такое представление числа с плавающей точкой назовём нормализованным, а, соответственно, все другие варианты - ненормализованными.

Интересно будет узнать, что данный способ представления как раз был выбран для FPU, с одной единственной оговоркой. Поскольку числа хранятся в двоичной системе, то, какое ни было бы двоичное число, его можно записать в виде:

1.xxxxx*2n

При этом ноль, вообще говоря, можно числом не считать, но об этом пусть спорят математики ..

Что мы видим? Старший бит мантиссы всегда будет 1. Да, это так, поскольку двоичное число состоит только из 0 и 1, а когда мы отбросим все "левые" нули, то обязательно получим старший бит равным 1. Недолго думая, инженеры Интел решили оставить этот бит "в уме", повысив тем самым точность представления числа. Но теперь мы вплотную подошли к формату хранения чисел с плавающей точкой.

3. Формат хранения чисел с "плавающей точкой"

x87 (FPU) поддерживает три формата:

  • Одинарной точности
  • Двойной точности
  • Расширенной точности

Или, вы можете услышать следующее определение (любимое Фортраном)

  • Короткие вещественные
  • Длинные вещественные
  • Временные вещественные

Вот их формат:

<- В сторону возрастания адресов

Короткое (одинарной точности)

S Порядок (8bit) Мантисса (23bits)

Длинное (двойной точности)

S Порядок (11bit) Мантисса (52bits)

Временное вещественное (расширенной точности)

S Порядок (15bit) Мантисса (64bits)

Где S - знаковый бит

Как и было сказано, форматы одинарной и двойной точности хранят мантиссу без старшего бита. Однако в последнем случае - формат расширенной точности - мантисса хранится полностью. В действительности формат расширенной точности не используется для хранения переменных, и предназначен для внутренней реализации (в Фортране REAL(4), REAL(8)). Можно только догадываться, почему фирма Интел выбрала именно такой формат (80 бит). Рациональней было бы создать 128- битовое число. Однако именно этот формат был выбран для представления промежуточных результатов. Дело в том, что Инженеры Интел попросту пожадничали, в некотором смысле этого слова.

Они рассчитывали так, чтобы при операциях (подобных z = yx) обеспечить достаточную точность или абсолютную точность формату длинного вещественного. Здесь следовало определить число бит мантиссы, которое необходимо для точного представления результата в формате двойной точности. Взяв, например, выражение z = 2x) можно доказать, что для представления результата операции как числа в формате длинного вещественного (обеспечивая заданную точность) необходимо иметь 64 бита в мантиссе (то есть 11bit + 52bit - сумма бит порядка и мантиссы длинного вещественного), что мы и видим в формате расширенной точности.

В случае 128 bits фирме Intel пришлось изобретать ещё один формат "расширенной точности", а по сему выбор 10 байтового формата оказался оптимальным (хотя и недальновидным).

Порядок. Порядок определяет степень двойки, на которую следует умножить мантиссу для получения значения числа с плавающей точкой. Для представления положительного и отрицательного значения порядков фирма Интел использовала метод, отличный от метода дополнения. Так, например, если мантисса имеет длину 8 бит - 256 значений, то считается, что значение порядка равно сумме порядка и константы - смещения, делящего весь диапазон пополам. Для нашего случая смещение будет 127. То есть, если порядок равен 3, то в поле порядка будет записано число 127+3. А если порядок равен -3, то в поле порядка будет записано 127-3. При этом заметьте: диапазон порядка равен от -127 (0) и +128 (255), однако, поскольку эти значения зарезервированы для описания особых ситуаций, диапазон порядка составляет: от -126, до +127. Аналогично для форматов двойной и расширенной точности (-1022/+1023, -16382/+16383).

Другой характерной особенностью является знаковый бит S. Как и ожидалось, его единичное значение соответствует знаку "-", а нулевое "+".

Теперь, когда формат определён, читателю предлагается потренироваться в переводе чисел. Предлагаю два простеньких примера: 0.125 и 0.625. Вы можете усложнить их: 23.125 и 456.625. Свои результаты вы сможете проверить, например, записав результат в переменную dword, и посмотрев под отладчиком число в стеке FPU. Автор настаивает на такой практике, даже если вы не новичок.

4. Особые числа

4.1. Ноль

Не все числа можно представить в нормализованном виде. Это достаточно очевидно, хотя бы потому, что есть такое число как 0. Его представление в расширенной точности: нулевая мантисса и порядок, в то время как мы сказали, что старший бит всегда должен быть равен 1. Интересно отметить, что в FPU существует два нуля, и это является математически корректным. Есть +0 и -0. Далее мы узнаем, что FPU не только способно содержать +/-0, но и образовывать +/-0 при арифметических ситуациях.

4.2. Бесконечность

Говоря о мире математики, вспоминая 0, нельзя не вспомнить его противоположность - бесконечность. Как и ноль, бесконечность - ненормализованное число. У этого числа все биты мантиссы нули, а порядка единицы. Как и нулей, бесконечностей - две: положительная и отрицательная.

5. Особые ситуации

Продолжая повествование об особых ненормализованных числах, автор подошёл к описанию особых ситуаций. Пока скажем только то, что FPU обладает возможностью отвечать на особенные экстремальные операции особенными "ненормализованными" результатами. "Экстремальной" операцией можно назвать деление на ноль, но мир FPU полон многими другими, имеющими свой математический смысл.

  • Деление на ноль - ненулевое число делится на ноль.
  • Неточный результат - результат операции не может быть представлен в данном формате.

Как ни странно, но данная ситуация является наиболее частой. Например, дробь 1/3 представляется периодической десятичной вида 0.3333333.... То же самое наблюдается и в двоичном формате. Следует отметить, что количество "неточно" представляемых дробей для двоичной системы больше, нежели для десятичной.

Более явно эта ситуация проявляется в преобразовании форматов. Например, числа двойной точности в число с одинарной. В большинстве случаев программист может считать, что ситуация "неточного результата" не есть ошибка, и приводит к округлению или усечению результата.

  • Антипереполнение - ненулевой результат слишком мал для его представления, близок к нулю. Далее этот случай будет рассмотрен более подробно.
  • Численное переполнение - результат слишком велик, чтобы быть представленным в приёмнике.
  • Недействительная операция - возникает во всех остальных случаях.

6. Специальные значения

FPU позволяет программисту отреагировать на исключительную ситуацию при помощи двух методов: либо при помощи ловушек (прерываний или исключений*), либо при помощи контроля на образование специальных значений. Здесь мы остановимся на разборе этих специальных значений.

Уже должно быть понятно, что специальное значение - есть ненормализованное число. Итак. Мы рассмотрим особые ситуации снова, но теперь с подробным описанием.

6.1. Случай неточного результата

Случай неточного результата, как правило, попросту приводит к округлению. При этом, по сути, специальное значение не возвращается.

FPU содержит четыре режима округления, управляемого при помощи поля RC регистра управления CR:

   Значение RC	       Способ округления
	0		К ближайшему числу
	1		К отрицательной бесконечности
	2		К положительной бесконечности
	3		К нулю

На рисунках показаны схематически эти режимы.

* - В данном контексте и то и другое приблизительно равноправно.

Здесь кружочками обозначены идеально точные числа, а квадратиками - реальные результаты. Наиболее часто употребляемым является режим округления к ближайшему. При этом действует правило: "если истинный результат находится точно посередине между парой чисел с плавающей точкой, выбирается чётное число, то есть число, содержащее ноль в младшем бите мантиссы. Режимы 1 и 2 могут быть использованы для того, чтобы получить чёткие границы существования истинного результата. В этом случае расчёт выполняется дважды: первый раз в одном режиме и второй раз - в другом. В результате мы можем быть уверены, что истинный результат находится где-то в диапазоне между этими значениями. К сожалению, такой способ содержит много подводных камней, так что требует отдельного подробного описания.

6.2. Численное антипереполнение

На этот раз результатом данной операции есть специальное значение. Автор напомнит, что, в начале статьи рассказывая о формате чисел, мы упомянули, что значения поля порядка 0 и максимальное зарезервированы для особых ситуаций. Ну вот!!! Вот она - особая ситуация. Пусть будет считаться, что:

1. Если все биты поля порядка равны 0, то
2. мы будем считать, что порядок равен 1
3. а старший бит мантиссы равен не 1, а 0.

Таким образом, мы получаем ненормализованное число, которое расширяет диапазон представления очень малых чисел от -126 до -149 (для Real4), но при этом точность естественно уменьшается до одного бита. Такое представление чисел называется "денормализованным".

* Нельзя забывать, что в формате расширенной точности старший бит мантиссы всё равно равен 1.

Итак, если случай численного антипереполнения замаскирован, и результат слишком мал, чтобы его представить в виде нормализованного значения, FPU формирует денормализованное число. Если же это невозможно, образуется ноль. Такая методика называется плавным переполнением. Она реализована на большинстве современных калькуляторах инженерного типа, и приводит к верному результату. Например:

(a - b) + b

по правилам должно дать ноль. Но это не обязательно, если бы было реализовано резкое переполнение. Так, например, если a-b вызывает антипереполнение, то в случае резкого переполнения образовался бы ноль, и общий результат получился бы как b. Метод плавного переполнения обеспечивает нужный эффект. Но здесь мы незримо переходим к рассмотрению другого особого случая.

6.3. Особый случай денормализованного операнда

Итак, возвращаясь к предыдущему примеру. В данном случае после вычитания мы имеем денормализованный операнд. Это значит, что операндом следующей операции будет денормализованное число. Именно этот случай и называется случаем денормализованного операнда. Обычно он маскируется и таким образом разрешается использовать в операциях денормализованные числа.

Поскольку при проектировании FPU разработчики стремились обеспечить заданную точность, операции с денормализованным операндом (ами) дают следующие результаты:

1. ноль, если результат слишком мал, чтобы представить его в виде денормализованного операнда.

2. Денормализованный результат, если он достаточно мал, чтобы быть представленным денормализованным числом.

3. Нормализованный результат, если потеря точности при использовании денормализованного операнда меньше, чем потеря точности при ошибке округления (например, произведение большого нормализованного числа и денормализованного).

4. Ненормализованный результат, если результат неточен, но велик для денормализованного числа - при этом приёмник результата имеет расширенную точность.

5. Особый случай недействительной операции, если результат подобен условию 4, но приёмник не имеет расширенной точности.

Здесь ненормализованное число - это любое число, имеющее обычное поле порядка, но старший нулевой бит мантиссы. Поскольку такая ситуация возможна только в формате расширенной точности, поэтому и существование таких чисел возможно только там. Ненормализованные операнды ведут себя подобно денормализованным по правилам 1-5.

Правила 1-5 позволяют гарантировать, что все операции с ненормализованными/денормализрованными числами, дают либо ненормализованный результат, либо результат имеет малую потерю точности.

Особый вид ненормализованного операнда - псевдоноль, появляющийся при умножении двух ненормализованных чисел, у которых суммарное количество нулей в мантиссе более 64. Псевдоноль обладает ненулевым порядком, но нулевой мантиссой. Единственная особенность псевдонулей - то, что в сравнениях они ведут себя как обычные нули.

6.4. Деление на ноль (или "на ноль делить возможно")

Вспомним сразу, что деление на ноль порождает антизначение - бесконечность. Будет интересно узнать, что FPU действует верно при делении числа на ноль, если только этот случай замаскирован и замаскирован случай численного переполнения. То есть:

x/-0 = -[8], x/+0 = +[8];

*Здесь символом [8] обозначена бесконечность.

Интересней то, что при операциях с бесконечностью FPU даёт математически корректные результаты:

x/+[8] = +0; -x/+[8] = -0;
x *( +[8] ) = +[8]; -x *( +[8] ) = -[8];
(+[8])*(+[8]) = +[8];

где x - любое число, кроме нуля и бесконечности.

Однако операции с операндами "бесконечность" или "ноль" вызывают особый случай недействительной операции, впрочем, как и попытка деления бесконечности на ноль, либо подобное.

6.5. Режимы сравнения

FPU поддерживал два режима сравнения: первый - проективный, второй - аффинный. Речь идёт о бите IC регистра CR управления бесконечностью. Но в любом случае +[8] > -[8]. В 80286 действовала схема деления на режимы. То есть разработчики фирмы Интел несколько скрыли эффект присутствия знаковых 0 и [8].

Проективный режим "скрывает" факт наличия двух бесконечностей и нулей. То есть сравнение бесконечности с числом вызывает особый случай недействительной операции, а сравнение бесконечностей всегда даёт результат: равны. Или: сложение / вычитание бесконечности и числа даёт бесконечность, а вычитание/сложение бесконечностей вызывает снова особый случай недействительной операции.

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

6.6. Численное переполнение

Как и следует ожидать, результатом переполнения есть бесконечность. Однако этого следует ожидать программисту, но не математику. С точки зрения математики такой результат не есть правильный. Но если можно как-то смириться с данным фактом, сложно смириться с "ошибкой знака", которая бывает при переполнении, и зависит от корректности округления. Случай переполнения желательно не маскировать.

6.7. Особый случай недействительной операции

Это самый серьёзный случай из всех, здесь рассмотренных, так как после данной ситуации не может быть вычислений. Из всех рассмотренных ситуаций результатом в таком случае может стать NAN (не число). NAN имеет: поле порядка все биты 1, а поле мантиссы что угодно кроме нуля. Особым NAN числом является неопределенность, которая имеет мантиссу 11 в формате расширенной точности, и 1 в остальных случаях. FPU не даёт нечислу превратиться в число. А поэтому в случае бесконтрольного появления нечисла следует их размножение, что может привести к трудно обнаружимым ошибкам.

7. Далее...

Начнём с теории; немного почитав, отложим её в сторону, приступив к практике. А как лучше попрактиковаться? Нет ничего лучше, чем практиковаться в окне отладчика. Автор надеется, что перед тем как читатель раскроет следующую часть, он ещё почитает об архитектуре FPU. И разберётся в некоторых азах, если этого ещё не сделал.

  [C] Edmond / HI-TECH