Един от начините за прилагане на FMA в софтуера е чрез разделяне на значимите на високи и ниски битове. Използвам Алгоритъмът на Декер
typedef struct { float hi; float lo; } doublefloat;
doublefloat split(float a) {
float t = ((1<<12)+1)*a;
float hi = t - (t - a);
float lo = a - hi;
return (doublefloat){hi, lo};
}
След като разделите float, можете да изчислите a*b-c
с едно закръгляване като това
float fmsub(float a, float b, float c) {
doublefloat as = split(a), bs = split(b);
return ((as.hi*bs.hi - c) + as.hi*bs.lo + as.lo*bs.hi) + as.lo*bs.lo;
}
Това основно изважда c
от (ahi,alo)*(bhi,blo) = (ahi*bhi + ahi*blo + alo*bhi + alo*blo)
.
Получих тази идея от функцията twoProd
в статията Числа с плаваща запетая с разширена точност за GPU изчисления и от функцията mul_sub_x
в библиотеката на векторни класове на Agner Fog. Той използва различна функция за разделяне на вектори на плувки, която се разделя по различен начин. Опитах се да възпроизведа скаларна версия тук
typedef union {float f; int i;} u;
doublefloat split2(float a) {
u lo, hi = {a};
hi.i &= -(1<<12);
lo.f = a - hi.f;
return (doublefloat){hi.f,lo.f};
}
Във всеки случай използването на split
или split2
в fmsub
се съгласува добре с fma(a,b,-c)
от математическата библиотека в glibc. По някаква причина моята версия е значително по-бърза от fma
, освен на машина, която има хардуер fma (в който случай използвам _mm_fmsub_ss
така или иначе).
person
Z boson
schedule
08.05.2015
-mfma
или-mfma4
или-march=something
къдетоsomething
е процесор, поддържащ fma). В Linux може да погледнетеsysdeps/ieee754/dbl-64/s_fma.c
в glibc, за да добиете представа как изглежда резервната функция на библиотеката. - person tmyklebu   schedule 20.02.2015