程序员是如何神不知鬼不觉的弄丢银行1分钱的? ...

原文链接:https://bbs.51cto.com/thread-1568463-1.html

前段时间和某银行合作共同开发了适合我们的一套支付系统。最近,我们对账发现某些订单始终都对不齐。银行的下单金额与对账金额始终少了1分钱。
这就奇怪了,如果这种异常订单一多就是少了很多钱。在涉及钱的金融领域,这是个很谨慎严肃的问题。
我们跟银行排查发现了问题的原因,也就是我今天想聊的关于技术上的东西:double精度的丢失问题。
1问题复现
我们先举个简单的例子

  • double result = 1.0 - 0.9;

这段代码中result等于多少?0.1么?如果执行代码的话,分分钟打脸。

double精度丢失问题

2背后原理
无论是我们本文提到的double,还是float,都是浮点数。
在计算机科学中,浮点(英语:floating point,缩写为FP)是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。
计算机使用浮点数运算的主因,在于计算机使用二进位制的运算。
例如:4÷2=2,4=100(二进制)、2=010(二进制)。在二进制中除以2相当于退一位数。
那么1.0÷2=0.5=0.1(二进制)也就是1/2,依此类推二进制的0.01(二进制)就是十进制 1/(2^2) = 1/4 = 0.25。
上面看到的1、0.5、0.25那都是可以转换成二进制的小数,如十进制的0.1,就无法用二进制准确的表示出来。因此只能使用近似值的方式表达。
比如,我们尝试着把10进制的0.1转化成二进制试试,步骤如下:

  • 0.1*2=0.2……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.2”接着计算。
  • 0.2*2=0.4……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.4”接着计算。
  • 0.4*2=0.8……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.8”接着计算。
  • 0.8*2=1.6……1——整数部分为“1”。整数部分“1”清零后为“0”,用“0.6”接着计算。
  • 0.6*2=1.2……1——整数部分为“1”。整数部分“1”清零后为“0”,用“0.2”接着计算。
  • 0.2*2=0.4……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.4”接着计算。
  • 0.4*2=0.8……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.8”接着计算。
  • 0.8*2=1.6……1——整数部分为“1”。整数部分“1”清零后为“0”,用“0.6”接着计算。
  • 0.6*2=1.2……1——整数部分为“1”。整数部分“1”清零后为“0”,用“0.2”接着计算。
  • 0.2*2=0.4……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.4”接着计算。
  • 0.4*2=0.8……0——整数部分为“0”。整数部分“0”清零后为“0”,用“0.2”接着计算。
  • 0.8*2=1.6……1——整数部分为“1”。整数部分“1”清零后为“0”,用“0.2”接着计算。
  • ……

可以发现,这个过程是除不尽的,除出了一个无限循环小数:二进制的 0.0001100110011…
那么,如何在计算机中表示这个无限不循环的小数呢?只能考虑按照不同的精度保理不同的位数。
我们知道float是单精度的,double是双精度的。不同的精度,其实就是保留的有效数字位数不同,保留的位数越多,精度越高。
所以,浮点数在Java中是无法精确表示的,因为大部分浮点数转换成二进制是一个无限不循环的小数,只能通过保留精度的方式进行近似表示。
在《阿里巴巴Java开发手册》中其实也有着明确的规定,说明了小数类型禁止使用float或者double来表示。(虽然这条是Mysql相关规则,但是Java代码同样适用。)

3问题引申
我们现在基本已经知道了double的精度问题是什么问题。
在实际的订单交易过程中,出现这个问题的更多场景是金额单位元与分的转换。银行给你的单位是元,你自己的运算是分;前端输入是元,计算是分等等。
举个例子:用户下了一笔64.6元的订单,你在需要转换成分。如果直接除以100,你会发现计算出来的分始终是6459,少1分钱。

金额丢失1分问题

4解决方式
1)使用BigDecimal
为了解决这种浮点小数进度丢失问题,java提供了一种计算方式BigDecimal。

