Есть ли способ получить правильное округление с помощью инструкции i387 fsqrt?
-
12-12-2019 - |
Вопрос
Есть ли способ получить правильное округление с помощью инструкции i387 fsqrt?...
...кроме изменения режима точности в управляющем слове x87 - я знаю, что это возможно, но это неразумное решение, поскольку оно имеет неприятные проблемы типа повторного входа, когда режим точности будет неправильным, если операция sqrt будет прервана.
Проблема, с которой я имею дело, заключается в следующем:х87 fsqrt
код операции выполняет правильно округленную (согласно IEEE 754) операцию квадратного корня с точностью регистров fpu, что, как я предполагаю, является расширенной (80-битной) точностью.Однако я хочу использовать его для реализации эффективных функций квадратного корня одинарной и двойной точности с правильным округлением результатов (в соответствии с текущим режимом округления).Поскольку результат имеет избыточную точность, на втором этапе снова преобразуется результат в округления с одинарной или двойной точностью, что может привести к получению неправильно округленного результата.
С помощью некоторых операций это можно обойти с помощью смещений.Например, я могу избежать избыточной точности результатов сложения, добавив смещение в виде степени двойки, которая принудительно помещает 52 значащих бита значения двойной точности в последние 52 бита 63-битной мантиссы повышенной точности. .Но я не вижу очевидного способа проделать такой трюк с квадратным корнем.
Есть умные идеи?
(Также помечен как C, потому что предполагаемое приложение — это реализация C. sqrt
и sqrtf
функции.)
Решение
Для начала давайте разберемся с очевидным:вам следует использовать SSE вместо x87.ССЕ sqrtss
и sqrtsd
инструкции делают именно то, что вы хотите, поддерживаются всеми современными системами x86, а также работают значительно быстрее.
Теперь, если вы настаиваете на использовании x87, начну с хороших новостей:вам не нужно ничего делать для float.Тебе нужно 2p + 2
бит для вычисления правильно округленного квадратного корня в p-битном формате с плавающей запятой.Потому что 80 > 2*24 + 2
, дополнительное округление до одинарной точности всегда будет корректным, и вы получите правильно округленный квадратный корень.
Теперь плохие новости: 80 < 2*53 + 2
, так что с двойной точностью не повезло.Я могу предложить несколько обходных путей;вот хороший простой вариант, который пришел мне в голову.
- позволять
y = round_to_double(x87_square_root(x));
- используйте произведение Деккера (голова-хвост) для вычисления
a
иb
такой, чтоy*y = a + b
точно. - вычислить остаток
r = x - a - b
. if (r == 0) return y
if (r > 0)
, позволятьy1 = y + 1 ulp
, и вычислитьa1
,b1
с.т.y1*y1 = a1 + b1
.Сравниватьr1 = x - a1 - b1
кr
, и верните либоy
илиy1
, в зависимости от того, какой из них имеет меньший остаток (или тот, у которого младший бит равен нулю, если остатки равны по величине).if (r < 0)
, сделайте то же самое дляy1 = y - 1 ulp
.
Эта процедура обрабатывает только режим округления по умолчанию;однако в режимах направленного округления достаточно простого округления до целевого формата.
Другие советы
Хорошо, я думаю, у меня есть лучшее решение:
- Вычислить
y=sqrt(x)
в расширенной точности (fsqrt
). - Если последние 11 бит не
0x400
, просто преобразуйте в двойную точность и верните результат. - Добавлять
0x100-(fpu_status_word&0x200)
к младшему слову представления расширенной точности. - Преобразование в двойную точность и возврат.
Шаг 3 основан на том факте, что бит C1 (0x200) слова состояния равен 1 тогда и только тогда, когда fsqrt
результат округлили в большую сторону.Это действительно так, поскольку из-за теста на шаге 2 x
не был идеальным квадратом;если бы это был идеальный квадрат, y
не будет иметь битов, превышающих двойную точность.
Возможно, будет быстрее выполнить шаг 3 с условной операцией с плавающей запятой, чем работать над представлением битов и перезагрузкой.
Вот код (кажется, работает во всех случаях):
sqrt:
fldl 4(%esp)
fsqrt
fstsw %ax
sub $12,%esp
fld %st(0)
fstpt (%esp)
mov (%esp),%ecx
and $0x7ff,%ecx
cmp $0x400,%ecx
jnz 1f
and $0x200,%eax
sub $0x100,%eax
sub %eax,(%esp)
fstp %st(0)
fldt (%esp)
1: add $12,%esp
fstpl 4(%esp)
fldl 4(%esp)
ret
Возможно, это не то, что вам нужно, поскольку оно не использует преимущества 387. fsqrt
инструкция, но есть удивительно эффективный sqrtf(float)
в glibc реализован с помощью 32-битной целочисленной арифметики.Он даже правильно обрабатывает NaN, Infs, субнормальные значения — возможно, можно было бы исключить некоторые из этих проверок с помощью реальных инструкций x87/флагов управляющего слова FP.видеть: glibc-2.14/sysdeps/ieee754/flt-32/e_sqrtf.c
А dbl-64/e_sqrt.c
код не такой дружелюбный.Трудно с первого взгляда сказать, какие предположения сделаны.Любопытно, что библиотека i386 sqrt[f|l]
реализации просто позвоните fsqrt
, но загрузите значение по-другому. flds
для СП, fldl
для ДП.