金融科技应用多币种及加密货币金额存储的数据类型选型问询
金融科技应用多币种及加密货币金额存储的数据类型选型问询
老哥,太懂你这种卡脖子的感觉了——之前靠「整数存最小单位」的方案顺风顺水跑了好几年,结果碰到里亚尔、比特币这类要么数值超大要么小数位超多的币种,直接丢生意,换谁都得急着解决。结合你Ruby+JS微服务、4亿条历史数据还不想动坏现有系统的情况,给你两个实操性拉满的方案,都是完全规避浮点坑的:
方案一:高精度小数类型(直观易用,兼容全币种)
这方案是直接把金额存成人类可读的精确小数,完全绕开浮点精度问题,适合想长期简化维护的场景:
- 数据库侧:用
DECIMAL(38,18)(或者NUMERIC,俩在主流数据库里功能完全等价)。38位总长度足够装下任何加密货币的最大总量+18位小数位(毕竟像以太坊这类代币普遍是18位小数,比特币是8位,里亚尔的大额更是不在话下)。主流数据库(PostgreSQL、MySQL、SQL Server)全支持,不会有兼容性问题,而且运算时完全精确,不会出现0.1+0.2≠0.3的破事。 - Ruby侧:直接用标准库自带的
BigDecimal类,和数据库的DECIMAL映射完美。原来的整数逻辑改起来也顺:比如原来存12345代表$123.45,现在直接转成BigDecimal('12345') / 100就行,运算时全程用BigDecimal,绝对不会有精度丢失。注意初始化的时候一定要用字符串,比如BigDecimal('123.45'),别用BigDecimal(123.45)——后者是从Float转的,会带精度坑。 - JS侧:别用原生Number,用
Decimal.js或者big.js这类轻量级高精度小数库(都是纯JS实现,不用装啥复杂依赖)。这些库的API和Number几乎一样,改代码成本极低,比如原来写123.45 + 67.89,现在改成new Decimal('123.45').add('67.89')就行,完全精确。
方案二:原有整数方案升级(零侵入适配,完全兼容旧代码)
如果你想最小化改动,几乎不碰现有Ruby/JS逻辑,这个方案绝对是首选——本质是把原来固定的「除以100转美元」改成「按币种配置的最小单位系数转换」:
- 数据库侧:不用改原有交易表的金额字段类型,直接用
BIGINT(64位整数)就行,它能存到9e18的数值,不管是里亚尔的大额(1美元=45000里亚尔,存最小单位费尔的话就是4500000,远小于9e18)还是比特币的聪(1BTC=1e8聪,2100万BTC也才2.1e15聪),甚至是18位小数的加密货币代币,都能轻松装下。新增一张currencies配置表,每个币种存decimal_places(比如美元是2,比特币是8,里亚尔如果用费尔就是2),交易表加个外键关联币种ID就行。 - Ruby侧:完全不用改原来的整数运算逻辑!原来的代码是
amount / 100转成美元,现在改成amount / 10**currency.decimal_places就行,Ruby的Integer是任意精度的,根本不用担心溢出问题,连类型转换都省了。 - JS侧:大部分币种的最小单位换算后数值都在Number的安全整数范围(2^53-1,约9e15)内,直接用原来的Number逻辑就行;如果碰到少数超过这个范围的代币,换成
BigInt处理就行,比如BigInt(amount) / BigInt(10**decimalPlaces),改动极小。
方案选型建议
- 要是你想快速上线,零风险兼容旧系统,不想大动代码:选方案二,改动只涉及「把固定的100换成动态获取的系数」,几乎不会引入新问题,4亿条历史数据也不用动(原来的美元数据直接对应decimal_places=2就行)。
- 要是你不想维护币种配置,以后要在数据库里做大量金额统计/运算:选方案一,直接存直观的金额数值,数据库查询时不用再做系数换算,长期维护更省心。
绝对要避的坑
不管选哪个方案,打死都别用FLOAT/DOUBLE类型!浮点精度丢失是金融系统的致命问题,哪怕是0.0000001的误差,都可能导致对账不平、用户投诉甚至合规问题,绝对碰不得。
内容来源于stack exchange




