[英]Safe Floating Point Division
我在代码中有一些地方要确保2个任意浮点数(32位单精度)的除法不会溢出。 目标/编译器不能保证(足够明确地)对-INF / INF进行良好的处理,并且(不能完全保证IEEE 754的特殊值-(可能未定义)-目标可能会改变)。 同样,我无法在这几个特殊位置的输入上做保存假设,并且我绑定到C90标准库。
我已经阅读了每位计算机科学家应该知道的有关浮点算法的内容,但老实说,我有点迷失了。
所以...我想问社区,以下代码是否可以解决问题,以及是否有更好/更快/更精确/更正确的方法来实现:
#define SIGN_F(val) ((val >= 0.0f)? 1.0f : -1.0f)
float32_t safedivf(float32_t num, float32_t denum)
{
const float32_t abs_denum = fabs(denum);
if((abs_denum < 1.0f) && ((abs_denum * FLT_MAX) <= (float32_t)fabs(num))
return SIGN_F(denum) * SIGN_F(num) * FLT_MAX;
else
return num / denum;
}
编辑:根据Pascal Cuoq的建议,将((abs_denum * FLT_MAX) < (float32_t)fabs(num))
更改为((abs_denum * FLT_MAX) <= (float32_t)fabs(num))
。
您可以尝试提取num和denum的指数和尾数,并确保满足以下条件:
((exp(num) - exp (denum)) > max_exp) && (mantissa(num) >= mantissa(denum))
并根据输入的符号生成相应的INF。
在((abs_denum * FLT_MAX) < (float32_t)fabs(num)
,乘积abs_denum * FLT_MAX
可能会舍入并最终等于fabs(num)
,这并不意味着num / denum
在数学上不大于FLT_MAX
,并且你应该担心它可能发生导致要避免溢出。你最好更换此<
通过<=
对于替代解决方案,如果可以使用double
类型并且比float
宽,则计算(double)num/(double)denum
可能更经济。 如果float
是binary32ish而double
是binary64ish,则double
除法溢出的唯一方法是denum
为(a)为零(如果denum
为零,则您的代码也有问题)。
double dbl_res = (double)num/(double)denum;
float res = dbl_res < -FLT_MAX ? -FLT_MAX : dbl_res > FLT_MAX ? FLT_MAX : (float)dbl_res;
当商接近FLT_MAX
时num, denom
请小心使用num, denom
FLT_MAX
。
以下使用受OP启发的测试,但与FLT_MAX
附近的结果保持距离。 正如@Pascal Cuoq指出的那样,舍入可能只会将结果推到边缘。 相反,它使用FLT_MAX/FLT_RADIX
FLT_MAX*FLT_RADIX
和FLT_MAX*FLT_RADIX
FLT_MAX/FLT_RADIX
阈值。
通过使用FLT_RADIX
(通常为2)进行缩放,代码应始终获得准确的结果。 在任何舍入模式下舍入均不会影响结果。
就速度而言,“快乐之路”,即当结果肯定不会溢出时,应该是快速的计算。 仍然需要进行单元测试,但是注释应提供这种方法的要点。
static int SD_Sign(float x) {
if (x > 0.0f)
return 1;
if (x < 0.0f)
return -1;
if (atan2f(x, -1.0f) > 0.0f)
return 1;
return -1;
}
static float SD_Overflow(float num, float denom) {
return SD_Sign(num) * SD_Sign(denom) * FLT_MAX;
}
float safedivf(float num, float denom) {
float abs_denom = fabsf(denom);
// If |quotient| > |num|
if (abs_denom < 1.0f) {
float abs_num = fabsf(num);
// If |num/denom| > FLT_MAX/2 --> quotient is very large or overflows
// This computation is safe from rounding and overflow.
if (abs_num > FLT_MAX / FLT_RADIX * abs_denom) {
// If |num/denom| >= FLT_MAX*2 --> overflow
// This also catches denom == 0.0
if (abs_num / FLT_RADIX >= FLT_MAX * abs_denom) {
return SD_Overflow(num, denom);
}
// At this point, quotient must be in or near range FLT_MAX/2 to FLT_MAX*2
// Scale parameters so quotient is a FLT_RADIX * FLT_RADIX factor smaller.
if (abs_num > 1.0) {
abs_num /= FLT_RADIX * FLT_RADIX;
} else {
abs_denom *= FLT_RADIX * FLT_RADIX;
}
float quotient = abs_num / abs_denom;
if (quotient > FLT_MAX / (FLT_RADIX * FLT_RADIX)) {
return SD_Overflow(num, denom);
}
}
}
return num / denom;
}
该SIGN_F()
需要考虑denum
为+0.0
或-0.0
。 @Pascal Cuoq在评论中提到的各种方法:
copysign()
或signbit()
另外,某些函数在正确实现时会区分+/-零,例如atan2f(zero, -1.0)
和sprintf(buffer, "%+f", zero)
。
注意:为简单起见,使用float
与float32_t
。
注意:也许使用fabsf()
而不是fabs()
。
次要:建议用denom
(分母)代替denum
。
为了避免出现四舍五入的情况,可以用frexp()和ldexp()按摩除数上的指数,并担心结果是否可以缩小而不会溢出。 或者使用frexp()这两个参数,并手动完成指数工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.