【转载】定点数杂谈
注:本文转载自 定点数杂谈 - 掘金
如有侵权,联系删除
假如你开发了一个后台系统,其中有关于金额的一些操作。比如说,最小精度是分
,也就是一分钱。
那么可以规定,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