一个由于浮点数精度导致的错误

今天看到一篇文章:http://younglab.blog.51cto.com/416652/241886,大概是说在使用Javascript进行下面的浮点数计算时出现了问题:

obj.style.opacity =  (parseInt(obj.style.opacity *100) + 1)/100;

obj.style.opacity是一个浮点数,范围从0~1,初始值为0。这句代码每隔一小段时间执行一次,从而让目标由透明慢慢变为不透明(淡入效果)。起初,obj.style.opacity能够按照预期的每次以0.01逐步增加,但增加到0.29时就一直保持不变了。

作者只是记录了这个问题,没有写出为什么。读完这篇博客后我的第一感觉是:这又是一个由于浮点数精度所引发的问题。

下面让我们来写一个小程序重现一下这个问题。

double opacity = 0;

for (int i = 0; i < 100; i++) {
     System.out.println("opacity=" + opacity);
     System.out.println("opacity*100=" + (opacity * 100));
     System.out.println("----------------------------");

     opacity = ((int) (opacity * 100 + 1)) / 100.0;
}

程序是用Java写的,共执行100次循环,采用了与那篇文章中相同的计算方法。正常情况下opacity会由0逐步增大到1。

由于Java和JS采用的是相同的浮点数格式,所以结果是相同的。下面是程序的输出内容:

opacity=0.0
opacity*100=0.0
----------------------------
opacity=0.01
opacity*100=1.0
----------------------------
opacity=0.02
opacity*100=2.0
----------------------------
opacity=0.03
opacity*100=3.0

(中间省略……)

opacity=0.27
opacity*100=27.0
----------------------------
opacity=0.28
opacity*100=28.000000000000004
----------------------------
opacity=0.29
opacity*100=28.999999999999996
----------------------------
opacity=0.29
opacity*100=28.999999999999996

……后面一直重复相同的内容

可以发现,当opacity的值为0.29时,实际上在内存中的准确值是0.28999999999999996,因为Java在将浮点数转换为字符串时会做一些处理,让结果看起来更“美观”一些,所以会将其转成“0.29”。这一点在乘以100以后就比较明显了。

由于0.29在内存中实际保存的是0.28999999999999996,所以当乘以100变成28.999999999999996后强制转换为整数后,结果是28而不是期望的29。而这正是导致这个问题的真正原因。

需要注意的是,在这段程序中,除数必须写成100.0。这是由于在Java中有整数除法和浮点数除法两种不同的运算,如果写成100,那么被除数和除数将都是整数,Java就会按照整数除法来计算,就会导致每次计算的结果都是0。JS里没有这个问题,因为JS没有整数除法,所有除法都会当成浮点数除法来对待。

总结

只要稍有经验的程序员都知道浮点数不能直接进行相等比较,但是像这篇文章中所介绍的问题可能并不那么常见,因此有时不容易意识到发生了问题。

每个程序员都应该知道计算机中是采用近似值来保存浮点数的,当进行浮点数相关的计算时,需要时刻提防由于精度问题所导致的误差,并注意避免那些会影响到结果正确性的误差(所谓正确性,就是误差超出了所允许的最大范围)。

附:由于计算问题引起的软件灾难
http://ta.twi.tudelft.nl/users/vuik/wi211/disasters.html

时间: 2024-07-31 14:35:15

一个由于浮点数精度导致的错误的相关文章

Error-ASP.NET:由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件,导致发生错误。如果未分配 ID,请显式设置引发回发事件的控件的 ID 属性以避免此错误。

ylbtech-Error-ASP.NET:由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件,导致发生错误.如果未分配 ID,请显式设置引发回发事件的控件的 ID 属性以避免此错误. 1.返回顶部 1. “/”应用程序中的服务器错误. 由于未能找到 id 为“FileUpload1$gvFiles$ctl02$lnkBtnRemoveFile”的控件或在回发后将同一 ID 分配给另一个控件

又见浮点数精度问题

