vs2010对浮点数的处理埋下的坑

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jewelsu/article/details/75270383

首先看一段代码

int main()
{
    int a(-4195000);
    float b;
    b=*(float *)(&a);
    int c;
    c=*(int *)(&b);
    printf("0x%04x,%d,%f,0x%04x,%d\n",a,a,b,c,c);
    getchar();
    return 0;
}

c的值与a的值是否相等呢?

当a的值为大部分数值时时相等的,但是当a是-4195000,附近的数值(这个数值是我刚好碰到的,这附近其他值也一样)时,用vs2010的默认设置编译后运行就会发现,a的值居然与c的值不一样。

如果这段代码还不太好理解的话,还有这一段代码

union ui
{
    float f;
    int i;
};
int main()
{
    ui ui1;
    ui1.i=-4195000;
    ui ui2;
    ui2.f=ui1.f;
    //float av;
   // av=ui1.f;
  //  ui2.f=av;
    printf("0x%04x,%d,0x%04x,%d,%f\n",ui1.i,ui1.i,ui2.i,ui2.i,av);
    getchar();
    return 0;
}

同样的问题。ui2.i的值时-696.

那么问题来了,为什么呢?

代码的本意是用将ui1.i赋给ui2.i,但是用的是.f的形式,理论上说,是没有问题的。但是问题出在了float类型的表示方法上。

详见附录1.float 类型。

浮点类型的单精度值具有 4 个字节,包括一个符号位、一个 8 位 excess-127 二进制指数和一个 23 位尾数。 尾数表示一个介于 1.0 和 2.0 之间的数。 由于尾数的高顺序位始终为 1,因此它不是以数字形式存储的。 此表示形式为 float 类型提供了一个大约在 3.4E–38 和 3.4E+38 之间的范围。

关键在于尾数的高顺序位始终为 1这项设定。

int型数据-4195000在内存中的二进制表示为1111 1111 1011 1111 1111 1101 0100 1000

int型数据        -696在内存中的二进制表示为1111 1111 1111 1111 1111 1101 0100 1000

二者差在了第22位上,而这一位刚好是float类型表示尾数的最高位。因此将不合法的-4195000转成了-696.

而影响这项转换的编译参数是“浮点模型”,默认为“精度”,vs的编译器就会做这项工作。而如果选择为“严格”,就不会转换,得到的数据ui2和ui1是相同的。gcc的编译器是默认不做转换的。

浮点模型选项的定义见附录2


附录1. 转自https://msdn.microsoft.com/zh-cn/library/hd7199ke.aspx

float 类型


浮点数使用 IEEE(电气和电子工程师协会)格式。 浮点类型的单精度值具有 4 个字节,包括一个符号位、一个 8 位 excess-127 二进制指数和一个 23 位尾数。 尾数表示一个介于 1.0 和 2.0 之间的数。 由于尾数的高顺序位始终为 1,因此它不是以数字形式存储的。 此表示形式为 float 类型提供了一个大约在 3.4E–38 和 3.4E+38 之间的范围。

您可根据应用程序的需求将变量声明为 float 或 double。 这两种类型之间的主要差异在于它们可表示的基数、它们需要的存储以及它们的范围。 下表显示了基数与存储需求之间的关系。

浮点类型

类型 有效位 字节数
float 6 – 7 4
double 15 – 16 8

浮点变量由尾数(包含数字的值)和指数(包含数字的数量级)表示。

下表显示了分配给每个浮点类型的尾数和指数的位数。 任何 float 或 double 的最高有效位始终是符号位。 如果符号位为 1,则将数字视为负数;否则,将数字视为正数。

指数和尾数的长度

类型 指数长度 尾数长度
float 8 位 23 位
double 11 位 52 位

由于指数是以无符号形式存储的,因此指数的偏差为其可能值的一半。 对于 float 类型,偏差为 127;对于 double 类型,偏差为 1023。 您可以通过将指数值减去偏差值来计算实际指数值。

