Литература по вычислению элементарной функции sin
с таблицами относится к формуле:
sin(x) = sin(Cn) * cos(h) + cos(Cn) * sin(h)
где x = Cn + h
, Cn
- константа, для которой sin(Cn)
и cos(Cn)
были предварительно вычислены и доступны в таблице, и, если следовать методу Gal, Cn
был выбран так, чтобы как sin(Cn)
, так и cos(Cn)
близко аппроксимируются числами с плавающей запятой. Величина h
близка к 0.0
. Примером ссылки на эту формулу является статья (стр. 7).
Я не понимаю, почему это имеет смысл: cos(h)
, однако он вычисляется, вероятно, будет ошибочным, по меньшей мере, на 0,5 ULP для некоторых значений h
, и поскольку он близок к 1.0
, это по-видимому, оказывает резкое влияние на точность результата sin(x)
при вычислении этого способа.
Я не понимаю, почему приведенная ниже формула не используется:
sin(x) = sin(Cn) + (sin(Cn) * (cos(h) - 1.0) + cos(Cn) * sin(h))
Тогда две величины (cos(h) - 1.0)
и sin(h)
могут быть аппроксимированы полиномами, которые легко сделать точными, поскольку они дают результаты вблизи нуля. Значения для sin(Cn) * (cos(h) - 1.0)
, cos(Cn) * sin(h)
и для их суммы все еще малы и его абсолютная точность выражается в ULPs малой величины, которую представляет сумма, так что добавление этой величины в sin(Cn)
почти правильно округлено.
Мне не хватает чего-то, что делает более раннюю, популярную, более простую формулу тоже хорошо себя вести? Разве авторы считают само собой разумеющимся, что читатели поймут, что первая формула фактически реализована как вторая формула?
EDIT: Пример
Таблица с одной точностью для вычисления одноточечной sinf()
и cosf()
может содержать следующую точку в одной точности:
f | cos f | sin f -----------------------+-----------------------+--------------------- 0.017967 0x1.2660bcp-6 | 0x1.ffead8p-1 | 0x1.265caep-6 | (actual value:) | (actual value:) | ~0x1.ffead8000715dp-1 | ~0x1.265cae000e6f9p-6
Следующие функции представляют собой специализированные функции с одной точностью для использования вокруг 0.017967
:
float sinf_trad(float x)
{
float h = x - 0x1.2660bcp-6f;
return 0x1.265caep-6f * cos_0(h) + 0x1.ffead8p-1f * sin_0(h);
}
float sinf_new(float x)
{
float h = x - 0x1.2660bcp-6f;
return 0x1.265caep-6f + (0x1.265caep-6f * cosm1_0(h) + 0x1.ffead8p-1f * sin_0(h));
}
Тестирование этих функций между 0.01f и 0.025f, по-видимому, показывает, что новая формула дает более точные результаты:
$ gcc -std=c99 test.c && ./a.out relative error, traditional: 2.169624e-07, new: 1.288049e-07 sum of squares of absolute error, traditional: 6.616202e-12, new: 2.522784e-12
Я сделал несколько ярлыков, поэтому просмотрите полную программу.