BigDecimal

这样就可以了么? 不是,这样能解决大部分问题,假如其他系统或语言不支持BigDecimal呢。当我们无法解决这个问题的时候,我们需要做的是想办法规避这个问题带来的影响。
2)以分为单位,Long为数据结构存储
目前我们某些核心系统在金额传输的过程和存储中还是以元存储浮点数。导致低于10元的订单计算利息费率的时候,无法计算清楚,使得我们的业务服务在处理这些问题头疼死了。
整数与整数的计算,就没有这些精度丢失问题。Long取值范围(9223372036854775807)完全够用。
3)除不尽怎么办
对于除法,始终会产生除不尽的情况怎么办?有个词叫轧差
什么意思呢?举个简单例子。假如现在需要把10元分成3分,如果是10除以3这么除,会发现为3.33333无穷尽的3。这些数字完全无法在程序或数据库中进行精确的存储。
简单理解就是,当除不尽或需去除小数点的时候,前面的n-1笔(这里n=3)做四舍五入。最后一笔做兜底(总金额减去前面n-1笔之和)。这样保证总金额的不会丢失。
这里我们的具体应用场景是用户使用了现金券,然后有部分退款,计算应退本金的问题。现金券的处理又是一大篇文章,这里以后有机会再介绍。
5总结能用Long不用浮点数存储。
前后端传输金额(元)的时候,请使用字符串,不要使用浮点数。
浮点数运算请使用BigDecimal。
实在无法除尽,可以考虑通过轧差的方式解决。
double精度不是坑,是个容易忽视的巨坑,小小经验希望对大家有所帮助,谨慎对待。>-<
关于银行1分差的问题,等待银行修复,历史订单做调账处理,哈哈哈哈哈。

原文地址:https://www.cnblogs.com/superduan/p/11527700.html

时间: 2024-08-02 03:27:32

程序员是如何神不知鬼不觉的弄丢银行1分钱的? ...的相关文章

未来,什么样的程序员才是不可替代的?

一个足球评论员可能并不会踢足球,却并不妨碍在解说比赛时对某某球星的技艺评头论足.同样我也绝不敢以高明的程序员自居,而只是以类似足球评论员的角度来阐述我对程序员的理解.这样,大家也许就不以我为鄙薄狂妄了.这是我必须首先声明的. 什么是程序员按照Wikipedia的定义,程序员又称为计算机程序员(Computer Programmer).开发者(Developer).编码者(Coder)或计算机工程师(Computer Engineer),和网络上广泛流传的码农或程序猿同义.我无意于也不能够为程序员

北京尚学堂带你“重新”认识程序员