存储为二进制分数的尾数大于或等于 1 且小于 2。 对于 float 和 double 类型,最高有效位位置的尾数中有一个隐含的前导 1,这样,尾数实际上分别为 24 和 53 位长,即使最高有效位从未存储在内存中也是如此。

浮点包可以将二进制浮点数存储为非标准化数,而不使用刚刚介绍的存储方法。“非标准化数”是带有保留指数值的非零浮点数,其中尾数的最高有效位为 0。 通过使用非标准化格式,浮点数的范围可以扩展,但会失去精度。 您无法控制浮点数以标准化形式还是非标准化形式表示;浮点包决定了表示形式。 浮点包从不使用非标准化形式,除非指数变为小于可以标准化形式表示的最小值。

下表显示了可在每种浮点类型的变量中存储的最小值和最大值。 此表中所列的值仅适用于标准化浮点数;非标准化浮点数的最小值更小。 请注意,在 80x87 寄存器中保留的数字始终以 80 位标准化形式表示;数字存储在 32 位或 64 位浮点变量(float 类型和 long 类型的变量)中时只能以非标准化形式表示。

浮点类型的范围

类型 最小值 最大值
float 1.175494351 E – 38 3.402823466 E + 38
double 2.2250738585072014 E – 308 1.7976931348623158 E + 308

如果存储比精度更重要,请考虑对浮点变量使用 float 类型。 相反,如果精度是最重要的条件,则使用 double 类型。

浮点变量可以提升为更大基数的类型(从 float 类型到 double 类型)。 当您对浮点变量执行算术时,通常会出现提升。 此算术始终以与具有最高精度的变量一样高的精度执行。 例如,请考虑下列类型声明:

float f_short;  
double f_long;  
long double f_longer;  
  
f_short = f_short * f_long;  

在前面的示例中,变量 f_short 提升到类型 double 并且与 f_long 相乘;然后,结果舍入到类型 float,然后赋给f_short

在以下示例中(使用前面示例中的声明),将以浮点(32 位)精度对变量执行算术;结果随后将提升到 double 类型:

f_longer = f_short * f_short;  


附录2.转自https://msdn.microsoft.com/zh-cn/library/e7s85ffb.aspx

/fp(指定浮点行为)

指定源代码文件中的浮点行为。

/fp:[precise | except[-] | fast | strict ]  

precise
默认值。

通过禁用可更改浮点计算精度的优化,可提高等式和不等式的浮点测试的一致性。(若要严格遵守 ANSI,则必须保持特定的精度。)默认情况下,在针对 x86 体系结构的代码中,编译器使用协处理器的 80 位寄存器保存浮点计算的中间结果。 这提高了程序速度,并减小了程序大小。 但是,由于计算涉及浮点数据类型(在内存中用少于 80 位表示),在冗长计算中进位多余精度位(80 位减去较小浮点型的位数)会产生不一致的结果。

在 x86 处理器上使用 /fp:precise 时,编译器将对 float 类型的变量执行舍入,使其达到赋值、强制转换以及将参数传递给函数时所需的准确精度。 这种舍入保证了数据不会保留任何超出其类型的容量的有效数字。 与不使用/fp:precise 编译的程序相比,使用/fp:precise 编译的程序可能更大,运行速度更慢。/fp:precise 会禁用内部函数,转而使用标准运行库例程。 有关详细信息,请参阅/Oi(生成内部函数)

