我的代码中有一些地方,我想确保2个任意浮点数(32位单精度)的除法不会溢出。目标/编译器不保证(显然足够)良好地处理-INF / INF和(不完全保证IEEE 754的异常值 - (可能未定义) - 并且目标可能会改变)。此外,我无法对这几个特殊地点的输入进行保存,我必须使用C90标准库。
我已阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic但说实话,我有点失落。
所以......我想问一下社区,如果下面的代码可以解决这个问题,并且有更好/更快/更高/更正/更正的方法:
#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))
。
答案 0 :(得分:3)
您可以尝试提取num和denum的指数和尾数,并确保条件:
((exp(num) - exp (denum)) > max_exp) && (mantissa(num) >= mantissa(denum))
根据输入的符号,生成相应的INF。
答案 1 :(得分:1)
在((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;
答案 2 :(得分:1)
当商在num, denom
附近时,请小心使用FLT_MAX
。
以下使用受OP启发的测试但远离FLT_MAX
附近的结果。正如@Pascal Cuoq指出的那样,舍入可能只会将结果推到边缘。相反,它使用FLT_MAX/FLT_RADIX
和FLT_MAX*FLT_RADIX
的阈值。
通过使用FLT_RADIX
进行缩放,通常为2,代码应始终获得准确的结果。在任何舍入模式下舍入都不会感染结果。
就速度而言,快乐的路径&#34;,即当结果肯定不会溢出时,应该是一个快速的计算。仍然需要进行单元测试,但评论应该提供这种方法的要点。
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()
另外,一些功能在实施良好时,可以区分+/- 0,如atan2f(zero, -1.0)
和sprintf(buffer, "%+f", zero)
。
注意:为简单起见,使用了float
与float32_t
注意:也许使用fabsf()
而不是fabs()
次要:建议denom
(分母)代替denum
。
答案 3 :(得分:0)
为了避免带有舍入的拐角情况以及什么不可以,你可以按下除数上的指数 - 使用frexp()和ldexp() - 并担心结果是否可以缩小而不会溢出。或者frexp()这两个参数,并且手工完成exponenent工作。