干货细说-Javascript-中的浮点数精度丢失问题内附好课推荐

前言最近,朋友 L 问了我这样一个问题:在 chrome 中的运算结果,为什么是这样的? 0.55 * 100 // 55.000000000000010.56 * 100 // 56.000000000000010.57 * 100 // 56.999999999999990.58 * 100 // 57.999999999999990.59 * 100 // 590.60 * 100 // 60虽然我告诉他说,这是由于浮点数精度问题导致的。但他还是不太明白,为何有的结果输出整数,有的是以 ...001 的小数结尾,有的却是以 ...999 的小数结尾,跟预想中的有差异。 这其实牵涉到了计算机原理的知识,真要解释清楚什么是浮点数,恐怕得分好几个章节了。想深入了解的同学,可以前往 这篇文章 细读。今天我们仅讨论浮点数运算结果的成因,以及如何实现我们期望的结果。 浮点数与 IEEE 754在解释什么是浮点数之前,让我们先从较为简单的小数点说起。 小数点,在数制中代表一种对齐方式。比如要比较 1000 和 200 哪个比较大,该怎么做呢?必须把他们右对齐: 1000 200发现 1 比 0(前面补零)大,所以 1000 比较大。那么如果要比较 1000 和 200.01 呢?这时候就不是右对齐了,而应该是以小数点对齐: 1000 200.01小数点的位置,在进制表示中是至关重要的。位置差一位整体就要差进制倍(十进制就是十倍)。在计算机中也是这样,虽然计算机使用二进制,但在处理非整数时,也需要考虑小数点的位置问题。无法对齐小数点,就无法做加减法比较这样的操作。 接下来的一个重要概念:在计算机中的小数有两种,定点 和 浮点。 定点的意思是,小数点固定在 32 位中的某个位置,前面的是整数,后面的是小数。小数点具体固定在哪里,可以自己在程序中指定。定点数的优点是很简单,大部分运算实现起来和整数一样或者略有变化,但是缺点则是表示范围太小,精度很差,不能充分运用存储单元。 浮点数就是设计来克服这个缺点的,它相当于一个定点数加上一个阶码,阶码表示将这个定点数的小数点移动若干位。由于可以用阶码移动小数点,因此称为浮点数。我们在写程序时,用到小数的地方,用 float 类型表示,可以方便快速地对小数进行运算。 ...

April 25, 2019 · 2 min · jiezi

一个游戏拨账系统的数据库结算设计

假设现存在一个简单的猜大小游戏,由用户下注大或者小,扣除手续费3%后的钱全部放入奖池中,赢的一方按投注比例平分整个奖池。使用mysql作为数据库,系统精度精确到1位小数。 本文将会讲解其中会出现的业务结算导致的数据问题,以及解决方法。数据库逻辑设计系统内应该存在一个用户钱包表,其中指定两条记录为系统收入账户和系统拨出账户。这样可以将投注的时候,对系统账户余额增加操作,和发奖的时候,对系统账户余额的减去操作分离。可以避免上一期游戏的结算,对下一期游戏的投注发生锁等待的问题。业务加锁考虑到高并发的情况下,推荐使用mysql自带的排他锁,不推荐乐观锁,因为乐观锁需要重试机制,而队列结算暂时不考虑。 当一名用户发起投注的时候,检查顺序应该如下检查系统游戏开关(冗余) 查询一次用户余额是否大于这次下注金额开启事务对系统收入账户加排他锁对用户收入账户加排他锁检查用户余额是否足够对用户进行扣款对系统进行收款为奖池加入97%的投注额度事务提交这里之所以要冗余检查用户的额度,是否了避免开启事务的消耗,防止恶意攻击消耗系统资源,用来开启无意义事务。奖池额度的97%这里计算需要保持一位精度,如果用户投注是98,按照计算得到的值应该是95.06,我们应该取95.0而不是95.1,否则你最后存到奖池里面的数就会大于97%,这样系统抽取就不会达到3%,用户少分点没关系,要保证系统一定能分到3%。简单一句话就是:精度位后都舍弃发奖过程设计假设按照投注比例,瓜分出的奖金总数是22.1,A用户的份额是55.5%,A用户拿到12.2655,B用户的份额是45%,B用户拿到9.8345。这种情况下,你会发现,按照舍弃,原则,分别是12.2和9.8,结果是只发放了22,如果你按照四舍五入原则,才能发放到22.1那为什么还要坚持舍弃原则呢?因为,假设出一个极端情况,当你碰到A的值是12.05,B的值是9.05,按照舍弃原则,总数的确还是22.1。但是按照四舍五入原则,发放的总值就是22.2了。结语在计算机系统内,浮点数的计算本身就是不可靠的,在业务内应该用整形去避免,当设计到百分比操作的时候,请尽量使用舍弃原则,保证不多发。按照舍弃原则,给用户少发0.05这种精度外的值,对业务来说无关紧要。如果超发了,会导致系统内账目混乱,后果将不堪设想。

