相关主题
{{discussion.title}}
最牛社区
首页
新主题
新回复
热门
注册
登录
在 Go 项目里,涉及到金额计算,大家一般用什么方式?
{{ getUsernameByUid(13216) }}
发布{{ getTimeInfo('2025-04-02 11:52:26') }}
#0
{{ getUsernameByUid(13216) }}
发布{{ getTimeInfo('2025-04-02 11:52:26') }}
#0
以分为单位存储,然后程序里都使用整形来计算以元到单位存储,使用 math/big 包处理高精度金额
{{ getUsernameByUid(12) }}
发布{{ getTimeInfo('2025-04-02 11:54:29') }}
#1
{{ getUsernameByUid(12) }}
发布{{ getTimeInfo('2025-04-02 11:54:29') }}
#1
分为单位,相应国家法规
{{ getUsernameByUid(12) }}
发布{{ getTimeInfo('2025-04-02 11:57:20') }}
#2
{{ getUsernameByUid(12) }}
发布{{ getTimeInfo('2025-04-02 11:57:20') }}
#2
{{ getUsernameByPostNum(1) }}
#1
纠正,这叫辅币单位…
{{ getUsernameByUid(17057) }}
发布{{ getTimeInfo('2025-04-02 11:58:26') }}
#3
{{ getUsernameByUid(17057) }}
发布{{ getTimeInfo('2025-04-02 11:58:26') }}
#3
计算根据精度要求统一转成整形来计算,存储正常是分
{{ getUsernameByUid(12806) }}
发布{{ getTimeInfo('2025-04-02 12:00:49') }}
#4
{{ getUsernameByUid(12806) }}
发布{{ getTimeInfo('2025-04-02 12:00:49') }}
#4
第一种
{{ getUsernameByUid(2611) }}
发布{{ getTimeInfo('2025-04-02 12:03:11') }}
#5
{{ getUsernameByUid(2611) }}
发布{{ getTimeInfo('2025-04-02 12:03:11') }}
#5
第二种用法也不应该用 math/big ,那还是二进制的。而是应该用 Decimal ,比如: https://pkg.go.dev/github.com/shopspring/decimal
金额计算最重要的应该是 尽可能地延后计算(尤其是涉及乘除法的)来最大化避免误差。某些时候表达式(例如字符串 "10.0/3*3")可能是交换数据时更好的表示方式。
{{ getUsernameByUid(17058) }}
发布{{ getTimeInfo('2025-04-02 12:08:46') }}
#6
{{ getUsernameByUid(17058) }}
发布{{ getTimeInfo('2025-04-02 12:08:46') }}
#6
正常用第一种,分;如果无奈必须要用第二种,用 shopspring/decimal 库
{{ getUsernameByUid(14211) }}
发布{{ getTimeInfo('2025-04-02 12:15:02') }}
#7
{{ getUsernameByUid(14211) }}
发布{{ getTimeInfo('2025-04-02 12:15:02') }}
#7
元*1e6 存 bigint
{{ getUsernameByUid(2684) }}
发布{{ getTimeInfo('2025-04-02 12:24:34') }}
#8
{{ getUsernameByUid(2684) }}
发布{{ getTimeInfo('2025-04-02 12:24:34') }}
#8
最早的时候用 vb 实现的是,先转换为整数,再计算,算完再转为小数。
另外需要看场景,往往和钱有关系的系统,通常也和财务有关系,和财务有关系的通常和“成本核算”有关系。
此时,用“分”为单位反而不适合,因为涉及“成本”的计算,就要很多位小数了。
说回来,如果只是涉及“钱”,那未来也可能有多币种问题,还是绕不开多位小数。
所以为什么不一劳永逸呢。
{{ getUsernameByUid(59) }}
发布{{ getTimeInfo('2025-04-02 12:37:39') }}
#9
{{ getUsernameByUid(59) }}
发布{{ getTimeInfo('2025-04-02 12:37:39') }}
#9
分三个字段,整数(bigint)、小数(bigint)、小数位数(int)
{{ getUsernameByUid(2497) }}
发布{{ getTimeInfo('2025-04-02 12:56:01') }}
#10
{{ getUsernameByUid(2497) }}
发布{{ getTimeInfo('2025-04-02 12:56:01') }}
#10
上家公司数据库里存的是字符串,计算的话用了 decimal 库,小数点后保留 8 位,计算完后转字符串后存到数据库
{{ getUsernameByUid(15165) }}
发布{{ getTimeInfo('2025-04-02 13:09:48') }}
#11
{{ getUsernameByUid(15165) }}
发布{{ getTimeInfo('2025-04-02 13:09:48') }}
#11
这是个业务或者说工程问题,不全是技术问题。因为不像 J**a 用 BigDecimal 比较多,Go 这边没有习惯性做法,所以主要靠约定,剩下的是如何执行。无论哪种语言,计算这类纯技术问题都是小事,与业务结合的部分才是大事。
我这里说几个技术人员可能不太熟悉的业务需求或者是需要注意的点:
- 最理想的是用 Go 自定义数据结构,在数据结构上实现相应的运算方法。这样可以避免代码上的出错。这里比较关键的点是,计算过程肯定是高精度/整型(对应 BigDecimal ),同时一定要支持 split 操作,主要是处理类似 1 拆成 0.33+0.33+0.34 的业务需求,这种在会计、税务场景很常见。
- 根据全流程数据交换的环节来定义精度和数据格式。一般推荐的是比常见货币最小单位更精确一点,或者支持自定义。传递过程中使用字符串,做好格式转换。说到底还是靠约定,为了方便执行可以独立成库强制所有上下游都使用特定实现。
- 有需要的话提前考虑多币种支持,比如订单系统如果可以支持多币种,数据库和业务逻辑都要做 schema 调整。通常的业务需求来说,多币种就是多账本,如果最初设计成单账本后期修改会很痛苦。
- 推荐使用 event driven 架构来做 cqrs ,即用事务的方式持久化变动,实时状态由历史计算推导得出。类似银行卡只记录进出,余额是算出来的。好处一方面能和通常的消息队列架构方便对接,另一方面整个业务的可靠性、灾难恢复都好做。有 olap 需求也容易改造。
{{ getUsernameByUid(9823) }}
发布{{ getTimeInfo('2025-04-02 13:17:27') }}
#12
{{ getUsernameByUid(9823) }}
发布{{ getTimeInfo('2025-04-02 13:17:27') }}
#12
以最小单位存储
比如日元就没有分
美元有分
人民币有分
{{ getUsernameByUid(2279) }}
发布{{ getTimeInfo('2025-04-02 13:17:35') }}
#13
{{ getUsernameByUid(2279) }}
发布{{ getTimeInfo('2025-04-02 13:17:35') }}
#13
minor unit
{{ getUsernameByUid(16855) }}
发布{{ getTimeInfo('2025-04-02 13:31:49') }}
#14
{{ getUsernameByUid(16855) }}
发布{{ getTimeInfo('2025-04-02 13:31:49') }}
#14
自己做一个指定 decimal 的定点数类吧
{{ getUsernameByUid(333) }}
发布{{ getTimeInfo('2025-04-02 13:35:12') }}
#15
{{ getUsernameByUid(333) }}
发布{{ getTimeInfo('2025-04-02 13:35:12') }}
#15
我有个问题,哪怕用分为单位算 如果除完之后 是 6666.6666666 这到底是直接舍弃小数点后面的 还是进一位?
{{ getUsernameByUid(17059) }}
发布{{ getTimeInfo('2025-04-02 13:35:13') }}
#16
{{ getUsernameByUid(17059) }}
发布{{ getTimeInfo('2025-04-02 13:35:13') }}
#16
用的 5 楼的那个库
{{ getUsernameByUid(59) }}
发布{{ getTimeInfo('2025-04-02 13:47:09') }}
#17
{{ getUsernameByUid(59) }}
发布{{ getTimeInfo('2025-04-02 13:47:09') }}
#17
非金融行业的看领导决定咯,满五进一还是直接舍弃
{{ getUsernameByUid(5745) }}
发布{{ getTimeInfo('2025-04-02 13:47:16') }}
#18
{{ getUsernameByUid(5745) }}
发布{{ getTimeInfo('2025-04-02 13:47:16') }}
#18
10 分分成 3 份,那就是 3+3+4 ,最后一份是差值出来的。至于用 3 还是 4 ,要业务方来决定
{{ getUsernameByUid(3082) }}
发布{{ getTimeInfo('2025-04-02 13:54:40') }}
#19
{{ getUsernameByUid(3082) }}
发布{{ getTimeInfo('2025-04-02 13:54:40') }}
#19
用分计算的目的就是为了方便直接舍弃
{{ getUsernameByUid(post.updatedByUid) }}
编辑于 {{ getTimeInfo(post.UpdatedAt) }}
{{ getUsernameByUid(post.uid) }}
发布于 {{ getTimeInfo(post.CreatedAt) }}
# {{post.num}}
{{ getUsernameByUid(post.updatedByUid) }}
编辑于 {{ getTimeInfo(post.UpdatedAt) }}
{{ getUsernameByUid(post.uid) }}
发布于 {{ getTimeInfo(post.CreatedAt) }}
# {{post.num}}
{{ getUsernameByPostNum(post.mentionNum) }}
登录回复
#{{nav.post.anchor}}
{{ alert.text }}
关闭