浮点运算何时“无效”?

问题描述

考虑在Xeon 15上使用msvc15和16,即Visual Studio 2017和2019):

int main()
{
    unsigned int x;
    uint8_t val;
    float f;

    x = _status87();    // x = 0 here,OK
    f = -1.00e+9;
    x = _status87();    // x = 0 here,OK
    val = uint8_t(f);   // val = 0 here,I can live with that
    x = _status87();    // x = 0 here,OK
    f = -1.00e+10;
    val = uint8_t(f);   // val = 0 here,I can live with that
    x = _status87();    // x = 16 = _EM_INVALID,wtf?
}

很明显,某些强制转换会给出“错误”的结果,即,当您要存储的数字大于某个特定类型的变量所能容纳的数字时,就无法存储该值。我的问题是-为什么浮点寄存器的状态标志设置为“无效”?我可以忍受的上溢/下溢和/或不精确,为什么是“无效”?我找不到任何特定cpu认为“无效”浮点运算的定义。我还不知道为什么在尾数9时未设置此寄存器的原因(尽管该值不适合且转换结果为0),但是在尾数10时却标记 。在我看来,在该阈值上没有通过任何相关的最大值/最小值。

(更重要的是,(对我而言),有没有一种方法可以强制我不触及浮点寄存器?原因是我正在处理的代码依赖于(稍后依赖于)寄存器未处于“无效”状态,并且我无法合理或可靠地修改该寄存器标志检查的每次使用。但是,仅重置标志也容易出错(由于其他地方的假设,“其他地方”是我无法触及的代码)。我一直在看boost :: numeric_cast,但这似乎无济于事,除非我在某处缺少什么?

但是,总的来说,任何有关“无效”浮点运算如何工作的帮助都会有所帮助。

解决方法

generated assembly中,我们可以看到cvttss2si指令用于转换。 documentation for this instruction reads

将源操作数(第二个操作数)中的单精度浮点值转换为目标中的带符号双字整数(如果操作数大小为64位,则为带符号四字整数)操作数(第一个操作数)。

由于那里使用的寄存器为eax,因此此处采用双字形式。接下来,写成:

如果转换后的结果大于最大有符号双字整数,则会引发浮点无效异常

在您的情况下,-1e9可以存储在带符号的双字中,但-1e10不能存储。然后,异常似乎只是被转换为_status87()函数读取的状态寄存器。


请注意,根据conv.fpint/1,此处的C ++标准行为为未定义

浮点类型的prvalue可以转换为整数类型的prvalue。转换被截断;即,小数部分被丢弃。 如果截断的值不能用目标类型表示,则行为不确定。

这适用于f的两个值。