浮点数探寻(二)怎么做精确的浮点数运算

上文说到了由于很多十进制小数在计算机中用二进制表示,是无尽的。因此会有精度丢失的问题,那么我们如何解决这个问题呢

大致有两种解决途径:

一、计算机支持

二、编程语言支持

1、计算机中有一种用十进制存储数字的格式,叫做BCD码,它用四位二进制数来表示一个十进制数字,比如98,用BCD码表示为

98
10011000

 

这种亦称为8421码(4位二进制数中,第一位代表 = 8, 第二位代表 = 4,第三位代表 = 2, 第四位代表 = 1)

BCD码牺牲空间来换取了精度,但编程语言通常不是用这种方案来解决高精度预算(猜想是大部分服务器用CPU不支持BCD运算,欢迎大佬么指正),该方案常见于PLC单片机

2、编程语言支持

多数编程语言都有高精度运算库来支持浮点数计算,比如PHP的bcmath数学库,比如java的BigDecimal,简单看了下bcmath中的add和sub方法,相当于用字符串来保存浮点数,然后遍历字符串位,实现了字符串加减法。对该源码有兴趣的可以看https://github.com/php/php-src/tree/master/ext/bcmath,对字符串运算有兴趣的leetcode上也有类似的题目https://leetcode-cn.com/problems/add-strings/

需要注意的是PHP使用bcmath库一定要指定你要精确的位数(第三个参数),否则会出现

Java的BigDecimal具体的实现没有看,但猜想也是类似字符串处理相加。因为Java在使用BigDecimal方法时,必须传入字符串类型才能得到理想值,比如下面这个例子

public void test(){

   BigDecimal value1 =new BigDecimal(10.511);
   System.out.println("value1: " + value1); 
   BigDecimal value2 = new BigDecimal("10.511");
   System.out.println("value2: " + value2);

}
value1: 10.510999999999999232613845379091799259185791015625
value2: 10.511

3、最后再看一下mysql对浮点数的支持

mysql有三种类型可以表示浮点数,分别为float(32位,对应第一篇文章中说的IEEE754单精度),double(64位,对应双精度),还有我们这节课的主角decimal(mysql官方建议高精度浮点数计算用decimal存储,decimal不同版本中允许的范围不同,可以根据自己的版本查一下)。float占4个字节,double占8个字节,decimail(M,D)占M+2个字节。

decimal(M,D):decimal使用时需要指定两个精度,M是数字的最大数(精度),D是小数位数,比如decimal(5, 2)的表示范围是-999.99到9999.99(因为最大存储是M+2 = 5 + 2 = 7个字节),存储时,小数位数如果超过D会直接四舍五入,比如有个字段decimal(5,2),我们执行插入语句

INSERT INTO `test`.`test_float` (`a`, `b`, `c`) VALUES ('5.554', '5.555', '5.999');

结果为

我们还需要注意的一个问题就是mysql decimal会有两个小bug,第一个就是用字符串比较的时候,会有不精准的问题

解决方法可以对该字段做索引

另外还有一个bug就是decimal不能用作分区key,这个大部分人应该不会遇到吧,关于两个bug详细情况和解决方案可以参考

https://bugs.mysql.com/bug.php?id=72056

https://bugs.mysql.com/bug.php?id=72274

4、浮点数高精度到此应该就算说完了,当然还有最后一种方案,就是把所有的浮点数都转化成整形计算,比如金额就用分来存储(微信和支付宝的sdk都是用的这种方法)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值