Обратный квадратный корень из землетрясения: точность

«Магический» метод вычисления обратного квадратного корня, по-видимому, восходящий к игре Quake, описан во многих источниках. В Википедии есть хорошая статья об этом: https://en.wikipedia.org/wiki/Fast_inverse_square_root

В частности, я обнаружил, что следующее очень хорошее описание и анализ алгоритма: https://cs.uwaterloo.ca/~m32rober/rsqrt.pdf

Я пытаюсь воспроизвести некоторые из этих результатов в этой статье, но у меня проблемы с точностью. Алгоритм, закодированный на C, имеет следующий вид:

#include <math.h>
#include <stdio.h>

float Q_rsqrt(float number) {
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y = number;
  i = *(long *) &y;
  i = 0x5f3759df - (i >> 1);
  y = *(float *) &i;
  y = y * (threehalfs - (x2 * y * y));
  // y = y * (threehalfs - (x2 * y * y));
  return y;
}

В документе говорится, что относительная ошибка не превышает 0.0017522874 для всех положительных нормальных чисел с плавающей запятой. (См. Код в Приложении 2 и обсуждение в Разделе 1.4.)

Однако, когда я «подключаю» число 1.4569335e-2F, получаемая мной ошибка превышает этот прогнозируемый допуск:

int main ()
{

  float f = 1.4569335e-2F;

  double tolerance = 0.0017522874;
  double actual    = 1.0 / sqrt(f);
  float  magic     = Q_rsqrt(f);
  double err       = fabs (sqrt(f) * (double) magic - 1);

  printf("Input    : %a\n", f);
  printf("Actual   : %a\n", actual);
  printf("Magic    : %a\n", magic);
  printf("Err      : %a\n", err);
  printf("Tolerance: %a\n", tolerance);
  printf("Passes   : %d\n", err <= tolerance);

  return 0;
}

Результат:

Input    : 0x1.dd687p-7
Actual   : 0x1.091cc953ea828p+3
Magic    : 0x1.08a5dcp+3
Err      : 0x1.cb5b716b7b6p-10
Tolerance: 0x1.cb5a044e0581p-10
Passes   : 0

Итак, этот конкретный ввод, кажется, нарушает утверждение, сделанное в этой статье.

Мне интересно, проблема ли это в самой статье, или я допустил ошибку в коде. Буду признателен за любые отзывы!


person alias    schedule 04.01.2017    source источник
comment
Глядя на статью, приложение A.2, максимальная ошибка была вычислена отбор проб. Следовательно, я бы не стал слишком беспокоиться, если бы обнаружил ошибку, немного превышающую максимальную ошибку ...   -  person francis    schedule 04.01.2017
comment
@francis Не совсем. Программа проверяет каждое 32-битное значение от 0x00800000 до 0x7f7fffff, которое охватывает почти весь диапазон положительных значений с плавающей запятой.   -  person r3mainer    schedule 04.01.2017
comment
Я думаю, что @francis прав; здесь проблема в выборке. Указанная относительная ошибка относится к исходному магическому числу, используемому в Quake, которое я тоже использовал. (то есть вопрос действительно в исходном магическом числе и связанной с ним относительной ошибке, а не в улучшениях по сравнению с ним.)   -  person alias    schedule 04.01.2017


Ответы (2)


Давайте попробуем небольшой фрагмент кода пересчитать границу относительной ошибки и покажем, что она немного больше, чем в диссертация Мэтью Робертсона. Действительно, как было впервые отмечено в ответе @squeamishossifrage и отмечено в тезисе Мэтью Робертсона, именно эта реализация была раскрыта в исходниках Quake III. В частности, исходное значение константы Quake III можно найти в исходнике Quake III, в файле q_math.c в строке 561.

Во-первых, необходимо адаптировать код для работы с 64-битными пластинами. Единственное, что может потребоваться изменить, - это целочисленный тип: long не является независимым от формы. На моем компьютере с Linux sizeof(long) возвращает 8 ... Как обновлено в документе на стр. 49, тип uint32_t гарантирует, что тип целого числа будет того же размера, что и float.

Вот код, который должен быть скомпилирован gcc main.c -o main -lm -Wall и запущен ./main:

#include <math.h>
#include <stdio.h>
#include <inttypes.h>

