2016-02-16 4 views
1

Следующий код должен квантовать положительный (одиночная точность) число с плавающей запятой до 32-разрядного целого. Как положительный диапазон содержит только 2^31 - 1 (дискретные) уровней, код умножает образец на этой величине, а округляет результат до целого числа:x86 Assembly (SSE): Неожиданный результат умножения

mov eax, 0x7FFFFFFF // eax = 2^31 - 1 
cvtsi2ss xmm1, eax // convert eax to float --> xmm1 
movss xmm0, [sample] // where 'sample' is of type float 
mulss xmm0, xmm1  // Get sample's quantum into xmm0 
cvtss2si eax, xmm0 // Round quantum to the nearest integer --> eax 

Проблема заключается в: что для sample значения 1.0f, конца результат (eax значение) 0x80000000 = 2^31, что вне допустимого диапазона. Ожидаемый результат: 1.0 x (2^31 - 1) = (2^31 - 1) = 0x7FFFFFFF.

Кроме того, это значение представляет собой представление дополнений 2 от -2^31 (обратите внимание на знак минус).

Что мне здесь не хватает?

{MSVC2010 используется для тестирования. } `

+3

0x7FFFFFFF при преобразовании в поплавок округляется до 2147483648 для начала – harold

+2

Да, вы должны попробовать двойную точность. – Jester

+0

@harold, так что в основном вы говорите, что 'xmm1' содержит float-представление' 2^31' и * not * of '2^31 - 1'? – Bliss

ответ

3

При перемещении 2 -1 до EAX и преобразовать его из 32-разрядного целого числа к одному (32-бит) скалярного поплавка.

mov eax, 0x7FFFFFFF // eax = 2^31 - 1 
cvtsi2ss xmm1, eax // convert eax to float --> xmm1 

Проблема заключается в том, что нет достаточного количества мантиссы в IEEE754 32-битной плавающей точкой для представления 2 -1 точно. Он фактически округляется до 2.147483648E9. Существует online binary converter, который может лучше описать, как это произошло. Преобразование целого числа 2 -1 до одного скалярного поплавка 2.147483648E9 является demonstrated here


Точно представляет каждое целое число от 0 до 2 -1 занимают 31 бит. 32-битный плавающий IEEE (с 23 + 1 implicit bit mantissa) может exactly represent every integer with magnitude up to 224. Вне этого диапазона полномочия 2 точно представляются.

Это доказывает (с теорией информации), что невозможно спроектировать 31-битную кодировку, которая может точно представлять все целые числа от 0 до 2 -1, а также иметь возможность представлять любые другие значения. Целые числа используют все пространство для кодирования. Если бы такая возможность была возможна, вы могли бы использовать эту технику несколько раз, чтобы сжать все данные мира в один бит.


0x80000000 результат, как переполнение cvtss2si и cvtsd2si сигнала. От Intel insn иого руководства (см вики для ссылок):

Если преобразованный результат больше, чем максимум, подписанного в двойном целом, с плавающей точкой недействителен возбуждаются исключение, и если это исключение маскируются , возвращается неопределенное целочисленное значение (80000000H).

Это не имеет никакого отношения к целочисленному wraparound, или поплавковому значению, которое находится за точным результатом.

Обратите внимание, что с 64-битным целочисленным регистром cvtss2si rax, xmm1 может производить результаты до 0x7fffff8000000000, с большими поплавками, производящими «неопределенное значение» 0x8000000000000000. Это противоречит описанию текста в руководстве Intel, где они забыли обновить абзац max-value для 64-разрядного размера операнда, чтобы он соответствовал тому, что говорит cvtsd2si. Наибольшее целое число, которое вы можете совершить в одноточечном поплавке, без создания переполнения, равно 0x7fffffbfffffffff.


Если вы используете двойной скаляр есть достаточно мантисса, чтобы точно представлять 2 -1. Преобразование целого числа 2 -1 в двойной скалярный поплавок 2.147483647E9 - demonstrated here.

Как отметил Шестер, используя двойные (64-битные) скалярные поплавки, ваша проблема будет исправлена. Этот код может выглядеть примерно так:

double sample = 1.0f; 

__asm 
{ 
    mov eax, 0x7FFFFFFF // eax = 2^31 - 1 
    cvtsi2sd xmm1, eax // convert eax to double float --> xmm1 
    movsd xmm0, [sample] // where 'sample' is of type double float 
    mulsd xmm0, xmm1  // Get sample's quantum into xmm0 
    cvtsd2si eax, xmm0 // Round quantum to the nearest integer --> eax 
} 

Если вы хотите сохранить sample как 32-битной плавающей точкой вместо двойной в моем примере, вы могли бы заменить movsd xmm0, [sample] с cvtss2sd xmm0, [sample]

Учитывая, что этот ответ основан после ввода нескольких участников я отметил это как сообщество wiki, так что не стесняйтесь редактировать.

+1

Привет, Майкл, и спасибо за подробный, самый полезный ответ (который был отмечен как * ответ *). Если я правильно понял, мой исходный код был бы подходящим для глубин кодирования до 25 бит на выборку (т. Е. Амплитуда - 24 бит), но для 26-32 преобразование в float с одной точностью приведет к (внутренняя) ошибка. – Bliss

+0

@Bliss: Спасибо, что приняли ответ. Поскольку это было групповое усилие, и потому что я отметил это как сообщество wiki, никто не получает никакой репутации (включая меня), но теперь он будет по крайней мере отмечен как закрытый/ответ для поиска людей в будущем. –

+0

Не следует ли, чтобы последняя инструкция 'cvtsd2si' на вашем коде была заменена на' cvtss2sd'? – Bliss