如何让 C++ signaling_nan() 抛出异常,而不需要 if 测试?
How do I get C++ signaling_nan() to throw an exception, without needing if tests?
我正在尝试使用 signaling_nan() 来自动抛出一个异常,如果一个变量的值将被设置 post-初始化,在被设置之前被使用。
我已经尝试通过 fenv.h 使用 floating point exception functionalities,但是该解决方案需要对 FE_INVALID 进行 if 测试,我想避免这种情况。
我在下面包含了简化的示例代码。在代码中,我有一个带有成员双 x 的 class A,我在构造函数中将其初始化为 signaling_nan()。 class 还包括一个函数divide_by_x,它将class 成员x 传入的参数进行除法。我创建一个对象 A a1,并调用 a1.divide_by_x(10.0)。我预计程序会抛出异常,但它会继续进行,并且 returns nan 作为 divide_by_x 函数调用的值。
在示例代码中,我在程序末尾包含了一个针对 FE_INVALID 浮点异常的 if 测试,如果 x 是用 quiet_nan 初始化的,它的输出会沿着预期的行变化() [无效] vs signaling_nan() [FE_INVALID]。这表明 signaling_nan() 确实产生了浮点异常,但程序继续执行。
#include<iostream>
#include <utility>
#include <limits>
#include <float.h>
#include <signal.h>
#include <fenv.h>
using namespace std;
class A
{
public:
// Constructor
A():x(std::numeric_limits<double>::signaling_NaN()){}
//A():x(std::numeric_limits<double>::quiet_NaN()){}
// Set x
void set_x(double x_value) { x = x_value; }
// Divide number by x
double divide_by_x(double other_number)
{
double result = other_number/x;
return result;
}
// Member
double x;
};
int main(void)
{
feclearexcept(FE_ALL_EXCEPT);
A a1;
double before_setting_x;
before_setting_x = a1.divide_by_x(10.0);
if (fetestexcept(FE_INVALID))
printf("FE_INVALID\n");
else
printf("Not invalid\n");
return 0;
}
用 signaling_nan() 初始化 x 的输出:
FE_INVALID
用 quiet_nan() 初始化 x 的输出:
不无效
所以,有很多方法可以解决这个问题:
自定义 类,在操作中检查 FPE
最简单的方法就是编写一个包装器 class,它检查浮点异常,以后再也不用处理它了,所有的数字运算都是内置的。
#include <exception>
#include <fenv.h>
class FloatingPointException: public std::exception {
const char* what() const throw() {
return "Exception raised in floating-point operation.";
}
};
class F64 {
public:
F64(double f): m_f(f) {}
F64(const F64 &other): m_f(other.m_f) {}
F64(F64 &&other): m_f(other.m_f) {}
F64& operator=(const F64& other) { m_f = other.m_f; return *this; }
F64& operator=(F64&& other) { m_f = other.m_f; return *this; }
operator double() const { return m_f; }
// This is where we use fenv.
F64 operator*(F64 other) const {
feclearexcept(FE_ALL_EXCEPT);
auto result = m_f * other.m_f;
if (fetestexcept(FE_INVALID)) {
throw FloatingPointException();
}
return F64(result);
}
F64& operator*=(F64 other) {
operator=(operator*(other));
return *this;
}
// This is where we use fenv.
F64 operator/(F64 other) const {
feclearexcept(FE_ALL_EXCEPT);
auto result = m_f / other.m_f;
if (fetestexcept(FE_INVALID)) {
throw FloatingPointException();
}
return F64(result);
}
F64& operator/=(F64 other) {
operator=(operator/(other));
return *this;
}
// Implement all other operations.
private:
double m_f;
};
feenableexcept
另一种方法是在程序开始时使用feenableexcept
:
#include <fenv.h>
int main() {
feenableexcept(FE_INVALID);
...
return 0;
}
如果发生无效浮点异常,这将产生一个 SIGFPE。您还可以使用 GCC 扩展,这样这会在 main
.
之前自动发生
#define _GNU_SOURCE 1
#include <fenv.h>
static void __attribute__ ((constructor)) trapfpe {
feenableexcept(FE_INVALID);
}
您也可以将其与信号处理程序一起使用。
定制组装
我强烈建议不要这样做,因为它不可移植(即使在 x86 上,因为 x87 FPU 与带有 SSE2 的较新的 x86 CPU 会有所不同。但是,您可以尝试使用内联汇编来检查对于浮点异常,尽管这可能是一个非常糟糕的主意。例如,处理 x87 FPU 状态寄存器的详细信息 here.
我正在尝试使用 signaling_nan() 来自动抛出一个异常,如果一个变量的值将被设置 post-初始化,在被设置之前被使用。
我已经尝试通过 fenv.h 使用 floating point exception functionalities,但是该解决方案需要对 FE_INVALID 进行 if 测试,我想避免这种情况。
我在下面包含了简化的示例代码。在代码中,我有一个带有成员双 x 的 class A,我在构造函数中将其初始化为 signaling_nan()。 class 还包括一个函数divide_by_x,它将class 成员x 传入的参数进行除法。我创建一个对象 A a1,并调用 a1.divide_by_x(10.0)。我预计程序会抛出异常,但它会继续进行,并且 returns nan 作为 divide_by_x 函数调用的值。
在示例代码中,我在程序末尾包含了一个针对 FE_INVALID 浮点异常的 if 测试,如果 x 是用 quiet_nan 初始化的,它的输出会沿着预期的行变化() [无效] vs signaling_nan() [FE_INVALID]。这表明 signaling_nan() 确实产生了浮点异常,但程序继续执行。
#include<iostream>
#include <utility>
#include <limits>
#include <float.h>
#include <signal.h>
#include <fenv.h>
using namespace std;
class A
{
public:
// Constructor
A():x(std::numeric_limits<double>::signaling_NaN()){}
//A():x(std::numeric_limits<double>::quiet_NaN()){}
// Set x
void set_x(double x_value) { x = x_value; }
// Divide number by x
double divide_by_x(double other_number)
{
double result = other_number/x;
return result;
}
// Member
double x;
};
int main(void)
{
feclearexcept(FE_ALL_EXCEPT);
A a1;
double before_setting_x;
before_setting_x = a1.divide_by_x(10.0);
if (fetestexcept(FE_INVALID))
printf("FE_INVALID\n");
else
printf("Not invalid\n");
return 0;
}
用 signaling_nan() 初始化 x 的输出:
FE_INVALID
用 quiet_nan() 初始化 x 的输出:
不无效
所以,有很多方法可以解决这个问题:
自定义 类,在操作中检查 FPE
最简单的方法就是编写一个包装器 class,它检查浮点异常,以后再也不用处理它了,所有的数字运算都是内置的。
#include <exception>
#include <fenv.h>
class FloatingPointException: public std::exception {
const char* what() const throw() {
return "Exception raised in floating-point operation.";
}
};
class F64 {
public:
F64(double f): m_f(f) {}
F64(const F64 &other): m_f(other.m_f) {}
F64(F64 &&other): m_f(other.m_f) {}
F64& operator=(const F64& other) { m_f = other.m_f; return *this; }
F64& operator=(F64&& other) { m_f = other.m_f; return *this; }
operator double() const { return m_f; }
// This is where we use fenv.
F64 operator*(F64 other) const {
feclearexcept(FE_ALL_EXCEPT);
auto result = m_f * other.m_f;
if (fetestexcept(FE_INVALID)) {
throw FloatingPointException();
}
return F64(result);
}
F64& operator*=(F64 other) {
operator=(operator*(other));
return *this;
}
// This is where we use fenv.
F64 operator/(F64 other) const {
feclearexcept(FE_ALL_EXCEPT);
auto result = m_f / other.m_f;
if (fetestexcept(FE_INVALID)) {
throw FloatingPointException();
}
return F64(result);
}
F64& operator/=(F64 other) {
operator=(operator/(other));
return *this;
}
// Implement all other operations.
private:
double m_f;
};
feenableexcept
另一种方法是在程序开始时使用feenableexcept
:
#include <fenv.h>
int main() {
feenableexcept(FE_INVALID);
...
return 0;
}
如果发生无效浮点异常,这将产生一个 SIGFPE。您还可以使用 GCC 扩展,这样这会在 main
.
#define _GNU_SOURCE 1
#include <fenv.h>
static void __attribute__ ((constructor)) trapfpe {
feenableexcept(FE_INVALID);
}
您也可以将其与信号处理程序一起使用。
定制组装
我强烈建议不要这样做,因为它不可移植(即使在 x86 上,因为 x87 FPU 与带有 SSE2 的较新的 x86 CPU 会有所不同。但是,您可以尝试使用内联汇编来检查对于浮点异常,尽管这可能是一个非常糟糕的主意。例如,处理 x87 FPU 状态寄存器的详细信息 here.