float Q_rsqrt(float number) {
    uint32_t i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y = number;
    i = *(uint32_t *) &y;
    i = 0x5f3759df - (i >> 1); //  0x5f3759df 0x5f375a86
    y = *(float *) &i;
    y = y * (threehalfs - (x2 * y * y));
    // y = y * (threehalfs - (x2 * y * y));
    return y;
}

int main ()
{

    printf("%ld %ld\n",sizeof(long),sizeof(uint32_t));

    uint32_t i;
    float y;
    double e, max = 0.0;
    float maxval=0;
    for(i = 0x0000000; i < 0x6f800000; i++) {
        y = *(float *) &i;
        if(y>1e-30){
            e = fabs(sqrt((double)y)*(double)Q_rsqrt(y) - 1);
            if(e > max){
                max = e;
                maxval=y;
            }
        }
    }
    printf("On value %2.8g == %a\n", maxval, maxval);
    printf("The bound is %2.12g == %a\n", max, max);

    return 0;
}

За границу я получил 0.0017523386721 == 0x1.cb5d752717ep-10. Как вы заметили, он немного больше, чем указано в статье (0.001752287). Оценка ошибки с использованием float вместо double не сильно меняет результат.

person francis    schedule 07.01.2017
comment
Спасибо за анализ! - person alias; 07.01.2017

Вы используете неправильное магическое число.

0x5f3759df - это значение, которое изначально использовалось в Quake III, но позже выяснилось, что 0x5f375a86 дает лучшие результаты. Если вы посмотрите на рис. 6.1 на странице 40 цитированной вами статьи, вы увидите, что он использует улучшенную константу.

Вот результаты, которые я получил с помощью 0x5f375a86:

Input    : 0x1.dd687p-7
Actual   : 0x1.091cc953ea828p+3
Magic    : 0x1.08a5fap+3
Err      : 0x1.cae79153f2cp-10
Tolerance: 0x1.cb5a044e0581p-10
Passes   : 1
person r3mainer    schedule 04.01.2017
comment
действительно, ты прав. Это исходное значение Quake III можно найти в исходный код Quake III в файле q_math.c в строке 561. Комментарий к той самой строке не предназначен для включения на сайт вопросов и ответов ... История константы и оптимума значение для 64-битного IEEE754 double (0x5fe6eb50c7b537a9) можно найти в [wikipedia], цитируя работу Мэтью Робертсон, который также сообщает значения двойной и четверной точности! - person francis; 04.01.2017
comment
Я не уверен, что согласен. Если вы посмотрите на Раздел 4.7 документа, то улучшенное магическое число 0x5f375a86 имеет максимальную относительную ошибку 0,0017512378; которое отличается от исходного магического числа. Поэтому я думаю, что мой вопрос касается относительной ошибки исходной функции Q_rsqrt. - person alias; 04.01.2017
comment
@LeventErkok Я понимаю, о чем вы. Судя по всему, автор использует Q_rsqrt() для обозначения исходного кода Quake III и rsqrt() для обозначения улучшенной версии. Ни одна из этих функций не дает результата, указанного в разделе 4.7. Возможно, вы могли бы попытаться связаться с автором для уточнения. - person r3mainer; 05.01.2017
comment
Я решил, что для тестирования функции Q_rsqrt() на моем 64-битном компьютере будет лучше использовать unsigned int вместо long. Я обнаружил, что самая большая ошибка Q_rsqrt () - 0x1.cb5d752717ep-10 = 0.001752338672. Получается для поплавка 0x1.dd678p-49. Как заметил @LeventErkok, это выше указанной границы ... - person francis; 05.01.2017
comment
Я попытался найти автора, но не смог найти для него адрес электронной почты. Возможно, он когда-нибудь прочтет это! @francis Спасибо за дальнейший анализ. Если вы сможете превратить этот комментарий в ответ, я с радостью его приму! - person alias; 05.01.2017
comment
@LeventErkok Я не уверен, но думаю, что это парень: linkedin.com/in/mattcharobertson - person Giulio Pulina; 29.10.2018
comment
@GiulioPulina Спасибо! Я потерял интерес к дальнейшим исследованиям, но если кто-то еще захочет разобраться в этом, ваша ссылка может оказаться плодотворной! - person alias; 29.10.2018