今天看到一篇文章:http://younglab.blog.51cto.com/416652/241886,大概是说在使用Javascript进行下面的浮点数计算时出现了问题: obj.style.opacity =  (parseInt(obj.style.opacity *100) + 1)/100; obj.style.opacity是一个浮点数,范围从0~1,初始值为0.这句代码每隔一小段时间执行一次,从而让目标由透明慢慢变为不透明(淡入效果). 问题是,起初obj.style.opac

mingw fbx sdk /浮点数精度

接下来要做一个linux下的程序了. 下载linux version     fbx sdk tar zxvf ...gz 按照安装说明 提升权限并没什么用 还是,cannot execute binary file 感觉是版本的问题,也就是说我要用f extension bx sdk这个版本 是dll的 vs跑完用mingw windows + vs2013用的肯定是 febx sdk windows version mingw 下面,据说那只是gcc而不意味着 linux所以...也许还是要

一个粗心的Bug,JSON格式不规范导致AJAX错误

一.事件回放  今天工作时碰到了一个奇怪的问题,这个问题很早很早以前也碰到过,不过没想到过这么久了竟然又栽在这里. 当时正在联调一个项目,由于后端没有提供数据接口,于是我直接本地建立了一个 json 文件,然后把配置的URL指向这个json文件,文件内容大概如下 : // account.json { success: true, data: [{ id: "1", name: "张XX", job: "员工", type: 1 }] } 嗯,一

[网络课摘抄]4.演示一个导致ora-01555错误的场景

1创建一个undo表空间 2查看当前undo配置 3更该默认undo表空间 4确认更改的配置 5创建一张测试表 6模拟批量操作 7 查询2分钟前的数据 从这里可以到当查询2分钟前的数据时候,系统报出ORA-01555错误,提示我们快照太老.导致这种错误出现的原因是因为由于设置的回滚段太小且回滚段循环使用,在查询之前某个时刻数据状态时需要从回滚段中勾勒出数据的前映像,由于保存数据前映像的回滚段已经被覆盖,导致查询无法实现一致性读,查询失败,报出ORA-01555错误.

一个多线程问题引发的血案-(代码段执行完毕,子进程未执行完毕导致段错误)

今天遇到一个问题,gdb执行程序完全没有问题,但直接执行就会段错误,百思不得其解,各种纠结,各种搜索引擎都试了一遍,无果!后来问题还是被我自己挖出来了. 看下边一段代码: int TaskSendControl() { pthread_t prov_thread[CLIENT_NUM]; int prov[CLIENT_NUM]; for(int i=0; i< CLIENT_NUM; i++) { prov[i] = i; if( pthread_create(&prov_thread[i

ACM中的浮点数精度处理

在ACM中,精度问题非常常见.其中计算几何头疼的地方一般在于代码量大和精度问题,代码量问题只要平时注意积累模板一般就不成问题了.精度问题则不好说,有时候一个精度问题就可能成为一道题的瓶颈,让你debug半天都找不到错误出在哪. 1.浮点数为啥会有精度问题: 浮点数(以C/C++为准),一般用的较多的是float, double. 占字节数 数值范围 十进制精度位数 float 4 -3.4e-38-3.4e38 6~7 double 8 -1.7e-308-1.7e308 14~15 如果内存不

BigDecimal 小数 浮点数 精度 财务计算

最常用法示例 构造方法 //测试构造方法 System.out.println("double类型的1.22:" + new BigDecimal(1.22));//1.2199999999999999733546474089962430298328399658203125 System.out.println("String类型的1.22: " + new BigDecimal("1.22"));//1.22 System.out.printl

stm32f407 官方ucos-iii 不支持FPU 导致haltfault错误的处理办法

由于官方提供的μCOS-III移植工程中对于浮点寄存器的入栈和出栈处理是错误的,所以网上就流传了 各种修正版本.但是这些修正的代码只能在 MDK4.7 以下版本中可以正常的运行,MDK4.7 及其以上的版 本无法正常运行.下面针对高版本的MDK进行修正处理 处理方法如下: 为了解决 FPU 的问题,有两个函数需要修改:一个是 CPU_STK  *OSTaskStkInit(),另一个是 PendSV 中断. 最后需要在工程选项中开启FPU的支持 修改函数CPU_STK  *OSTaskStkIn