Умножение мантиссы в арифметике с плавающей запятой

Что касается мантиссы (см. это руководство по арифметике с плавающей запятой), как вы на самом деле умножить две мантиссы вместе?

Предположим, что представление с плавающей запятой одинарной точности IEEE 754.

Допустим, у одного числа есть мантисса 1.5, которая будет закодирована как 0b10000000000000000000000 (что равно 4194304 в десятичной системе). У второго числа есть мантисса 1.125, которая будет закодирована как 0b00100000000000000000000 (что равно 1048576 в десятичной системе).

1.5 x 1.125 = 1.6875.

1.6875 кодируется как 0b10110000000000000000000 (что равно 5767168 в десятичной системе). Однако 4194304 * 1048576 не равно _12 _...

Как работает умножение мантиссы, когда умножение 4194304 (1,5) на 1048576 (1,125) дает 5767168 (1,6875)?

То есть как умножить закодированные мантиссы?

Т.е. как оборудование использует закодированные мантиссы, хранящиеся в памяти, для получения нового значения?



Из руководства умножение чисел с плавающей запятой можно выполнить следующим образом. Я застрял на шаге 2.

  1. Отделите мантиссу от экспоненты
  2. Умножьте (или разделите) части мантиссы вместе
  3. Сложите (или вычтите) экспоненты вместе
  4. Объедините два результата в новое значение
  5. Нормализовать значение результата (необязательно)

person Jet Blue    schedule 18.02.2018    source источник
comment
Обратите внимание, что 1/2 и 1/8 имеют одинаковую мантиссу, поскольку они представлены как 1 * 2 ^ -1 и 1 * 2 ^ -3, то есть мантисса равна 1, а степень - -1 и -3. Фактически вы даже не увидите 1 в представлении этих чисел IEEE 754 из-за понятия скрытого бита.   -  person Kevin Jin    schedule 19.02.2018
comment
Ошибочное мышление: 0,125 будет иметь ту же мантиссу, что и 0,5, только другой показатель степени. Плавающая точка IEEE-754 нормализована (за исключением очень малых чисел).   -  person Rudy Velthuis    schedule 19.02.2018
comment
Обновил пример в вопросе. Это была моя ошибка. Однако, если вы посмотрите на обновление, вы увидите, что мой вопрос все еще актуален.   -  person Jet Blue    schedule 19.02.2018


Ответы (3)


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

Возьму 1,5 * 3,0 с одинарной точностью.

0x3FC00000  1.5
0x40400000  3.0
0x40900000  1.5 * 3.0 = 4.5

расширять

0x3FC0
0011111111000000
0 01111111 1000000

0x4040
0100000001000000
0 10000000 1000000

поэтому в двоичном формате мы умножаем 1,1 на 1,1, как в начальной школе, только проще

  11
* 11
======
  11 
+11
======
1001

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

10.01

но нам нужно нормализовать это

1.001 with an increase in the exponent.

экспоненты

01111111 2^0

10000000 2^1

Как и в начальной школе, мы складываем показатели 2 ^ (0 + 1) = 2 ^ 1. Затем у нас есть нормализация добавления еще одного сдвига на единицу к показателю 2 ^ (1 + 1) = 2 ^ 2.

0 10000001 001000....
010000001001000....
0100 0000 1001 000....

дает результат, соответствующий тому, что произвел компьютер

0x40900000

В этом нет никакого волшебства.

Аппаратное умножение ничем не отличается от школьного, просто оно проще. Посмотрите на эти отдельные биты: a - это бит 0 или 1, b - это бит 0 или 1 и так далее.

   ab
*  cd
======
   ef
  gh
======
 jklm

если d равно 0, то оба ef равны нулю, если d равно 1, то ef = ab, поэтому уравнения до сих пор имеют вид

e = a & d
f = b & d
g = a & c
h = b & c

все, что связано с 0, равно 0, все, что связано с 1, является само по себе.

затем делаем сложение

 nop0
   ef
+ gh
======
 jklm

Я жестко жульничал здесь, сложение двух битов, выполнение и результат, таблица истинности

xy cr
00 00 
01 01
10 01
11 10

результат - x или y, выполнение - x и y

m = f
p = 0 because f+0 cant have a carry bit.
l = e xor h 
o = e & h 
k = o xor g
n = o & g
j = n

замены и, надеюсь, я не допущу (больше) ошибок

m = f = b & d
l = e xor h = (a&d) xor (b&c)
k = o xor g = (e & h) xor (a&c) = ((a&d) & (b&c)) xor (a&c)
j = n = o & g = (e & h) & (a&c) = ((a&d) & (b&c)) & (a&c)