January 28, 2019 · 1 min · jiezi

论证PHP是世界上最好的语言(其实是浮点数问题

众所周知,计算机内的浮点存储并不是精确的,本文的目的是为了警醒各位,在业务中,遇到浮点计算,一定要慎重,尽可能的使用整形来规避。这次我们使用 1.38 * 10000这个式子来测试各个语言对于浮点数的处理。NodeJspython2python3GolangC++JavaPHP是世界上最好的语言对于金额计算,最好是使用整形来规避,比如系统内的精度设置为小数点后两位, 用户的余额 1.38 存到数据库内, 可以存成138,这样计算可以避免这个问题。但是整形也不是万能的,也有最大值,如果精度过大或者金额过大,整形也是撑不住的。

January 15, 2019 · 1 min · jiezi

js 常用计算

js 常用计算由于存在计算精度的问题,例如 0.1+0.2 = 0.30000000000000004,所以需要整理以下方法,方便进行简单计算。主要思路是先转成整数,然后再进行计算,计算完再转回浮点数获取小数位以及向右移动小数位,是计算时转换成整数的工具函数。加减乘除四个函数都用到了获取小数位// 获取小数位export function getDecimalPlace(num) { try { let result = num.toString().split(’.’)[1].length; return result; } catch (e) { return 0; }}向右移动小数位/** * 向右移动小数点 * @param movePlace 移动步数,正向右,负数向左 /export function moveDecimalPlace(num, movePlace) { let decimalPlace = getDecimalPlace(num); let step = movePlace - decimalPlace; // 先转成整数类型,再确定需要如何移动,为了处理 268.34100 却等于 26833.999999999996 的问题 let intNum = Number(num.toString().replace(’.’, ‘’)); if (step > 0) { return intNum * Math.pow(10, step); } else if (step < 0) { return intNum / Math.pow(10, -step); } else { return intNum; }}这里右移动小数点需要先转成整形,再进一步处理是因为存在一些浮点数乘以 10 的倍数也会出问题!例如: 268.34*100 => 26833.999999999996加法/** * 相加 arg1 + arg2 /export function add(arg1, arg2) { let step1 = getDecimalPlace(arg1); let step2 = getDecimalPlace(arg2); let maxStep = Math.max(step1, step2); arg1 = moveDecimalPlace(arg1, maxStep); arg2 = moveDecimalPlace(arg2, maxStep); return (arg1 + arg2) / Math.pow(10, maxStep);}减法/* * 相减 arg1 - arg2 /export function sub(arg1, arg2) { return add(arg1, -arg2);}乘法/* * 乘法 arg1 * arg2 /export function multiply(arg1, arg2) { let step1 = getDecimalPlace(arg1); let step2 = getDecimalPlace(arg2); let maxStep = Math.max(step1, step2); arg1 = moveDecimalPlace(arg1, maxStep); arg2 = moveDecimalPlace(arg2, maxStep); if (maxStep > 0) { let stepPow = Math.pow(10, maxStep); return (arg1 * arg2) / (stepPow * stepPow); } return arg1 * arg2;}除法/* * 除法 arg1 / arg2 */export function division(arg1, arg2) { let step1 = getDecimalPlace(arg1); let step2 = getDecimalPlace(arg2); let maxStep = Math.max(step1, step2); arg1 = moveDecimalPlace(arg1, maxStep); arg2 = moveDecimalPlace(arg2, maxStep); return arg1 / arg2;}其他源码链接更多代码片段个人博客链接 ...

December 20, 2018 · 2 min · jiezi