Есть ли способ получить правильное округление с помощью инструкции i387 fsqrt?

Есть ли способ получить правильное округление с помощью инструкции i387 fsqrt? ...

... помимо изменения режима точности в управляющем слове x87 - я знаю, что это возможно, но это не разумное решение, потому что у него есть неприятные проблемы с повторным входом, когда режим точности будет неправильным, если Операция sqrt прервана.

Проблема, с которой я имею дело, заключается в следующем: код операции x87 fsqrt выполняет операцию с правильным округлением (согласно IEEE 754) квадратного корня в точности регистров fpu, что, как я предполагаю, является расширенной (80-битной) точностью. Однако я хочу использовать его для реализации эффективных функций квадратного корня с одинарной и двойной точностью с правильным округлением результатов (в соответствии с текущим режимом округления). Поскольку результат имеет избыточную точность, второй шаг преобразования результата в округление с одинарной или двойной точностью, возможно, приведет к некорректному округлению результата.

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

Есть какие-нибудь умные идеи?

(Также отмечен тегом C, поскольку предполагаемое приложение является реализацией функций C sqrt и sqrtf.)


person R.. GitHub STOP HELPING ICE    schedule 13.03.2012    source источник
comment
Из любопытства: есть ли причина, по которой вы не можете использовать здесь математику SSE2?   -  person    schedule 13.03.2012
comment
Потому что целью являются все машины x86, а не пост-Pentium-2 или что-то еще.   -  person R.. GitHub STOP HELPING ICE    schedule 13.03.2012
comment
Разве это не округление в 4- или 8-байтовой памяти? Или это слишком много накладных расходов?   -  person Mysticial    schedule 13.03.2012
comment
Выполняется второй шаг округления. Предположим, я прошу вас округлить 1,49 до целого числа. Округление за один шаг дает 1. Сначала округление до одного места после десятичной точки дает 1,5, затем округление до целого числа дает 2. Точно так же fsqrt выполняет одно округление (поскольку точное значение квадратного корня почти никогда не представляется) и преобразование его из 80-битной расширенной точности в правильный тип выполняет другое округление.   -  person R.. GitHub STOP HELPING ICE    schedule 13.03.2012
comment
Ой, что ты имеешь в виду. Я склонен думать, что математические свойства квадратного корня запрещают возникновение таких крайних случаев. Но это немного выходит за рамки моей компетенции.   -  person Mysticial    schedule 13.03.2012
comment
Это случается не часто, но есть достаточно много двойников, чтобы это случается время от времени. Точный критерий представляет собой что-то вроде битов 52-64 точного результата, выглядящего как 101000 ... 00, за которым следует ненулевой хвост где-то за концом 64-битной мантиссы расширенной точности. Возможно, можно было бы работать в обратном направлении и перечислять случаи, но я думаю, что их слишком много, чтобы рассматривать их как особые случаи.   -  person R.. GitHub STOP HELPING ICE    schedule 13.03.2012


Ответы (3)


Во-первых, давайте уберем очевидное: вы должны использовать SSE вместо x87. Инструкции SSE sqrtss и sqrtsd делают именно то, что вы хотите, поддерживаются во всех современных системах x86 и также значительно быстрее.

Теперь, если вы настаиваете на использовании x87, я начну с хороших новостей: вам не нужно ничего делать для float. Вам нужно 2p + 2 бит, чтобы вычислить правильно округленный квадратный корень в p-битном формате с плавающей запятой. Поскольку 80 > 2*24 + 2, дополнительное округление до одинарной точности всегда будет округляться правильно, и вы получите правильно округленный квадратный корень.

А теперь плохие новости: 80 < 2*53 + 2, так что не повезло с двойной точностью. Я могу предложить несколько обходных путей; вот приятная легкая штука с моей головы.

  1. пусть y = round_to_double(x87_square_root(x));
  2. используйте произведение Деккера (голова-хвост) для вычисления a и b, так что y*y = a + b точно.
  3. вычислить остаток r = x - a - b.
  4. if (r == 0) return y
  5. if (r > 0), пусть y1 = y + 1 ulp, и вычислите a1, b1 s.t. y1*y1 = a1 + b1. Сравните r1 = x - a1 - b1 с r и верните либо y, либо y1, в зависимости от того, какой остаток имеет меньший остаток (или тот, у которого нулевой бит младшего разряда, если остатки равны по величине).
  6. if (r < 0), сделайте то же самое для y1 = y - 1 ulp.

Эта процедура обрабатывает только режим округления по умолчанию; однако в режимах направленного округления правильное значение имеет простое округление до формата назначения.

