C 安全浮点除法
在我的代码中有一些地方我想确保2个任意浮点数(32位单精度)的除法不会溢出。目标/编译器不能保证(足够明确地)很好地处理-INF/INF,也不能完全保证IEEE 754中的异常值(可能未定义),目标可能会更改。此外,我无法对这几个特殊位置的输入进行保存假设,我必须使用C90标准库 我读过,但老实说,我有点迷路了 所以。。。我想问一下社区,下面的代码是否能起到作用,是否有更好/更快/更精确/更正确的方法:C 安全浮点除法,c,floating-point,division,divide-by-zero,c89,C,Floating Point,Division,Divide By Zero,C89,在我的代码中有一些地方我想确保2个任意浮点数(32位单精度)的除法不会溢出。目标/编译器不能保证(足够明确地)很好地处理-INF/INF,也不能完全保证IEEE 754中的异常值(可能未定义),目标可能会更改。此外,我无法对这几个特殊位置的输入进行保存假设,我必须使用C90标准库 我读过,但老实说,我有点迷路了 所以。。。我想问一下社区,下面的代码是否能起到作用,是否有更好/更快/更精确/更正确的方法: #define SIGN_F(val) ((val >= 0.0f)? 1.0f :
#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;
}
#定义符号F(val)((val>=0.0f)?1.0f:-1.0f)
float32_t safedivf(float32_t num,float32_t denum)
{
常量浮点数32_t abs_denum=fabs(denum);
如果((abs_denum<1.0f)&((abs_denum*FLT_MAX)您可以尝试提取num和denum的指数和尾数,并确保条件:
((exp(num) - exp (denum)) > max_exp) && (mantissa(num) >= mantissa(denum))
并且根据输入的符号,在((abs_denum*FLT_MAX)<(float32_t)fabs(num)
中生成相应的INF.,产品abs_denum*FLT_MAX
可能向下取整,最终等于fabs(num)
。这并不意味着num/denum
在数学上不大于FLT\u MAX
,您应该担心它可能会导致您想要避免的溢出。当商接近FLT\u MAX
时,您最好将此替换为num,denom
下面使用的测试受OP启发,但远离FLT_MAX
附近的结果。正如@Pascal Cuoq指出的,舍入可能只会将结果推到边缘。相反,它使用FLT_MAX/FLT_RADIX
和FLT_MAX*FLT_RADIX
的阈值
通过使用FLT_基数
(通常为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;
}
静态整数符号(浮点x){
如果(x>0.0f)
返回1;
如果(x<0.0f)
返回-1;
如果(atan2f(x,-1.0f)>0.0f)
返回1;
返回-1;
}
静态浮点SD_溢出(浮点数、浮点数){
返回标准符号(num)*标准符号(denom)*FLT_MAX;
}
float safedivf(float num,float denom){
浮动abs_denom=fabsf(denom);
//如果|商|>数|
如果(绝对值<1.0f){
浮动abs_num=fabsf(num);
//如果| num/denom |>FLT_MAX/2-->商非常大或溢出
//此计算不会出现舍入和溢出。
如果(abs_num>FLT_MAX/FLT_RADIX*abs_denom){
//如果| num/denom |>=FLT|u MAX*2-->溢出
//这也捕获了denom==0.0
如果(abs\u num/FLT\u基数>=FLT\u MAX*abs\u denom){
返回SD_溢出(num,denom);
}
//此时,商必须在FLT_MAX/2至FLT_MAX*2范围内或附近
//缩放参数,使商是FLT_基数*FLT_基数因子更小。
如果(绝对值>1.0){
abs_num/=FLT_基数*FLT_基数;
}否则{
abs_denom*=FLT_基数*FLT_基数;
}
浮动商=绝对值/绝对值;
if(商>FLT_MAX/(FLT_基数*FLT_基数)){
返回SD_溢出(num,denom);
}
}
}
返回num/denom;
}
<代码>符号>(或)代码>需要考虑在<代码> DuNU< <代码>是<代码> + 0 < /代码>或<代码> -0 < /代码> .评论中的@ Pascal Cuoq提到的各种方法:
copysign()
或signbit()
使用工会
另外,一些函数在得到很好的实现后,会在+/-零上进行区分,比如atan2f(zero,-1.0)
和sprintf(buffer,“%+f”,zero)
注意:为了简单起见,使用了float
vs.float32\u t
。
注意:可能使用fabsf()
而不是fabs()
小调:建议使用denom
(分母)代替denum
为了避免四舍五入的拐角情况,您可以使用frexp()和ldexp()在除数上按摩指数,并担心结果是否可以在不溢出的情况下缩小。或frexp()这两个论点,并手工完成演示工作。Goldberg的论文在第一次阅读时就失去了大多数人,对于初学者来说,这是一个可怕的资源,学习如何使用f-p数字和算术。它针对的是那些为实现f-p数字和算法而设计软件和硬件的人。你最好从一开始请求原谅比允许要好SIGN\u F(val)
无法正确提取-0.0
的符号,因此1.0/-0.0
最终将成为+FLT\u MAX
。您可能会介意,也可能不会介意。如果您不确定IEEE 754,可能也无法确定copysign
,但如果它存在,您也可以使用它。@MarkA.是的,对于=
,
,
,等等,带符号的零是不可区分的。您必须1-使用copysign
,或2-通过联合
访问表示,或3-除以它并导致您试图避免的溢出。这就剩下了解决方案2-。尝试执行除法。如果失败,则检测该失败并作出相应反应。Tryi提前预测失败与继续进行除法一样困难,事实上可能更困难。请指出,我的(更正)在哪里/如果在0.0的情况下,除了符号问题之外,代码也是有问题的?@MarkA。我只是指符号问题。Downvoter,有什么解释为什么你觉得这个答案特别糟糕吗?我也很惊讶,因为这个解决方案-加上一些-0.0符号检测器
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;
}