【转载】定点数杂谈
注:本文转载自 定点数杂谈 - 掘金
如有侵权,联系删除
假如你开发了一个后台系统,其中有关于金额的一些操作。比如说,最小精度是分,也就是一分钱。
那么可以规定,1 代表一分,而10代表一角,自然100代表一元钱。
此时此刻,0.1元这个金额就可以用 10 这个整数来表示。
这就代表着,用整数类型的变量是完全可以存储一个小数类型的数值的。
为什么要用整数来存储小数
因为现在计算机里自带的浮点数是不精确的。这个跟浮点数的存储方式有关系。这并不是本文的主要内容。所以这里不详解了。
用两个整数来表示一个小数
既然不能用浮点数,那么用两个整数来表达任意有理数。也就是分数的概念:
- 分子 : 一个任意整数(不能超出计算机能表示的范围)
- 分母 : 一个任意整数(不能超出计算机能表示的范围)
示例代码(Golang):
|
|
上述代码中,如果我们要表达 1/3, 那么
|
|
这样一来,只要不超过int64的范围,那么任意有理数都能被表示出来了。
但是有下面的问题:
- 如何来进行加减乘除的运算?
- 如何导出成正规的人能看得懂的数字呢?
答案就是,自己实现这些操作。
这里给出一个加法的例子(请复习小学数学):
x/y+a/b=(xb+ay)/yb
|
|
那么其他的运算啥的跟上面同样,用小学数学知识就行了。
这样搞行不行呢,当然行。
不过,这样搞有点类似于 BigDecimal,就是大数。
注意:真正的大数能超脱int64, 而上面的实现,还是基于int64的。
开始讲定点数
定点数本身的类型可以是int32, 也可以是int64,这取决于你的系统需要多大范围的数。
对,定点数本身不是BigDecimal,无法表示任意有理数,定点数是有范围的。
举一个例子:
int26_6 : 本质类型是 int32,只不过,前面26位bit,用来存放整数部分,后面6位用来存放小数部分。
那么具体怎么存放的呢,举一个例子:
1.25=1+1/4
如上,我们将 1.25 分成 1 和 1/4两个部分。
1,我们用 1<<6 = 64来表示。 1/4, 我们用 1<<4 = 16来表示。 1+1/4, 自然就是上述两者相加: 1<<6 + 1<<4 = 64 + 16 = 80来表示。
用二进制表示:
0000 0000 0000 0000 0000 0000 01|01 0000
注意上面的竖线,表示整数和小数之间的小数点。
这里有一个问题,前面整数部分很好理解,最低位是1,那么整数部分整个就是 1。
小数部分呢:
010000 为什么就是 1/4?
我们看一下 010000 这个二进制代表的是几:16。
而 四个16正好就是 64, 记得吗,64 在我们的算法里,就是整数1。
我们在二进制里也来计算一下四个010000相加:
010000 + 010000 + 010000 + 010000 = 1000000
而1000000在我们的算法里,也正好就是整数1。
从上面可以看出来,int26_6的加减法,正好兼容了原本整数的加减法,无需做改造。
但是乘除就不行了。这里不详解,有兴趣的可以去网上查,也可以自己推导。
用定点数的好处
- 精确 (尤其涉及到金额时, 当然金额最好用BigDecimal方案)
- 多系统同步(如果你做过游戏,就明白了)
- 比浮点数计算更节省cpu