/fp(指定浮点行为)

指定编译器如何处理浮点表达式、优化和异常。 /fp 选项指定生成的代码是否允许浮点环境更改舍入模式、异常屏蔽和次正规行为,以及浮点状态检查是否返回当前准确的结果。 它控制编译器是否生成可保持源运算和表达式顺序的代码,并符合 NaN 传播标准。 或者,它是否改为生成更高效的代码,可以重新排序或组合运算并使用 IEEE-754 标准不允许的简化代数变换。

语法

/fp:contract
]$
/fp:fast
/fp:precise
/fp:strict

]$
/fp:fast
/fp:precise
/fp:strict

自变量

/fp:contract

指定 /fp:precise/fp:except 选项时,/fp:contract 选项允许编译器生成浮点收缩。 收缩是一种计算机指令,它组合了浮点运算,例如混合乘加运算 (FMA)。 FMA 被 IEEE-754 定义为基本运算,在执行加法之前不会对中间乘积进行舍入,因此结果可能与单独的乘法和加法运算不同。 由于它作为单个指令实现,因此可能比单独的指令更快。 速度是以按位精确结果为代价的,要追求速度,就无法检查中间值。

默认情况下,/fp:fast 选项会启用 /fp:contract/fp:contract 选项与 /fp:strict 不兼容。

/fp:contract 选项是 Visual Studio 2022 中的新选项。

/fp:precise

默认情况下,编译器使用 /fp:precise 行为。

/fp:precise 下,编译器在为目标计算机生成和优化对象代码时会保留浮点代码的源表达式排序和舍入属性。 在表达式求值期间,编译器会在四个特定场合舍入为源代码精度:赋值时、类型强制转换时、将浮点参数传递给函数调用时,以及当函数调用返回浮点值时。 可以按计算机精度执行中间计算。 类型强制转换可用于显式舍入中间计算。

编译器不会对浮点表达式执行代数变换,例如重新关联或分布,除非它可以保证变换生成按位相同结果。 根据 IEEE-754 规范处理涉及特殊值(NaN、+infinity、-infinity、-0.0)的表达式。 例如,如果 x 为 NaN,则 x != x 求值为 true。 默认情况下,在 /fp:precise 下不会生成浮点收缩。 这是 Visual Studio 2022 中的新行为。 默认情况下,以前的编译器版本可以在 /fp:precise 下生成收缩。

编译器不会对浮点表达式执行代数变换,例如重新关联或分布,除非它可以保证变换生成按位相同结果。 根据 IEEE-754 规范处理涉及特殊值(NaN、+infinity、-infinity、-0.0)的表达式。 例如,如果 x 为 NaN,则 x != x 求值为 true。 在 /fp:precise 下可以生成浮点收缩。

编译器生成可在默认浮点环境中运行的代码。 它还假定在运行时不会访问或修改浮点环境。 也就是说,它假定代码:将浮点异常保持屏蔽状态,不读取或写入浮点状态寄存器,且不更改舍入模式。

如果浮点代码不依赖于浮点语句中的运算顺序和表达式(例如,如果你不关心 a * b + a * c 是否计算为 (b + c) * a,或者 2 * a 是否计算为 a + a),请考虑 /fp:fast 选项,它可以生成更快、更高效的代码。 如果代码既依赖于运算和表达式的顺序,又访问或更改浮点环境(例如,更改舍入模式或捕获浮点异常),请使用 /fp:strict

/fp:strict

/fp:strict 的行为类似于 /fp:precise,即编译器在为目标计算机生成和优化对象代码时保留浮点代码的源代码排序和舍入属性,并在处理特殊值时遵循标准。 程序还可以在运行时安全访问或修改浮点环境。

