上文说到了由于很多十进制小数在计算机中用二进制表示,是无尽的。因此会有精度丢失的问题,那么我们如何解决这个问题呢
大致有两种解决途径:
一、计算机支持
二、编程语言支持
1、计算机中有一种用十进制存储数字的格式,叫做BCD码,它用四位二进制数来表示一个十进制数字,比如98,用BCD码表示为
9 | 8 |
1001 | 1000 |
这种亦称为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都是用的这种方法)