так что там, вероятно, есть некоторые оптимизации, но если вам нужно / вы хотите вычислить двухбитный множитель на два бита за один такт, есть ваши уравнения ввода и вывода. Сильный обман, потому что делайте 3x3, и сложение становится намного хуже. Работайте до 8 бит, умноженных на 8, или до 32 бит, умноженных на 32. Переносимые биты начинают рассылать спам поверх других переносимых бит, математика - это не то, что вы хотите пытаться вручную. Это возможно, но создает огромное количество логики, множители одиночных тактовых импульсов могут потреблять значительный процент вашего чипа, есть уловки, такие как ... использование более одного тактового сигнала и канала, создающего иллюзию одного тактового сигнала ...

Вернемся к прежним временам, когда мы просто не могли сжечь столько логики, мы бы сказали обнулить аккумулятор, затем взять биты ab И оба с d сдвигом влево до нуля и добавить к аккумулятору. Возьмите биты ab И оба с помощью c сдвиньте влево и сложите в аккумулятор ... Точно так же, как мы делаем умножение бумаги и карандаша. Если у вас 8 бит * 8 бит, то для умножения требуется не менее 8 тактов. Потом нормализация и тд и тп ...

В любом случае, вы можете видеть, что умножение в логике ничем не отличается от того, что мы узнали в начальной школе, только проще, поскольку мы умножаем только на 0 и 1. Вы либо пишете нули, либо копируете операнд как есть.

Короткий ответ, вы, вероятно, упустили тот момент, что есть скрытый / подразумеваемый бит, нет причин тратить немного на формат, который можно использовать для точности. IEEE 754 - это 1 мантисса в некоторой степени при нормализации.

двойная же история, подразумевается 1. мантисса:

0x3FF8000000000000 1.5
0x4008000000000000 3.0
0x4012000000000000 4.5

0011111111111000
0100000000001000
0100000000010010

0 01111111111 1000   1.1000...
0 10000000000 1000   1.1000...
0 10000000001 0010   1.0010....

Другой способ взглянуть на это без десятичных точек (или с их подразумеваемыми, и они не десятичные точки, а двоичные точки, я думаю).