/fp:strict 下,编译器将生成允许程序安全取消屏蔽浮点异常、读取或写入浮点状态寄存器或更改舍入模式的代码。 在表达式求值期间,编译器会在四个特定场合舍入为源代码精度:赋值时、类型强制转换时、将浮点参数传递给函数调用时,以及当函数调用返回浮点值时。 可以按计算机精度执行中间计算。 类型强制转换可用于显式舍入中间计算。 编译器不会对浮点表达式执行代数变换,例如重新关联或分布,除非它可以保证变换生成按位相同结果。 根据 IEEE-754 规范处理涉及特殊值(NaN、+infinity、-infinity、-0.0)的表达式。 例如,如果 x 为 NaN,则 x != x 求值为 true。 在 /fp:strict 下不会生成浮点收缩。

/fp:strict 的计算开销比 /fp:precise 更高,因为编译器必须插入额外的指令才能捕获异常并允许程序在运行时访问或修改浮点环境。 如果代码不使用此功能,但需要进行源代码排序和舍入,或依赖于特殊值,请使用 /fp:precise。 否则,请考虑使用 /fp:fast,它可以生成更快更小的代码。

/fp:fast

/fp:fast 选项允许编译器重新排序、组合或简化浮点运算,以优化浮点代码的速度和空间。 编译器可以在赋值语句、类型强制转换或函数调用中省略舍入。 它可以重新排序运算或执行代数变换(例如,通过使用关联和分配律)。 即使这种变换会导致明显不同的舍入行为,它也可以重新排序代码。 由于这种增强的优化,某些浮点计算的结果可能与其他 /fp 选项生成的结果不同。 特殊值(NaN、+infinity、-infinity、-0.0)可能不严格按照 IEEE-754 标准传播或运行。 在 /fp:fast 下可以生成浮点收缩。 在 /fp:fast 下编译器仍受基础体系结构的约束,可以通过 /arch 选项来使用其他优化。

/fp:fast 下,编译器生成可在默认浮点环境中运行的代码,并假定在运行时不会访问或修改浮点环境。 也就是说,它假定代码:将浮点异常保持屏蔽状态,不读取或写入浮点状态寄存器,且不更改舍入模式。

/fp:fast 适用于不需要对浮点表达式严格进行源代码排序和舍入的程序,并且不依赖于处理特殊值(例如 NaN)的标准规则。 如果浮点代码需要保留源代码排序和舍入,或者依赖于特殊值的标准行为,请使用 /fp:precise。 如果代码访问或修改浮点环境以更改舍入模式、取消屏蔽浮点异常或检查浮点状态,请使用 /fp:strict

/fp:except

/fp:except 选项生成代码以确保在发生浮点异常的确切位置引发任何未屏蔽的浮点异常,并且不引发其他浮点异常。 默认情况下,/fp:strict 选项会启用 /fp:except,而 /fp:precise 则不会。 /fp:except 选项与 /fp:fast 不兼容。 可以使用 /fp:except- 显式禁用该选项。

/fp:except 本身不会启用任何浮点异常。 但是,程序需要启用浮点异常。 有关如何启用浮点异常的详细信息,请参阅 _controlfp

备注

可以在同一编译器命令行中指定多个 /fp 选项。 每次只能有 /fp:strict/fp:fast/fp:precise 选项中的一个生效。 如果在命令行中指定上述多个选项,则最后一个选项优先,且编译器会生成警告。 /fp:strict/fp:except 选项与 /clr 不兼容。

/Za(ANSI 兼容性)选项与 /fp 不兼容。

使用编译器指令控制浮点行为

编译器提供三个 pragma 指令来重写命令行中指定的浮点行为:float_controlfenv_accessfp_contract。 可以使用这些指令在函数级别(而不是函数内)控制浮点行为。 这些指令不直接对应于 /fp 选项。 下表显示了 /fp 选项和 pragma 指令如何相互映射。 有关详细信息,请参阅各个选项和 pragma 指令的文档。

选项 float_control(precise, *) float_control(except, *) fenv_access(*) fp_contract(*)
/fp:fast off off off on
/fp:precise on off off off*
/fp:strict on on on off

* 在 Visual Studio 2022 之前的 Visual Studio 版本中,/fp:precise 行为默认为 fp_contract(on)