以下浮点行为用 /fp:precise 实现:

  • 缩写,即,使用在末尾只进行一次舍入的复合运算来替换多个运算。

  • 不允许使用对特殊值(NaN、+infinity、-infinity、+0、-0)无效的表达式优化。 出于各种原因,优化 x-x => 0、x*0 => 0、x-0 => x、x+0 => x 和 0-x => -x 无效。(请参见 IEEE 754 和 C99 标准。)

  • 编译器会正确地处理涉及 NaN 的比较。 例如,如果 x 为 NaN 并且涉及 NaN 的有序比较引发了异常,则 x != x 的计算结果为true

  • 表达式计算在 C99 FLT_EVAL_METHOD=2 后面进行,但以下情况除外:当你为 x86 处理器进行编程时,由于 FPU 设置为 53 位精度,因此被视为长双精度。

  • 乘数正好是 1.0 的乘法将转换为使用另一乘数。 x*y*1.0 将转换为 x*y。 同样,x*1.0*y 将转换为 x*y。

  • 除数正好是 1.0 的除法将转换为使用被除数。 x*y/1.0 将转换为 x*y。 同样,x/1.0*y 将转换为 x*y。

在打开 fenv_access 时使用 /fp:precise 将禁用某些优化,如浮点表达式的编译时计算。 例如,如果使用 _control87、_controlfp、__control87_2 更改舍入模式,并且编译器执行浮点计算,则指定的舍入模式将无效,除非打开 fenv_access

/fp:precise 替换了 /Op 编译器选项。

fast
在大多数情况下,通过放松优化浮点运算的规则可创建最快的代码。 这使编译器能够优化浮点代码的速度,但会牺牲准确性和正确性。 如果指定 /fp:fast,则编译器可能在赋值语句、类型强制转换或函数调用时无法执行舍入,并且可能无法执行中间表达式的舍入。 编译器可能对运算进行重新排序或执行代数转换(例如,通过遵循关联和分布式规则)而不考虑对有限的精度结果的效果。 编译器可能将运算和操作数更改为单精度而不遵循 C++ 类型提升规则。 始终启用浮点特定缩写优化(打开fp_contract)。 始终禁用浮点异常和 FPU 环境访问(隐式使用 /fp:except- 并关闭 fenv_access)。

/fp:fast 不能与 /fp:strict/fp:precise 一起使用。 使用命令行中指定的最后一个选项。 同时指定/fp:fast/fp:except 将产生编译器错误。

指定 /Za、/Ze(禁用语言扩展)(ANSI 兼容性)和 /fp:fast 可能导致意外行为。 例如,单精度浮点运算可能不会舍入为单精度。

except[-]
可靠的浮点异常模型。 异常在触发后立即引发。 默认情况下关闭此选项。 将减号追加到此选项将显式禁用它。

strict
最严格的浮点模型。 /fp:strict 会导致关闭 fp_contract,并且打开 fenv_access/fp:except 是隐式使用的,并且可以通过显式指定 /fp:except- 来禁用。 与/fp:except- 一起使用时,/fp:strict 将强制执行严格的浮点语义,但不考虑异常事件。

在同一编译中可指定多个 /fp 选项。

要通过函数控制浮点行为,请参见浮点控制杂注。 这会重写/fp 编译器设置。 建议你将保存并还原本地浮点业行为作为良好工程做法:

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

/fp:strict/fp:except(及其相应的杂注)和 fp_contract 杂注相关的大多数浮点优化是依赖于计算机的。/fp:strict/fp:except/clr 不兼容。

/fp:precise 应能满足应用程序的大多数浮点要求。 可以使用 /fp:except/fp:strict,但性能可能会有所下降。 如果性能最重要,则要考虑是否使用/fp:fast

/fp:strict/fp:fast/fp:precise 是精度(正确性)模式。 每次仅一个有效。 如果同时指定了/fp:strict/fp:precise,编译器将使用它最后处理的那一项。 不能同时指定/fp:strict/fp:fast

有关详细信息,请参阅 Microsoft Visual C++ 浮点优化

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

  1. 打开项目的“属性页”对话框。 有关详细信息,请参见如何:打开项目属性页

  2. 展开“配置属性”节点。

  3. 展开“C/C++”节点。

  4. 选择“代码生成”属性页。

  5. 修改“浮点模型”属性。




猜你喜欢

转载自blog.csdn.net/jewelsu/article/details/75270383