Сверху 1.10000 ... * 1.10000 - это 0xC00000 * 0xC00000 = 0x900000000000, 48 бит. Мы убираем половину битов до 0x900000, потому что у нас есть 1 и 47 бит мантиссы, но есть только место для 23, поэтому мы отсекаем 24 младших бита, будь то нули или любое другое число. Не случайно получается, что мы оставляем половину битов и отбрасываем половину битов. Теперь, если бы это было 1.000 ... * 1.000 ... у нас было бы 0x400000000000 1 и 46 бит, разделенных на 23, а не на 24 бита. Вы можете провести несколько экспериментов с меньшим количеством битов, но помните, что в отличие от любых двух N-битных чисел, умноженных друг на друга, у нас есть подразумеваемая / фиксированная 1 вверху. Таким образом, (1 * 2 ^ 23) * (1 * 2 ^ 23) = (1 * 1) * (2 ^ (23 + 23) = 1 * 2 ^ 46 всегда присутствует, когда мантиссы умножаются (для нормального, не -нуль, числа).

Существуют и другие форматы с плавающей запятой, которые по большей части работают одинаково, они могут быть проще для отрицательных чисел и т. Д. (Поищите, например, формат ti DSP, если вы можете его найти, созданный для скорости, а не для угловых случаев).

person old_timer    schedule 19.02.2018
comment
Почему i пропускается в jklm? - person Pacerier; 31.01.2020

Вам нужен продукт (1.0 * 2 -1) * (1.0 * 2 -3).

Мантиссы равны 1 и 1, а показатель степени - -1 и -3.

Следовательно, произведение мантисс равно 1 * 1 = 1, а сумма показателей составляет (-1) + (-3) = -4.

Следовательно, произведение двух факторов составляет 1 * 2 -4 = 0,0625 в соответствии с шагами с 1 по 4.

Следует помнить о двух важных вещах: (1) показатели в двоичной системе с плавающей запятой IEEE 754 всегда представляют степени двойки, и (2) целая часть нормализованной мантиссы всегда равна 1 (поэтому первый бит может быть скрыт. в фактическом двоичном представлении числа).


РЕДАКТИРОВАТЬ: включение некоторых моих комментариев в этот ответ, поскольку вопрос был переписан.

Связанное вами руководство описывает поведение, но не реализацию. Если бы реализация была такой простой, я бы предположил, что математика с плавающей запятой была бы такой же быстрой, как целочисленная.

Для мантиссы оборудование может в какой-то момент сделать что-то вроде добавления обратно в скрытые биты, так что у вас есть

0b110000000000000000000000 * 0b100100000000000000000000 = 0b11011000000000000000000000000000000000000000000

Это становится 0b10110000000000000000000 после отбрасывания скрытого бита и округления конечных битов.

Для показателя степени аппаратное обеспечение может вычесть 127 из смещенных показателей обоих факторов, выполнить арифметические операции и затем прибавить 127 обратно к сумме или разности, чтобы повторно смещать показатель степени.

Имейте в виду, что 64-битный формат используется для компактного кодирования чисел в памяти, но это может не быть фактическим представлением, используемым при выполнении математических вычислений. Процессоры могут даже выполнять промежуточные математические вычисления с 80-битной точностью перед округлением результата до 64-битных, когда значение записывается обратно в память. См. x87 80-битная двойная расширенная точность.

person Kevin Jin    schedule 18.02.2018
comment
Ах, я вижу свою ошибку. Я обновлю вопрос соответственно. Но мой вопрос о выполнении арифметических действий с закодированными значениями все еще остается в силе. -1 кодируется как 126, -3 как 124, -4 как 123. Как вы используете 126 и 124, чтобы получить 123? Вот как вы выполняете арифметические действия с закодированными значениями, чтобы прийти к правильному ответу? Т.е. как аппаратное обеспечение принимает 126 и 124 и приходит к 123? - person Jet Blue; 19.02.2018
comment
На самом деле я не слишком уверен в том, как это на самом деле реализовано на оборудовании, но я полагаю, что вычесть 127 из смещенных показателей обоих факторов не слишком дорого, выполнить арифметику, а затем добавить 127 обратно к сумме или разнице. для повторного смещения показателя степени: при интерпретации он преобразуется в показатель степени в пределах подписанного диапазона путем вычитания смещения - person Kevin Jin; 19.02.2018
comment
Истинное вычитание не так уж и дорого. Но это восходит к моему первоначальному вопросу с мантиссой. Обратить процесс кодирования невозможно с помощью мантиссы (поскольку исходное состояние - это число с плавающей запятой (а не целое число, такое как экспонента) ...) - person Jet Blue; 19.02.2018
comment
Связанное вами руководство описывает поведение, но не реализацию. Если бы реализация была такой простой, я бы предположил, что математика с плавающей запятой была бы такой же быстрой, как целочисленная. Возможно, оборудование может в какой-то момент сделать что-то вроде добавления скрытых битов, так что у вас будет 0b110000000000000000000000 * 0b100100000000000000000000 = 0b1101100000000000000000000000000000000000000, который становится 0b10110000000000000000000 после отбрасывания скрытого бита и округления конечных битов. - person Kevin Jin; 19.02.2018
comment
Имейте в виду, что 64-битный формат используется для компактного кодирования чисел в памяти, но это может не быть фактическим представлением, используемым при выполнении математических вычислений. Процессоры могут даже выполнять промежуточные математические вычисления с 80-битной точностью перед округлением результата до 64-битных, когда значение записывается обратно в память. См. x87 80-битная двойная расширенная точность. - person Kevin Jin; 19.02.2018
comment
Я наткнулся на это видео, которое проясняет некоторые вещи. Смотрите мой ответ. Есть вероятность, что оборудование просто сдвинет конечные нули, а затем сдвинет на единицу. Использует новые значения для расчета. Затем возвращается к сохранению (сдвигает конечные нули, сдвигает ведущий). - person Jet Blue; 19.02.2018
comment
Когда я писал свой комментарий, я в третий раз взглянул на ваши предыдущие два комментария и понял, что, по сути, вы пришли к выводу ... - person Jet Blue; 19.02.2018

Я нашел это видео, которое ответило на мой вопрос.

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

Таким образом, для 1.5 0b11 (3) используется вместо 0b10000000000000000000000 (4194304).
Аналогично для 1.125, 0b1001 (9) используется вместо 0b00100000000000000000000 (1048576).

Таким образом 0b11 * 0b1001 = 0b11011 (27).

Если игнорировать дополнительный начальный бит * в 0b11011 и добавить конечные нули, получится 0b10110000000000000000000 (5767168).


* Что-то важное о значащих битах объясняется в около 8:05. В этом примере оказывается, что достаточно игнорировать дополнительный ведущий бит.

person Jet Blue    schedule 19.02.2018