选项 float_control(precise, *) float_control(except, *) fenv_access(*) fp_contract(*)
/fp:fast off off off on
/fp:precise on off off on*
/fp:strict on on on off

* 在从 Visual Studio 2022 开始的 Visual Studio 版本中,/fp:precise 行为默认为 fp_contract(off)

默认浮点环境

初始化进程时,会设置默认浮点环境。 此环境屏蔽所有浮点异常,将舍入模式设置为舍入到最接近的值 (FE_TONEAREST),保留次正规(非正规)值,对 floatdoublelong double 值使用默认有效数字(尾数)精度,并且在受支持的情况下,将无穷大控制设置为默认仿射模式。

浮点环境访问和修改

Microsoft Visual C++ 运行时提供了多个函数来访问和修改浮点环境。 这包括 _controlfp_clearfp_statusfp 及其变体。 为确保代码访问或修改浮点环境时程序行为正确,必须使用 /fp:strict 选项或 fenv_access pragma 启用 fenv_access,使这些函数生效。 如果未启用 fenv_access,访问或修改浮点环境可能会导致意外的程序行为:

  • 代码可能不遵循对浮点环境请求的更改;

  • 浮点状态寄存器可能不报告预期或当前结果;

  • 可能发生意外的浮点异常,或者可能不发生预期的浮点异常。

当代码访问或修改浮点环境时,将启用了 fenv_access 的代码与未启用 fenv_access 的代码相结合时必须小心。 在未启用 fenv_access 的代码中,编译器假定平台默认浮点环境有效。 它还假定不会访问或修改浮点状态。 在将控制权转移到未启用 fenv_access 的函数之前,我们建议保存本地浮点环境并将其还原为默认状态。 此示例演示如何设置和还原 float_control pragma:

#pragma float_control(precise, on, push)
// Code that uses /fp:strict mode
#pragma float_control(pop)

浮点舍入模式

/fp:precise/fp:fast 下,编译器会生成可在默认浮点环境中运行的代码。 它假定在运行时不会访问或修改环境。 也就是说,编译器假定代码永远不会取消屏蔽浮点异常、读取或写入浮点状态寄存器或更改舍入模式。 但是,某些程序需要更改浮点环境。 例如,此示例通过更改浮点舍入模式来计算浮点乘法的误差边界:

// fp_error_bounds.cpp
#include <iostream>
#include <limits>
using namespace std;

int main(void)
{
    float a = std::<float>::max();
    float b = -1.1;
    float cLower = 0.0;
    float cUpper = 0.0;
    unsigned int control_word = 0;
    int err = 0;

    // compute lower error bound.
    // set rounding mode to -infinity.
    err = _controlfp_s(&control_word, _RC_DOWN, _MCW_RC);
    if (err)
    {
        cout << "_controlfp_s(&control_word, _RC_DOWN, _MCW_RC) failed with error:" << err << endl;
    }  
    cLower = a * b;

    // compute upper error bound.
    // set rounding mode to +infinity.
    err = _controlfp_s(&control_word, _RC_UP, _MCW_RC);
    if (err)
    {
        cout << "_controlfp_s(&control_word, _RC_UP, _MCW_RC) failed with error:" << err << endl;
    }
    cUpper = a * b;

    // restore default rounding mode.
    err = _controlfp_s(&control_word, _CW_DEFAULT, _MCW_RC);
    if (err)
    {
        cout << "_controlfp_s(&control_word, _CW_DEFAULT, _MCW_RC) failed with error:" << err << endl;
    }
    // display error bounds.
    cout << "cLower = " << cLower << endl;
    cout << "cUpper = " << cUpper << endl;
    return 0;
}

由于在 /fp:fast/fp:precise 下编译器采用默认浮点环境,因此可以随意忽略对 _controlfp_s 的调用。 例如,对 x86 体系结构使用 /O2/fp:precise 进行编译时,不会计算边界,示例程序将输出:

cLower = -inf
cUpper = -inf

