32bit_me (32bit_me) wrote,
32bit_me
32bit_me

Categories:

FPU. Часть 1

Originally published at 32 bit.me. You can comment here or there.

Не задумывались ли вы о том, как работает FPU процессора? Казалось бы, там всё довольно просто. Есть стандарт представления чисел с плавающей точкой, IEEE754, а сама реализация арифметических операций очевидна.
Но на самом деле, за кажущейся простотой скрыто множество нюансов.

Во-первых, как это ни странно звучит, FPU оперирует не только с числами. Среди всех возможных значений кодов стандартом предусматриваются нечисла, выделенные значения, образующиеся в результате некоторых операций. Нечисла обозначаются как NaN (not a number). Например, нечисло образуется в результате деления ноля на ноль. Нечисло может иметь в знаковом разряде 0 или 1, однако особого смысла он не несёт, нечисла рассматриваются как беззнаковые. Еще одним выделенным значением является бесконечность. Бесконечность имеет знак, и может получаться как результат переполнения показателя степени числа, или, например, как результат деления ненулевого числа на ноль. Также числа могут быть нормализованными (и обычно ими и являются), и денормализованными. У денормализованного (subnormal, denormal) числа в разрядах экспоненты содержатся только нули, что соответствует минимально возможному показателю степени (2^-1022 для 64-битных чисел). Это крайне маленькие значения, порядка 10^-308, в реальном мире такие величины практически не применяются, поэтому в упрощенных реализациях FPU можно позволить себе некоторое отступление от стандарта и заменять их нулями (ноль, формально, тоже денормализованное число). И ещё, в стандарте IEEE754 нули имеют знак.

Вот и всё, что нам нужно знать для начала. За описанием стандарта добро пожаловать в википедию, здесь я его переписывать не буду.

Рассмотрим операцию деления, и начнем именно с «особых» значений. Для того, чтобы хорошо понять особенности работы FPU, я написал небольшую программу, которая выполняет деление различных чисел в 64-битном формате и выводит их двоичные коды. Вот что получилось:


0000000000000000 0000000000000000 fff8000000000000 //0 / 0 = nan
 0000000000000000 8000000000000000 fff8000000000000 //0 / -0 = nan
 0000000000000000 7ff8000000000000 7ff8000000000000 //0 / nan = nan
 0000000000000000 fff8000000000000 fff8000000000000 //0 / nan = nan
 0000000000000000 7ff0000000000000 0000000000000000 //0 / +inf = 0
 0000000000000000 fff0000000000000 8000000000000000 //0 / -inf = -0
 8000000000000000 0000000000000000 fff8000000000000 //-0 / 0 = nan
 8000000000000000 8000000000000000 fff8000000000000 //-0 / -0 = nan
 8000000000000000 7ff8000000000000 7ff8000000000000 //-0 / nan = nan
 8000000000000000 fff8000000000000 fff8000000000000 //-0 / nan = nan
 8000000000000000 7ff0000000000000 8000000000000000 //-0 / +inf = -0
 8000000000000000 fff0000000000000 0000000000000000 //-0 / -inf = 0
 7ff8000000000000 0000000000000000 7ff8000000000000 //nan / 0 = nan
 7ff8000000000000 8000000000000000 7ff8000000000000 //nan / -0 = nan
 7ff8000000000000 7ff8000000000000 7ff8000000000000 //nan / nan = nan
 7ff8000000000000 fff8000000000000 7ff8000000000000 //nan / nan = nan
 7ff8000000000000 7ff0000000000000 7ff8000000000000 //nan / +inf = nan
 7ff8000000000000 fff0000000000000 7ff8000000000000 //nan / -inf = nan
 fff8000000000000 0000000000000000 fff8000000000000 //nan / 0 = nan
 fff8000000000000 8000000000000000 fff8000000000000 //nan / -0 = nan
 fff8000000000000 7ff8000000000000 fff8000000000000 //nan / nan = nan
 fff8000000000000 fff8000000000000 fff8000000000000 //nan / nan = nan
 fff8000000000000 7ff0000000000000 fff8000000000000 //nan / +inf = nan
 fff8000000000000 fff0000000000000 fff8000000000000 //nan / -inf = nan
 7ff0000000000000 0000000000000000 7ff0000000000000 //+inf / 0 = +inf
 7ff0000000000000 8000000000000000 fff0000000000000 //+inf / -0 = -inf
 7ff0000000000000 7ff8000000000000 7ff8000000000000 //+inf / nan = nan
 7ff0000000000000 fff8000000000000 fff8000000000000 //+inf / nan = nan
 7ff0000000000000 7ff0000000000000 fff8000000000000 //+inf / +inf = nan
 7ff0000000000000 fff0000000000000 fff8000000000000 //+inf / -inf = nan
 fff0000000000000 0000000000000000 fff0000000000000 //-inf / 0 = -inf
 fff0000000000000 8000000000000000 7ff0000000000000 //-inf / -0 = +inf
 fff0000000000000 7ff8000000000000 7ff8000000000000 //-inf / nan = nan
 fff0000000000000 fff8000000000000 fff8000000000000 //-inf / nan = nan
 fff0000000000000 7ff0000000000000 fff8000000000000 //-inf / +inf = nan
 fff0000000000000 fff0000000000000 fff8000000000000 //-inf / -inf = nan

