金额精度问题
背景:本文以企业级区块链应用Quorum为例子(该应用由golang编写),说明了如何解决金额精度的问题。
金额精度问题是每一位金融系统开发者要面临的问题,从笔者入行开始,从银行系统,互联网支付公司系统,一直到区块链金融应用,这个问题无处不在。针对此典型问题典型解法如下。
具体例子
工行账户黄金应用(真实存在的应用)的金额单位定义成float64,会在实际中造成什么问题?effective java有提到这个问题的,会造成如下严重后果(我用golang改写如下):
package main
import "fmt"
const TEN_CENTS float64 = 0.1
func main() {
var itemBought int = 0
var myfund float64 = 1.00
for price := TEN_CENTS; price < myfund; price += TEN_CENTS {
itemBought++
myfund -= price
fmt.Println("loop")
}
fmt.Println("买到的东西:", itemBought, " 剩余的金额:", myfund)
}
如果用**“go run”**命令运行这个文件会有如下提示输出
vincent@zheng450:~/gopath$ go run currency.go
loop
loop
loop
买到的东西: 3 剩余的金额: 0.3999999999999999
为什么?就是float64其实不适合用来表示货币
这个问题笔者在同系列博客的《金融应用资金处理安全十问》有阐述,见其中第6点
那么Quorum怎么解决问题?
首先,货币的表示上人家用的是国际标准
考虑到国家间的不同情况,货币形式有不同,会在xml中定义
<currencies>
<currency type="USD">
<displayName>Dollar</displayName>
<symbol>$</symbol>
</currency>
<currency type ="JPY">
<displayName>Yen</displayName>
<symbol>¥</symbol>
</currency>
<currency type="PTE">
<displayName>Escudo</displayName>
<symbol>$</symbol>
</currency>
</currencies>
其次,聪明的做法是绕开这个问题,像比特币,直接没有小数点,以Wei为单位,而其不可以再细分。
见如下代码(core/types/transaction.go)
Amount *big.Int `json:"value"
所以都会引入math.big包,没有小数点
人民币怎么办?
定义好形式,比如最小是分,所有的操作统一换成是分,然后计算
过程类似如下:
转换 – 》 计算 – 》 转换
货币精度问题在之前的区块链应用中的解决
-
比特币 采用超大精度,38位,另外像以太坊之类,都是取math.bigInt,wei是最小不可细分
-
Fabric是搭建底层的channel,证书之类,没有涉及到金额,把这个问题抛给了应用层
银行应用对金额处理的路线之争:
-
国际贸易结算应用:设置专门的金额类,有符号,有小数,有复杂计算规则
-
主机核心系统应用:仅和人民币打交道,所以都规定到分,简单化规则
将来我们的应用系统,建议还是走第一套方案,初始就支持国际化,至于增加了计算量,其实主要性能瓶颈不在这里