浮点运算潜在的结果不一致问题

昨天阿楠发现了项目中的一个 bug ,是因为浮点运算的前后不一致导致的。明明是完全相同的 C 代码,参数也严格一致,但是计算出了不相同的结果。我对这个现象非常感兴趣,仔细研究了一下成因。

原始代码比较繁杂。在弄清楚原理后,我简化了出问题的代码,重现了这个问题:

static void
foo(float x) {
    float xx = x * 0.01f;
    printf("%d\n", (int)(x * 0.01f));
    printf("%d\n", (int)xx);
}

int
main() {
    foo(2000.0f);
    return 0;
}

使用 gcc 4.9.2 ,强制使用 x87 浮点运算编译运行,你会发现令人诧异的结果。

gcc a.c -mfpmath=387

19
20

前一次的输出是 19 ,后一次是 20 。

这是为什么呢?让我们来看看 gcc 生成的代码,我截取了相关的段落:

    flds    16(%rbp)
    flds    .LC0(%rip)
    fmulp   %st, %st(1)
    fstps   -4(%rbp)          ; 1. x * 0.01f 结果保存到内存中的 float 变量中
    flds    16(%rbp)
    flds    .LC0(%rip)
    fmulp   %st, %st(1)
    fisttpl -20(%rbp)        ; 2. x * 0.01f 结果直接转换为整型
    movl    -20(%rbp), %eax
    movl    %eax, %edx
    leaq    .LC1(%rip), %rcx
    call    printf
    flds    -4(%rbp)                 ; 3. 读出 1. 保存的乘法结果
    fisttpl -20(%rbp)
    movl    -20(%rbp), %eax
    movl    %eax, %edx
    leaq    .LC1(%rip), %rcx
    call    printf

这里我做了三行注释。

首先,0.01 是无法精确表示成 2 进制的,所以 * 0.01 这个操作一定会存在误差。

两次运算都是 x * 0.01f ,虽然按 C 语言的转换规则,表达式中都是 float 时,按 float 精度运算。但这里 gcc 生成的代码并没有严格设置 FPU 的精度控制,在注释 2 这个地方,乘法结果是直接从浮点寄存器转换为整数的。而在注释 1 这个地方,把乘法结果通过 fstps 以低精度形式保存到内存,再在注释 3 的地方 flds 读回。

所以在注释 2 和注释 3 的地方,浮点寄存器 st 内的值其实是有差别的,这导致了 fisttpl 转换为整数后结果不同。

时间: 2024-12-22 06:06:16

浮点运算潜在的结果不一致问题的相关文章

Maven学习笔记(七):聚合与继承

软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计及更高的重用性.当把Maven应用到实际项目中的时候,也需要将项目分成不同模块.Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性. <maven实战>一书实现了两个模块account-email和account-persist用以实现一个用户注册的项目. 在本文中,也使用这个例子来讲述聚合与继承. 聚合:  由于

Maven真——聚合和继承(于)

依赖管理 我们谈论继承一个dependencies因素,我们非常easy这个特性被认为是适用于accout-parent于. 子模块account-email和account-persist同一时候依赖了org.springframework:spring-core:2.5.6,spring-beans:2.5.6,spring-context:2.5.6,junit:junit:4.7.以此能够将这些公共依赖放到父模块account-parent中,两个子模块就能移除这些依赖,简化配置. 上述

java_db2错误码对应值

DB2-SQLSTATE消息 2012-08-27 10:35:27|  分类: db2|举报|字号 订阅 本节列示 SQLSTATE 及其含义.SQLSTATE 是按类代码进行分组的:对于子代码,请参阅相应的表. 表 2. SQLSTATE 类代码 类 代码 含义 要获得子代码, 参阅... 00 完全成功完成 表 3 01 警告 表 4 02 无数据 表 5 07 动态 SQL 错误 表 6 08 连接异常 表 7 09 触发操作异常 表 8 0A 功能部件不受支持 表 9 0D 目标类型规

DB2 错误代码 命令大全

SQLSTATE 消息 本节列示 SQLSTATE 及其含义.SQLSTATE 是按类代码进行分组的:对于子代码,请参阅相应的表. 表 2. SQLSTATE 类代码 类 代码 含义 要获得子代码, 参阅... 00 完全成功完成 表 3 01 警告 表 4 02 无数据 表 5 07 动态 SQL 错误 表 6 08 连接异常 表 7 09 触发操作异常 表 8 0A 功能部件不受支持 表 9 0D 目标类型规范无效 表 10 0F 无效标记 表 11 0K RESIGNAL 语句无效 表 1

第一节 理解关系型数据库

1. 概念 数据库,其实是一组结构化信息的集合.数据库设计的目的是用于管理大量的信息.它采取有组织有结构的方式存储数据,从而方便用户管理和检索需要的数据. 数据库管理系统(DBMS),是允许用户创建和维护数据库的软件程序.DBMS支持在表中以行与列的形式存储数据. 关系型数据库管理系统(RDBMS),是一种以相关表格形式存储信息的DBMS.RDBMS基于DBMS. 2. 生命周期 数据库的生命周期从概念上定义了数据库实现的完整过程. (1)需求分析:在开始和设计之前必先了解需求. (2)逻辑设计

Sqlstate解释

本篇文章主要介绍了"Sqlstate详解",主要涉及到方面的内容,对于DB2感兴趣的同学可以参考一下: 根据 X/Open 和 SQL Access Group SQL CAE 规范 (1992) 所进行的定义,SQLERROR 返回 SQLSTATE .SQLS... <divid="tip" http:="" cpro.baidustatic.com="" cpro="" ui="&qu

Maven实战——聚合与继承(中)

依赖管理 上一节我们说到可以继承dependencies元素,我们很容易想到把这一特性应用到accout-parent中.子模块account-email和account-persist同时依赖了org.springframework:spring-core:2.5.6,spring-beans:2.5.6,spring-context:2.5.6,junit:junit:4.7.以此可以将这些公共依赖放到父模块account-parent中,两个子模块就能移除这些依赖,简化配置. 上述方法时可

SQLSTATE 消息

SQLSTATE 消息本节列示 SQLSTATE 及其含义.SQLSTATE 是按类代码进行分组的:对于子代码,请参阅相应的表. 表 2. SQLSTATE 类代码 类 代码   含义 要获得子代码, 参阅... 00 完全成功完成 表 3 01 警告 表 4 02 无数据 表 5 07 动态 SQL 错误 表 6 08 连接异常 表 7 09 触发操作异常 表 8 0A 功能部件不受支持 表 9 0D 目标类型规范无效 表 10 0F 无效标记 表 11 0K RESIGNAL 语句无效 表

MySQL复制相关技术的简单总结

MySQL有很多种复制,至少从概念上来看,传统的主从复制,半同步复制,GTID复制,多线程复制,以及组复制(MGR).咋一看起来很多,各种各样的复制,其实从原理上看,各种复制的原理并无太大的异同,新的复制方式的出现,是原复制某一方面增强或者是优化的结果,就不难理解为什么有这么多中复制.每一种复制的出现都是有其原因的,也是解决(或者说是弥补)前一种的复制方案的潜在的问题的.其实搞出来这么多概念,个人觉得是源于开源的原因吧,不同复制版本的出现,因为是一个不断发现问题就解决问题的过程.如果是闭源的数据