Здесь проверяется деление всех комбинаций значений: +0, -0, NaN, -NaN (как я уже писал, знаковый разряд не имеет особого смысла для NaN, но хотелось проверить все варианты), +Inf (бесконечность), -Inf. В общем, довольно логично. 0/0 = NaN, Inf/Inf = NaN, любая операция с NaN даёт в результате NaN. По умолчанию, NaN имеет 1 в знаковом разряде, то есть +0/+0 = (-)NaN, как ни странно, однако если NaN является одним из операндов, то процессор просто копирует его код в результат, никак не изменяя его. Это подтверждается и такими результатами:


bb5580277b40413e fff4d91fa8a5b9e5 fffcd91fa8a5b9e5 //-7.11395e-23 / nan = nan
7ffc143b0befe6ff 730a54668a546338 7ffc143b0befe6ff //nan / 1.43824e+246 = nan

Программа генерации тестового файла сначала перебирает все варианты, приведенные ниже, а затем генерирует 10000 пар случайных вещественных чисел и результат их деления. Если в результате получается денормализованное число, оно заменяется нулём с соответствующим знаком. Для операндов замены не производится.

Также я написал небольшую программу с GUI, «калькулятор», позволяющий производить операции с бинарным представлением чисел с плавающей точкой.

Ссылка на гитхаб: https://github.com/arktur04/FPU

В репозитории содержится исходный текст программы генерации тестового файла (на C++, разработана в Linux, однако не содержит ничего Linux-специфического и должна собираться в любом компиляторе под Windows, возможно, с минимальными изменениями), сам тестовый файл, и программа-калькулятор, позволяющая переводить числа из hex-вида во float и наоборот и производить с числами арифметические действия (на С#, разработана в среде Visual Studio 2012).

По мере продвижения я буду пополнять репозиторий.

В следующий раз мы напишем простой тестбенч для операции деления.

Продолжение следует.

Tags: C и C++, c sharp, fpga, verilog, плис, программирование, статьи
Subscribe

  • Реальная история разработки Commodore C128

    Опубликовал на хабре новый перевод: Реальная история разработки Commodore C128. Заходите, ставьте плюсики, комментируйте там или здесь.

  • Статья "A Detailed Analysis of the LLVM’s Register Allocators"

    Статья "A Detailed Analysis of the LLVM’s Register Allocators". Интересная статья, 9 страниц. Проводится сравнение 4-х аллокаторов регистров,…

  • Введение в LLVM. Часть 1

    LLVM представляет собой инфраструктуру компилятора, и включает большое количество инструментов, предназначенных для построения инструментов…

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 8 comments