我需要帮助和示例代码,以标准方式检测有符号和无符号的浮点数下溢,不使用第三方库和异常。我在谷歌上搜索并发现各种作者都在谈论“渐进下溢是否算作下溢”?有人能解释一下什么是渐进下溢吗?我需要检查这两种情况。
感谢您的时间和帮助。
我需要帮助和示例代码,以标准方式检测有符号和无符号的浮点数下溢,不使用第三方库和异常。我在谷歌上搜索并发现各种作者都在谈论“渐进下溢是否算作下溢”?有人能解释一下什么是渐进下溢吗?我需要检查这两种情况。
感谢您的时间和帮助。
IEEE-754二进制浮点数在其大部分范围内具有正则模式:包含一个符号位、一个指数和一个具有固定位数的尾数(32位float为24位,64位double为53位)。但是,在两端必须打断这种模式。在高端,超出最大指数的结果会变成无穷大。在低端,我们可以做出选择。
一种选择是,如果结果小于最低指数,则将结果舍入为零。然而,IEEE-754使用了一种称为渐近下溢的不同方案。最低指数保留给不同于常规指数所用格式的尾数。
对于普通的指数,24位尾数为“1.”,后跟23位编码为尾数字段。当数字为次正常值时,指数具有与最低常规指数相同的值,但24位尾数为“0.”,后跟23位。这是渐进下溢,因为随着数字变得越来越小,它们的精度越来越低(尾数中前导位的零越多),直到达到零。
渐进下溢具有一些良好的数学特性,尤其是a-b == 0
当且仅当a == b
。对于突然下溢,如果a和b不同,则可能会出现a-b == 0
的情况,因为a-b
太小而无法在浮点格式中表示。对于渐进下溢,对于较小的a
和b
,所有可能的a-b
值都是可表示的,因为它们只是具有最低指数的尾数之间的差异。
确定浮点下溢是否发生的另一个问题是,实现允许(由IEEE-754标准)基于舍入前或舍入后的测试报告下溢。在计算结果时,浮点实现实际上必须执行以下步骤:
该标准允许实现使用以下任一测试报告下溢:
或:
因此,两个不同的浮点实现可能会针对相同的计算返回不同的下溢报告。
(关于处理下溢的一些额外规则。上述情况会导致触发下溢异常。但是,如果未启用此异常的陷阱并且结果是精确的[舍入没有改变任何内容],则会忽略“下溢”,并且下溢标志不会被引发。如果结果不精确,则会引发下溢并触发不精确异常。)
http://www.gnu.org/software/libc/manual/html_node/FP-Exceptions.html
template <typename T>
class Real
{
public:
Real(T x) : x_(x) { }
Real& operator/=(T rhs)
{
if (x_)
{
x_ /= rhs;
if (!x_)
do whatever you want for underflow...
}
}
friend Real operator/(Real lhs, Real rhs)
{ return lhs /= rhs; }
// similar for -= etc.
private:
T x_;
}
首先,您需要在构建中启用浮点异常的生成。其次,您必须在代码中捕获它们。
我做了类似于这样的事情(使用Visual Studio)
void translateFPException( unsigned int u, EXCEPTION_POINTERS* pExp )
{
unsigned int fpuStatus = _clearfp(); // clear the exception
switch (u)
{
case STATUS_FLOAT_DENORMAL_OPERAND:
throw fe_denormal_operand(fpuStatus);
case STATUS_FLOAT_DIVIDE_BY_ZERO:
throw fe_divide_by_zero(fpuStatus);
case STATUS_FLOAT_INEXACT_RESULT:
throw fe_inexact_result(fpuStatus);
case STATUS_FLOAT_INVALID_OPERATION:
throw fe_invalid_operation(fpuStatus);
case STATUS_FLOAT_OVERFLOW:
throw fe_overflow(fpuStatus);
case STATUS_FLOAT_UNDERFLOW:
throw fe_underflow(fpuStatus);
case STATUS_FLOAT_STACK_CHECK:
throw fe_stack_check(fpuStatus);
default:
throw float_exception(fpuStatus);
};
}
void initializeFloatingPointExceptionHandling()
{
unsigned int fpControlWord = 0x00;
_clearfp(); // always call _clearfp before enabling/unmasking a FPU exception
// enabling an exception is done by clearing the respective bit in the control word
errno_t success = _controlfp_s( &fpControlWord,
0xffffffff^( _EM_INVALID
| _EM_ZERODIVIDE
| _EM_OVERFLOW
| _EM_DENORMAL
| _EM_UNDERFLOW
| _EM_INEXACT), _MCW_EM );
if (success != 0)
{
stringstream errStream;
errStream << success << " " << __FILE__ << ":" << __LINE__ << std::endl;
throw exception(errStream.str().c_str());
}
_se_translator_function old =_set_se_translator(translateFPException);
}
void enableAllFPUExceptions()
{
unsigned int oldMask = 0x00000000;
_clearfp();
// enabling is done by clearing the respective bit in the mask
_controlfp_s(&oldMask, 0x00, _MCW_EM);
}
void disableAllFPUExceptions()
{
unsigned int oldMask = 0x00000000;
_clearfp();
// disabling is done by setting the respective bit in the mask
_controlfp_s(&oldMask, 0xffffffff, _MCW_EM);
}
然后你需要编写自己的异常(这只是一个摘录,但你应该能够理解概念)
class float_exception : public exception
{
public:
float_exception(unsigned int fpuStatus);
unsigned int getStatus();
protected:
unsigned int fpuStatus;
};
class fe_denormal_operand : public float_exception
{
public:
fe_denormal_operand(unsigned int fpuStatus);
};