我有两个数字:
FL_64 variable_number;
FL_64 constant_number;
常数始终是相同的,例如:
constant_number=(FL_64)0.0000176019966602325;
变量号已给我,我需要进行除法:
FL_64 result = variable_number/constant_number;
为了确保操作在执行之前不会上溢/下溢,我需要对 variable_number 做哪些检查?
编辑:FL_64只是double的typedef,所以FL_64 = double。
答案 0 :(得分:4)
假设:
为简单起见,下面的测试和证明是根据上述假设编写的,但一般情况很容易处理:
fabs(divisor)
时,请使用divisor
代替limit
。isinf(candidate)
)。 (如果除数的大小超过1,则除法可能会下溢。此答案未讨论在这种情况下的下溢测试。)关于符号的说明:使用非代码格式运算符的表达式,例如x
•y
,可以表示精确的数学表达式,而无需浮点舍入。代码格式的表达式,例如x*y
表示使用浮点舍入的计算结果。
要检测除以divisor
时的溢出,可以使用:
FL_64 limit = DBL_MAX * divisor;
if (-limit <= candidate && candidate <= limit)
// Overflow will not occur.
else
// Overflow will occur or candidate or divisor is NaN.
证明:
limit
等于DBL_MAX
乘以divisor
并四舍五入到最接近的可表示值。对于某些错误 e ,恰好是DBL_MAX
•divisor
•(1+ e ),使得−2 −53 ≤ e ≤2 −53 ,根据四舍五入到最接近的属性,再加上divisor
无可表示的值乘以{{1 },产生一个低于正常范围的值。 (在亚正常范围内,由于四舍五入引起的相对误差可能大于2 −53 。由于乘积保持在正常范围内,所以不会发生。)
但是,仅当DBL_MAX
•DBL_MAX
的确切数学值恰好介于两个可表示的值之间时,才会出现 e = 2 −53 值,因此要求它具有54个有效位(可表示值的53位有效值的最低位置的½的位是从前导位开始计数的第54 位)。我们知道divisor
的有效位数是1fffffffffffff 16 (53位)。将其乘以奇数将产生1fffffffffffff 16 (乘以1),5ffffffffffffd 16 (乘以3)和0x9ffffffffffffffb 16 (乘以5) ,以及与更大的奇数相乘时具有更高有效位的数字。请注意,5ffffffffffffd 16 具有55个有效位。这些都没有正好有54个有效位。当乘以偶数时,乘积的末尾为零,因此有效位数与乘以偶数除以2的最大幂所得的奇数相同。因此,DBL_MAX
的乘积没有正好在两个可表示值之间,因此错误 e 永远不会精确地为2 −53 。所以−2 53 < e <2 −53 。
因此,DBL_MAX
= limit
•DBL_MAX
•(1+ e ),其中 e <2 −53 。因此divisor
/ limit
是divisor
•(1+ e )。由于此结果小于DBL_MAX
的½ULP,因此它不会四舍五入到无穷大,因此它不会溢出。因此,将小于或等于DBL_MAX
的任何candidate
除以limit
不会溢出。
现在,我们将考虑超过divisor
的候选人。与上限一样,出于相同的原因, e 不能等于−2 −53 。那么最小的 e 可以是−2 −53 + 2 −105 ,因为limit
和{{1 }}最多具有106个有效位,因此从两个可表示值之间的中点起的任何增加必须至少为2 −105 的一部分。然后,如果DBL_MAX
,divisor
至少是limit < candidate
的2 -52 的一部分,因为有效位数为53位。因此candidate
•limit
•(1−2 −53 +2 −105 )•(1 + 2 −52 )<DBL_MAX
。那么divisor
/ candidate
至少是candidate
•((1-2−sup> −53 +2 −105 )•(1+ 2 −52 ),即divisor
•(1 + 2 −53 +2 −157 )。超过DBL_MAX
和指数范围不受限制时下一个可表示的值之间的中点,这是IEEE-754舍入标准的基础。因此,它四舍五入到无穷大,所以会发生溢出。
除以数量级小于一的数字当然会使数量级大,因此它永远不会下溢到零。但是,IEEE-754对下溢的定义是,在舍入之前或之后(无论是在实现之前还是之后使用),非零结果都是很小的(在低于正常范围内)。当然,将次标准数除以DBL_MAX
小于1可能会产生仍在次标准范围内的结果。但是,要做到这一点,必须先发生下溢,才能首先获得次要股利。因此,永远不会通过除以小于1的数来引入下溢。
如果确实希望测试该下溢,则可以通过将候选者与最小法线(或最大子法线)乘以DBL_MAX
进行比较,来类似于溢出测试,但我尚未进行过工作通过数值属性。
答案 1 :(得分:2)
假设FL_64
类似于double
,则可以从float.h
所以您要确保
DBL_MAX >= variable_number/constant_number
或同样
DBL_MAX * constant_number >= variable_number
在可能类似于
的代码中if (constant_number > 0.0 && constant_number < 1.0)
{
if (DBL_MAX * constant_number >= variable_number)
{
// wont overflow
}
else
{
// will overflow
}
}
else
{
// add code for other ranges of constant_number
}
但是,请注意浮点计算是不精确的,因此在某些极端情况下,上述代码可能会失败。
答案 2 :(得分:2)
我将尝试回答您提出的问题(而不是尝试回答您没有提出的另一种“如何检测无法避免的上溢或下溢”问题)。
为防止在软件设计过程中划分时出现上溢和下溢的情况,
确定分子的范围,并找到绝对值最大和最小的值
确定除数的范围,并找到最大和最小绝对值的值
确保数据类型的最大可表示值(例如FLT_MAX
)除以除数范围的最大绝对值大于分子范围的最大绝对值。 / p>
请确保数据类型的最小可表示值(例如FLT_MIN
)乘以除数范围的最小绝对值小于分子范围的最小绝对值。 / p>
请注意,对于每种可能的数据类型,可能需要重复最后几步,直到找到防止下溢和下溢的“最佳”(最小)数据类型(例如,您可能会检查float
是否满足要求最后2个步骤,发现没有,然后检查double
是否满足最后2个步骤,并发现确实需要。
还可能发现没有数据类型能够防止上溢和下溢,并且您必须限制可用于分子或除数或重新排列公式的值的范围(例如,更改{ 1}}转换为(c*a)/b
)或切换到其他表示形式(“双精度双精度”,有理数等)。
也;请注意,这可以确保(对于您范围内的所有值组合)防止上溢和下溢;但如果分子和除数的大小之间存在某种关系,则不能保证将选择最小的数据类型。举一个简单的例子,如果您要进行(c/b)*a
之类的操作,其中分子的大小取决于除数的大小,那么您将永远不会得到“除数最小的最大分子”或“除数最小的分子”。 “最大除数”的案例和较小的数据类型(无法处理不存在的案例)可能是合适的。
请注意,您也可以在每个单独的分区之前进行检查。这会在导致代码重复的同时(由于分支/检查)使性能变差(例如,在b = a*a+1; result = b/a;
可能导致上溢或下溢的情况下,提供使用double
的替代代码);并且在支持的最大类型不够大时无法工作(您最终遇到float
问题,无法以确保应该起作用的值起作用的方式解决该问题,因为通常情况下,您唯一要做的就是可以将其视为错误条件。
答案 3 :(得分:1)
我不知道您的FL_64
遵循什么标准,但是如果类似IEEE 754,您将需要提防
可能有一个特殊的NaN
值。在某些实现中,将其与任何内容进行比较的结果是0
,所以如果是(variable_number == variable_number) == 0
,那就是发生了什么。视实现而定,可能会有宏和函数对此进行检查,例如GNU C Library。
IEEE 754还支持无穷大(和负无穷大)。例如,这可能是溢出的结果。如果variable_number
是无限的,然后将其除以constant_number
,结果可能会再次变为无限。与NaN
一样,该实现通常会提供宏或函数来对此进行测试,否则,您可以尝试将数字除以某个值,然后看数字是否变小。
由于将数字除以constant_number
会使其更大,因此variable_number
可能会溢出(如果已经很大)。检查它是否不大到会发生这种情况。但是根据您的任务,可能已经排除了这么大的可能性。 IEEE 754中的64位浮点数最多达到10 ^ 308。如果您的电话号码溢出,则可能变成无穷大。
答案 4 :(得分:1)
我个人不知道FL_64变量类型,从我想它具有64位表示形式的名称来看,但是它是带符号的还是无符号的?
无论如何,只有在对类型进行签名的情况下,我才会看到潜在的问题,否则商和提醒都可以在相同数量的位上重新表示。
如果已签名,则需要检查结果符号:
FL_64 result = variable_number/constant_number;
if ((variable_number > 0 && constant_number > 0) || (variable_number < 0 && constant_number < 0)) {
if (result < 0) {
//OVER/UNDER FLOW
printf("over/under flow");
} else {
//NO OVER/UNDER FLOW
printf("no over/under flow");
}
} else {
if (result < 0) {
//NO OVER/UNDER FLOW
printf("no over/under flow");
} else {
//OVER/UNDER FLOW
printf("over/under flow");
}
}
还应检查其他情况,例如除以0。但是正如您所提到的,constant_number
始终是固定的,并且不同于0。
编辑:
好的,因此可能存在另一种使用DBL_MAX
值检查溢出的方法。通过将最大可表示数量加倍,可以将其乘以constant_number
并计算variable_number
的最大值。从下面的代码片段中,您可以看到第一种情况不会引起溢出,而第二种情况却会引起溢出(因为variable_number
比test
更大)。实际上,从控制台输出中,您可以看到第一个值result
高于第二个值,即使它实际上应该是前一个值的两倍。因此,这种情况是溢出情况。
#include <stdio.h>
#include <float.h>
typedef double FL_64;
int main() {
FL_64 constant_number = (FL_64)0.0000176019966602325;
FL_64 test = DBL_MAX * constant_number;
FL_64 variable_number = test;
FL_64 result;
printf("MAX double value:\n%f\n\n", DBL_MAX);
printf("Variable Number value:\n%f\n\n", variable_number);
printf(variable_number > test ? "Overflow case\n\n" : "No overflow\n\n");
result = variable_number / constant_number;
printf("Result: %f\n\n", variable_number);
variable_number *= 2;
printf("Variable Number value:\n%f\n\n", variable_number);
printf(variable_number > test ? "Overflow case\n\n" : "No overflow\n\n");
result = variable_number / constant_number;
printf("Result:\n%f\n\n", variable_number);
return 0;
}
这是一个特殊的案例解决方案,因为您有一个常数值。但是这种解决方案在一般情况下不起作用。