Вопрос точности поднят в вопросе может эффективно и эффективно, быть решены за счет использования ограниченных количеств двойного float
или дважды double
вычисления, облегченные использованием операции слиты умножения-сложения (ФМА).
Эта операция доступна, так как C99
через стандартные математические функции fmaf(a,b,c)
и fma(a,b,c)
, которые вычисляют * B + C, без округления промежуточного продукта. Хотя функции непосредственно сопоставляются с быстрыми аппаратными операциями почти на всех современных процессорах, они могут использовать код эмуляции на более старых платформах, и в этом случае они могут быть очень медленными.
Это позволяет вычисление продукта с удвоенной точностью, используя только две операции, в результате чего в голове: хвост пара нативной точных цифр:
prod_hi = a * b // head
prod_lo = FMA (a, b, -hi) // tail
высокого порядка бит результата может быть переданы в экспоненциацию, в то время как младшие биты используются для повышения точности результата с помощью линейной интерполяции, используя тот факт, что е х является его собственным производным:
e = exp (prod_hi) + exp (prod_hi) * prod_lo // exp (a*b)
Это позволяет устранить большую часть ошибки наивной реализации. Другой, второстепенный, источник ошибки вычисления - ограниченная точность, с которой представлена константа 1/√ (2π).Это может быть улучшено с помощью головки: хвост представления для константы, которая обеспечивает в два раза нативную точности и вычислительные:
r = FMA (const_hi, x, const_lo * x) // const * x
В следующей статье указует на то, что этот метод может даже привести к правильно округленному умножению на некоторые произвольном константы точности:
Николас Брисебарре и Жан-Мишель Мюллер, «Правильно закругленное умножение на произвольные прецизионные константы», IEEE Transactions on Computers, Vol. . 57, № 2, февраль 2008, стр 165-174
Сочетание двух методов, и заботиться о нескольких случаях, связанных с угловыми пренебрежимо малых, мы приходим к следующему float
реализации на основе IEEE-754 binary32
:
float my_normpdff (float a)
{
const float RCP_SQRT_2PI_HI = 0x1.988454p-02f; /* 1/sqrt(2*pi), msbs */
const float RCP_SQRT_2PI_LO = -0x1.857936p-27f; /* 1/sqrt(2*pi), lsbs */
float ah, sh, sl, ea;
ah = -0.5f * a;
sh = a * ah;
sl = fmaf (a, ah, 0.5f * a * a); /* don't flip "sign bit" of NaN argument */
ea = expf (sh);
if (ea != 0.0f) ea = fmaf (sl, ea, ea); /* avoid creation of NaN */
return fmaf (RCP_SQRT_2PI_HI, ea, RCP_SQRT_2PI_LO * ea);
}
Соответствующий double
реализации, на основе IEEE-754 binary64
, выглядит почти идентично, для различных постоянных значений, используемых исключением:
double my_normpdf (double a)
{
const double RCP_SQRT_2PI_HI = 0x1.9884533d436510p-02; /* 1/sqrt(2*pi), msbs */
const double RCP_SQRT_2PI_LO = -0x1.cbc0d30ebfd150p-56; /* 1/sqrt(2*pi), lsbs */
double ah, sh, sl, ea;
ah = -0.5 * a;
sh = a * ah;
sl = fma (a, ah, 0.5 * a * a); /* don't flip "sign bit" of NaN argument */
ea = exp (sh);
if (ea != 0.0) ea = fma (sl, ea, ea); /* avoid creation of NaN */
return fma (RCP_SQRT_2PI_HI, ea, RCP_SQRT_2PI_LO * ea);
}
Точность этих реализаций зависит от точности стандартных математических функций expf()
и exp()
, соответственно. В тех случаях, когда математическая библиотека C предоставляет верные версии этих версий, максимальная ошибка одной из двух реализаций, описанных выше, обычно составляет менее 2,5 ulps.
Nice. Этот подход также полезен для получения хорошей точности для 'erfc' для аргументов с большими значениями, для тех из нас, к сожалению, для реализации« erfc »для реализаций libm, которые его уже не поддерживают. –
@MarkDickinson Для моей попытки 'erfcf()', см. [Этот вопрос] (http://stackoverflow.com/questions/35966695/vectorizable-implementation-of-complementary-error-function-erfcf) – njuffa
А, спасибо! Я пропустил это. Да, именно ошибка в термине 'exp (-x^2)' была для меня проблемой. –