person Stephen Canon    schedule 13.03.2012
comment
+1 Единственный раз, когда двойное округление может закончиться неудачно, - это если первое округление больше. Принудительное усечение избавит от этой проблемы. - person Mysticial; 13.03.2012
comment
@Mysticial: неверно, что двойное округление является проблемой только в том случае, если первое округление больше. Рассмотрим значение формы ...0 100...00 0...1 (где пробелы обозначают круглые точки). Если мы округляем непосредственно до первой точки округления, мы округляем до ...1. Однако, если мы сначала округляем до второй точки округления, мы округляем до ...0 100...00; повторное округление в первой точке округления округляется до ...0. - person Stephen Canon; 13.03.2012
comment
@Mysticial: честно говоря, эти вещи довольно тонкие. - person Stephen Canon; 13.03.2012
comment
Действительно, с округлением до половины все намного проще. К сожалению, в реальном мире нам нужно округление до четности, чтобы избежать появления уродливых предубеждений. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
@StephenCanon: Похоже, у вашего алгоритма есть очевидная возможность остановиться на шаге 1, если биты 53-64 однозначно определяют округление, независимо от того, какие были последующие потерянные биты. Только угловые случаи требуют дополнительной работы. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
И .. у вас есть ссылка на шаг 2? Продукт Dekker не находит подходящих результатов. - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
@R ..: конечно, можно пораньше. Я старался сделать это как можно проще =). Продукт Dekker - это уловка, созданная Т.Дж. Деккер для вычисления a + b = x * y точно с плавающей запятой. Он был опубликован в «Технике с плавающей запятой для увеличения доступной точности» (1971), но вы можете найти подробный алгоритм практически в любом тексте по реализации с плавающей запятой. Я знаю, что это тоже есть в руководстве по crlibm, которое вы легко можете скачать. - person Stephen Canon; 13.03.2012
comment
(Кстати, продукт Dekker в crlibm называется Mul22) - person Stephen Canon; 13.03.2012
comment
На самом деле я думаю, что это Mul12Cond, но спасибо за ссылку; Я смог найти это с этим. Случайно рассмотрев алгоритм, он, кажется, полностью отвечает на вопрос, так что принято! - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
Правильно ли я полагаю, что единственный раз, когда нам нужно сделать что-либо после шага 1, - это когда последние 11 бит результата расширенной точности равны 10000000000? - person R.. GitHub STOP HELPING ICE; 15.03.2012
comment
@Stephen: Я думаю, что этот алгоритм можно значительно улучшить, если вы будете работать с не округленным до двойного значения результатом с расширенной точностью как y, а не с округленным значением. Мне кажется, вам просто нужно определить, является ли y меньше или больше фактического значения sqrt(x), а затем (соответственно) увеличить или уменьшить его на 1ulp (расширенная точность) перед округлением до двойной точности. (Обратите внимание, это предполагает, что вы находитесь в случае, когда значение расширенной точности y заканчивается на 10000000000.) - person R.. GitHub STOP HELPING ICE; 15.03.2012
comment
@Stephen: Имея это в виду, условие C1 слова состояния i387 равно 1, если неточный результат был округлен в большую сторону, и 0, если он был округлен в меньшую сторону. - person R.. GitHub STOP HELPING ICE; 15.03.2012
comment
@R ..: Да, есть множество способов улучшить его (ни один из них не так хорош, как простое использование sqrtsd). Я хотел дать такой вариант, который было бы легко объяснить численно и который не зависел бы от неясных функций процессора или большого количества манипуляций с битовыми шаблонами - а не то, что я лично сделал бы при реализации библиотеки. Вы, конечно, можете сделать это в точности так, как вы описываете, хотя я хотел бы отметить, что вы должны фактически измерить производительность ветвления на C1; это именно то, что когда-то могло быть быстрым, но теперь представляет некоторую архитектурную опасность. - person Stephen Canon; 15.03.2012

Хорошо, думаю, у меня есть решение получше:

  1. Вычислить y=sqrt(x) с повышенной точностью (fsqrt).
  2. Если последние 11 бит не равны 0x400, просто преобразуйте их в двойную точность и верните.
  3. Добавьте 0x100-(fpu_status_word&0x200) к младшему слову представления с расширенной точностью.
  4. Преобразуйте в двойную точность и верните.

Шаг 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
person R.. GitHub STOP HELPING ICE    schedule 15.03.2012
comment
Этот подход кажется разумным при беглом осмотре (и, безусловно, он ближе к тому, что я выбрал бы при реализации библиотеки самостоятельно). Вы можете запустить его на тестовых векторах Джерома Кунена для получения дополнительных данных. Почему fld + fstp вместо fst? - person Stephen Canon; 15.03.2012
comment
Насколько мне известно, версии fst с повышенной точностью не существует, есть только fstp. - person R.. GitHub STOP HELPING ICE; 15.03.2012

Возможно, это не то, что вам нужно, поскольку в нем не используется инструкция 387 fsqrt, но есть удивительно эффективный sqrtf(float) в glibc, реализованный с использованием 32-битной целочисленной арифметики. Он даже правильно обрабатывает NaN, Inf и субнормальные значения - возможно, можно будет устранить некоторые из этих проверок с помощью реальных инструкций x87 / флагов управляющего слова FP. см .: glibc-2.14/sysdeps/ieee754/flt-32/e_sqrtf.c

Код dbl-64/e_sqrt.c не такой дружелюбный. Сложно сразу сказать, какие предположения делаются. Любопытно, что реализации библиотеки i386 sqrt[f|l] просто вызывают fsqrt, но загружают значение по-другому. flds для SP, fldl для DP.

person Brett Hale    schedule 13.03.2012
comment
Я посмотрю на целочисленный код. Интересно, есть ли аналогичный подход для двойной точности ... - person R.. GitHub STOP HELPING ICE; 13.03.2012
comment
@R .., я подозревал, что это может быть. Каковы границы ошибок для sqrt IEEE-754? Это 1/2 (ulp)? Влияет ли режим округления на внутренний расчет или только на возвращаемое значение? - person Brett Hale; 13.03.2012
comment
Предполагается, что результат будет правильно округлен в текущем режиме округления. Трансцендентные функции менее строгие; их не нужно правильно округлять, если возвращаемый результат верен в пределах 1ulp. - person R.. GitHub STOP HELPING ICE; 13.03.2012