给定两个 IEEE-754 双精度浮点数a和b,我想得到精确的商a / b四舍五入到零的整数。
执行此操作的 C99 程序可能如下所示:
#include <fenv.h>
#include <math.h>
#pragma STDC FENV_ACCESS on
double trunc_div(double a, double b) {
int old_mode = fegetround();
fesetround(FE_TOWARDZERO);
double result = a/b; // rounding occurs here
fesetround(old_mode);
return trunc(result);
}
#include <stdio.h>
int main() {
// should print "6004799503160662" because 18014398509481988 / 3 = 6004799503160662.666...
printf("%.17g", trunc_div(18014398509481988.0, 3.0));
}
现在假设我只能使用最接近偶数舍入模式:我可能正在使用带有优化的 GCC,为微控制器编译,或者必须使其在 JavaScript 中工作。
我尝试的是使用提供的舍入计算a / b,如果结果的幅度太大,则截断并进行补偿:
double trunc_div(double a, double b) {
double result = trunc(a/b);
double prod = result * b;
if (a > 0) {
if (prod > a || (prod == a && mul_error(result, b) > 0)) {
result = trunc(nextafter(result, 0.0));
}
}
else {
if (prod < a || (prod == a && mul_error(result, b) < 0)) {
result = trunc(nextafter(result, 0.0));
}
}
return result;
}
辅助函数mul_error
计算精确的乘法误差(使用 Veltkamp-Dekker 分裂):
// Return the 26 most significant bits of a.
// Assume fabs(a) < 1e300 so that the multiplication doesn't overflow.
double highbits(double a) {
double p = 0x8000001L * a;
double q = a - p;
return p + q;
}
// Compute the exact error of a * b.
double mul_error(double a, double b) {
if (!isfinite(a*b)) return -a*b;
int a_exp, b_exp;
a = frexp(a, &a_exp);
b = frexp(b, &b_exp);
double ah = highbits(a), al = a - ah;
double bh = highbits(b), bl = b - bh;
double p = a*b;
double e = ah*bh - p; // The following multiplications are exact.
e += ah*bl;
e += al*bh;
e += al*bl;
return ldexp(e, a_exp + b_exp);
}
某些输入的补偿是否会失败(例如,由于上溢或下溢)?
有更快的方法吗?
编辑:将第一行的mul_error
from更改… return a*b
为… return -a*b;
. 这修复了a = ±∞ 的情况;有限输入没问题。
感谢Eric Postpischil发现错误。
编辑:如果a,b是有限且非零且除法a / b溢出,我想在舍入到零模式下匹配 IEEE-754 除法,它返回最大有限双精度数 ±(2¹⁰²⁴ − 2⁹⁷¹)。
编辑:函数frexp
和ldexp
只能在必要时调用。这对于具有均匀随机位
的双倍a,b来说是 30% 的加速。
double mul_error(double a, double b) {
if (!isfinite(a*b)) return -a*b;
double A = fabs(a), B = fabs(b);
// bounds from http://proval.lri.fr/gallery/Dekker.en.html
if (A>0x1p995 || B>0x1p995 || (A*B!=0 && (A*B<0x1p-969 || A*B>0x1p1021))) {
// ... can overflow/underflow: use frexp, ldexp
} else {
// ... no need for frexp, ldexp
}
}
也许ldexp
总是不必要的,因为我们只需要知道 mul_error 与 0 的比较。
编辑:如果您有 128 位整数可用,请按照以下步骤操作。(它比原始版本慢。)
double trunc_div(double a, double b) {
typedef uint64_t u64;
typedef unsigned __int128 u128;
if (!isfinite(a) || !isfinite(b) || a==0 || b==0) return a/b;
int sign = signbit(a)==signbit(b) ? +1 : -1;
int ea; u64 ua = frexp(fabs(a), &ea) * 0x20000000000000;
int eb; u64 ub = frexp(fabs(b), &eb) * 0x20000000000000;
int scale = ea-53 - eb;
u64 r = ((u128)ua << 53) / ub; // integer division truncates
if (r & 0xFFE0000000000000) { r >>= 1; scale++; } // normalize
// Scale<0 means that we have fractional bits. Shift them out.
double d = scale<-63 ? 0 : scale<0 ? r>>-scale : ldexp(r, scale);
// Return the maximum finite double on overflow.
return sign * (isfinite(d) ? d : 0x1.fffffffffffffp1023);
}