谈谈程序员 什么是程序员 程序员又称为计算机程序员(Computer Programmer).开发者(Developer).编码者(Coder)或计算机工程师(Computer Engineer),和网络上广泛流传的码农或程序猿同义. 程序员是彻头彻尾的脑力工作者(Mind Worker),怠于思考者绝对不能成为好的程序员.有鉴于此,类Programmer天生的就应该是Thinker的子类.就程序员所使用的思考技巧而言,Thinker的具体内涵包括逻辑(Logic)和数学(Mathematics

未来战士--程序员

未来战士?那不是警察吗.No,你如果这样想就错了,真正的未来战士是程序员.我相信这一刻你会笑的很放肆,但是请相信这将会是一个不可改变的事实.虽然在这个特殊的时代,程序猿的在人们心中好感度较低.但是请你放开你的思维去想象未来,我们未来会是怎样的呢?这时你又有疑问了,未来?你连自己的未来也无法预知,你又怎么可能知道未来是什么样子的呢?对不起,你又错了,每一个新时代的产生,必然是在旧时代的推动下产生的.现在你想象下,如今我们生活在一个科技横行的快餐时代,处处都散发出科技两个字的味道.但是我们所处的时代

读“程序员生存定律”笔记

在这个最无聊的国庆里,读了上面这本书,也算收获.建议,感兴趣的工程师,程序猿一读.作者从现实的角度,系统的思考了我们职业和技术发展的路径和出路,在技术书泛滥的当下,有点清新,就是我们学技术,是干什么的,我们将去往何处,路径在哪? http://blog.csdn.net/leezy_2000/article/details/39210881 摘录一些,同感的话: 增值.读书与大局观 单纯从达成某一目的而言,读书往往非是绝对必要条件. 秦始皇把书一把火烧了,刘邦项羽一样造反并取得胜利.但读书无疑的

为什么国外程序员爱用 Mac?

from http://www.vpsee.com/2009/06/why-programmers-love-mac/ Mac 在国外很受欢迎,尤其是在 设计/web开发/IT 人员圈子里.普通用户喜欢 Mac 可以理解,毕竟 Mac 设计美观,简单好用,没有病毒.那么为什么专业人士也对 Mac 情有独钟呢?从个人使用经验来看我想有下面几个原因: 1.Mac OS X 是基于 Unix 的.这一点太重要了,尤其是对开发人员,至少对于我来说很重要,这意味着Unix 下一堆好用的工具都可以随手捡到.

聊聊程序员如何学习英语单词:写了一个记单词的小程序

背景: 关于英文对程序员的重要性,就不多说了! 英语的学习,有很多,今天也不聊多,只聊英语单词! 关于单词的记忆,找过很多方法,下载过很多软件. 如图(其它不好用的都卸载了): 上图算是我以前用过软件,注意,是以前哦~~~ 意思就是没有坚持下来~~~~ 随时间的推移,最后它们还是被我遗忘了~~~ 为什么???不能:坚持!坚持!坚持! 学习思考: 一直在找方法: 1:下载过联想记忆法.背文章记单词,词根,各种视频~~~ 2:连单词的数据库都网上下载了一份了,期望从数据库的直接记忆单词快些~~~ 通

我不是个内向的程序员,我只是很忙(转)

经在这待了好一阵了. 没错,现在我记起来了.这是公司同事的聚会,是Dan邀请我来的.是有人过40岁生日,也许是50.我记不清了.Dan是个不错的人,但我们的性格大相径庭,他是个话痨.估计他以前是卖保险的. 还好,在这里,我不需要穿西装.而且也不需要穿的像电梯旁边的那个傻冒那样.真的,为什么要在Party上穿连帽衣呢? 不过,谁会在意呢.他已经按了电梯,所以他很可能打算出去.哦,不,老弟,别在用iPhone看Facebook了,电梯门就要关了.灯灭了,你错过了这趟.哈哈,你就是个傻冒,你- 哇噢.

程序员的 升级 ,价值观的改变

韩梦飞沙  韩亚飞  [email protected]  yue31313  han_meng_fei_sha 第1章 为何这本书与你先前读过的任何书籍都迥然不同 高亮 [页面 19]: "优秀 的软件开发人员",是那些能够把控自己的职业生涯.达成目标.享受生活的人 高亮 [页面 19]: 一名软件开发人员如何超越编写代码本身? 高亮 [页面 19]: 如果你想真正成为一个更好的软件开发人员(或者其他真正优秀的人 才),你需要把重点放在整个"人"上,而不只是你生活

JAVA程序员必看的15本书-JAVA自学书籍推荐

作为Java程序员来说,最痛苦的事情莫过于可以选择的范围太广,可以读的书太多,往往容易无所适从.我想就我自己读过的技术书籍中挑选出来一些,按照学习的先后顺序,推荐给大家,特别是那些想不断提高自己技术水平的Java程序员们.此外,大家可以加入457036818交流群,互相分享一下关于JAVA方面的知识.一.Java编程入门类 对于没有Java编程经验的程序员要入门,随便读什么入门书籍都一样,这个阶段需要你快速的掌握Java基础语法和基本用法,宗旨就是"囫囵吞枣不求甚解",先对Java熟悉