对 x86 体系结构使用 /O2/fp:strict 进行编译时,示例程序将输出:

cLower = -inf
cUpper = -3.40282e+38

浮点特殊值

/fp:precise/fp:strict 下,涉及特殊值(NaN、+infinity、-infinity、-0.0)的表达式的行为符合 IEEE-754 规范。 在 /fp:fast 下,这些特殊值的行为可能与 IEEE-754 不一致。

此示例演示特殊值在 /fp:precise/fp:strict/fp:fast 下的不同行为:

// fp_special_values.cpp
#include <stdio.h>
#include <cmath>

float gf0 = -0.0;

int main()
{
    float f1 = INFINITY;
    float f2 = NAN;
    float f3 = -INFINITY;
    bool a, b;
    float c, d, e;
    a = (f1 == f1);
    b = (f2 == f2);
    c = (f1 - f1);
    d = (f2 - f2);
    e = (gf0 / f3);
    printf("INFINITY == INFINITY : %d\n", a);
    printf("NAN == NAN           : %d\n", b);
    printf("INFINITY - INFINITY  : %f\n", c);
    printf("NAN - NAN            : %f\n", d);
    printf("std::signbit(-0.0/-INFINITY): %d\n", std::signbit(e));
    return 0;
}

对 x86 体系结构使用 /O2 /fp:precise/O2 /fp:strict 进行编译时,输出与 IEEE-754 规范一致:

INFINITY == INFINITY : 1
NAN == NAN           : 0
INFINITY - INFINITY  : -nan(ind)
NAN - NAN            : nan
std::signbit(-0.0/-INFINITY): 0

对 x86 体系结构使用 /O2 /fp:fast ** 进行编译时,输出与 IEEE-754 不一致:

INFINITY == INFINITY : 1
NAN == NAN           : 1
INFINITY - INFINITY  : 0.000000
NAN - NAN            : 0.000000
std::signbit(-0.0/-INFINITY): 0

浮点代数变换

/fp:precise/fp:strict 下,编译器不执行任何数学变换,除非保证变换生成按位相同结果。 在 /fp:fast 下,编译器可能执行这种变换。 例如,在 /fp:fast 下,示例函数 algebraic_transformation 中的表达式 a * b + a * c 可编译为 a * (b + c)。 在 /fp:precise/fp:strict 下不会执行这种变换,编译器会生成 a * b + a * c

float algebraic_transformation (float a, float b, float c)
{
    return a * b + a * c;
}

浮点显式强制转换点

/fp:precise/fp:strict 下,在表达式求值期间,编译器会在四个特定场合舍入为源代码精度:赋值时、类型强制转换时、将浮点参数传递给函数调用时,以及当函数调用返回浮点值时。 类型强制转换可用于显式舍入中间计算。 在 /fp:fast 下,编译器不会在这些位置生成显式强制转换以保证源代码的精度。 此示例演示了不同 /fp 选项下的行为:

float casting(float a, float b)
{
    return 5.0*((double)(a+b));
}

使用 /O2 /fp:precise/O2 /fp:strict 进行编译时,可以看到显式类型强制转换插入在为 x64 体系结构生成的代码中的类型强制转换和函数返回点处:

        addss    xmm0, xmm1
        cvtss2sd xmm0, xmm0
        mulsd    xmm0, QWORD PTR __real@4014000000000000
        cvtsd2ss xmm0, xmm0
        ret      0

/O2 /fp:fast 下,生成的代码已简化,因为所有类型强制转换都已优化掉:

        addss    xmm0, xmm1
        mulss    xmm0, DWORD PTR __real@40a00000
        ret      0

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性页” 对话框。 有关详细信息,请参阅在 Visual Studio 中设置 C++ 编译器和生成属性

  2. 选择“配置属性”>“C/C++”>“代码生成”属性页面

  3. 修改“浮点模型”属性

以编程方式设置此编译器选项

另请参阅

MSVC 编译器选项
MSVC 编译器命令行语法