V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
inSpring
V2EX  ›  Go 编程语言

在 Go 项目里,涉及到金额计算,大家一般用什么方式?

  •  
  •   inSpring · 11 天前 · 5243 次点击
    1. 以分为单位存储,然后程序里都使用整形来计算
    2. 以元到单位存储,使用 math/big 包处理高精度金额
    48 条回复    2025-04-03 17:34:01 +08:00
    ferock
        1
    ferock  
       11 天前 via iPhone
    分为单位,相应国家法规
    ferock
        2
    ferock  
       11 天前 via iPhone   ❤️ 1
    @ferock #1

    纠正,这叫辅币单位…
    zlhsvc
        3
    zlhsvc  
       11 天前
    计算根据精度要求统一转成整形来计算,存储正常是分
    japeth
        4
    japeth  
       11 天前
    第一种
    w568w
        5
    w568w  
       11 天前   ❤️ 7
    第二种用法也不应该用 math/big ,那还是二进制的。而是应该用 Decimal ,比如: https://pkg.go.dev/github.com/shopspring/decimal

    金额计算最重要的应该是 尽可能地延后计算(尤其是涉及乘除法的)来最大化避免误差。某些时候表达式(例如字符串 "10.0/3*3")可能是交换数据时更好的表示方式。
    kkkwar
        6
    kkkwar  
       11 天前
    正常用第一种,分;如果无奈必须要用第二种,用 shopspring/decimal 库
    zpfhbyx
        7
    zpfhbyx  
       11 天前
    元*1e6 存 bigint
    NewYear
        8
    NewYear  
       11 天前
    最早的时候用 vb 实现的是,先转换为整数,再计算,算完再转为小数。

    另外需要看场景,往往和钱有关系的系统,通常也和财务有关系,和财务有关系的通常和“成本核算”有关系。
    此时,用“分”为单位反而不适合,因为涉及“成本”的计算,就要很多位小数了。

    说回来,如果只是涉及“钱”,那未来也可能有多币种问题,还是绕不开多位小数。

    所以为什么不一劳永逸呢。
    kk2syc
        9
    kk2syc  
       11 天前
    分三个字段,整数(bigint)、小数(bigint)、小数位数(int)
    sardina
        10
    sardina  
       11 天前
    上家公司数据库里存的是字符串,计算的话用了 decimal 库,小数点后保留 8 位,计算完后转字符串后存到数据库
    kuanat
        11
    kuanat  
       11 天前   ❤️ 2
    这是个业务或者说工程问题,不全是技术问题。因为不像 Java 用 BigDecimal 比较多,Go 这边没有习惯性做法,所以主要靠约定,剩下的是如何执行。无论哪种语言,计算这类纯技术问题都是小事,与业务结合的部分才是大事。

    我这里说几个技术人员可能不太熟悉的业务需求或者是需要注意的点:

    - 最理想的是用 Go 自定义数据结构,在数据结构上实现相应的运算方法。这样可以避免代码上的出错。这里比较关键的点是,计算过程肯定是高精度/整型(对应 BigDecimal ),同时一定要支持 split 操作,主要是处理类似 1 拆成 0.33+0.33+0.34 的业务需求,这种在会计、税务场景很常见。

    - 根据全流程数据交换的环节来定义精度和数据格式。一般推荐的是比常见货币最小单位更精确一点,或者支持自定义。传递过程中使用字符串,做好格式转换。说到底还是靠约定,为了方便执行可以独立成库强制所有上下游都使用特定实现。

    - 有需要的话提前考虑多币种支持,比如订单系统如果可以支持多币种,数据库和业务逻辑都要做 schema 调整。通常的业务需求来说,多币种就是多账本,如果最初设计成单账本后期修改会很痛苦。

    - 推荐使用 event driven 架构来做 cqrs ,即用事务的方式持久化变动,实时状态由历史计算推导得出。类似银行卡只记录进出,余额是算出来的。好处一方面能和通常的消息队列架构方便对接,另一方面整个业务的可靠性、灾难恢复都好做。有 olap 需求也容易改造。
    guiyumin
        12
    guiyumin  
       11 天前
    以最小单位存储
    比如日元就没有分
    美元有分
    人民币有分
    pike0002
        13
    pike0002  
       11 天前
    minor unit
    futuretech6
        14
    futuretech6  
       11 天前
    自己做一个指定 decimal 的定点数类吧
    zhangfeiwudi
        15
    zhangfeiwudi  
       11 天前
    我有个问题,哪怕用分为单位算 如果除完之后 是 6666.6666666 这到底是直接舍弃小数点后面的 还是进一位?
    Desdemor
        16
    Desdemor  
       11 天前
    用的 5 楼的那个库
    kk2syc
        17
    kk2syc  
       11 天前
    @zhangfeiwudi 非金融行业的看领导决定咯,满五进一还是直接舍弃
    cyrivlclth
        18
    cyrivlclth  
       11 天前
    @zhangfeiwudi 10 分分成 3 份,那就是 3+3+4 ,最后一份是差值出来的。至于用 3 还是 4 ,要业务方来决定
    me1onsoda
        19
    me1onsoda  
       11 天前
    @zhangfeiwudi 用分计算的目的就是为了方便直接舍弃
    adoal
        20
    adoal  
       11 天前
    衍生问题:写 web API 时,JSON 里表示金额用什么类型?
    layxy
        21
    layxy  
       10 天前
    @adoal 字符串
    realpg
        22
    realpg  
       10 天前
    不是特殊金融行业 一律分
    金融行业也争取用小数点后八位 int64
    ALongRanger
        23
    ALongRanger  
       10 天前   ❤️ 1
    我的回答可能和具体语言无关。
    1. 建议采用对应货币最小单位作为存储,主要是为了避免在计算和存储过程中产生精度损失导致资损;

    2. 不管是 java 还是 go ,请编码统一的金额处理工具包,定义好金额结构和转换方法,避免直接使用基础结构例如整形,浮点来表示金额, 所有金额的表示直接使用定义好的金额结构。 整个项目内部统一采用,保证金额计算、转换的准确性。

    3. 一个金额处理工具包应该考虑如下内容:
    3.1. 要能够进行序列化和比较
    3.2 要能存储币种和金额,要确保高精度和标准化
    3.3 要能提供丰富的操作方法,包括加减乘除,需要自定义舍入和四舍五入;
    3.4 要提供常用单位 getAmount()和获取最小单位金额的方法 getAmountMinorUnit()
    3.5 确保结构的不可变性和安全性;
    3.6 需要能够根据币种计算出最小单位,例如人民币就是分,日元就是元;
    3.7 乘除支持舍入,默认使用四舍五入,但是支持其它舍入方式。

    以上可以在跨境支付、金融应用等需要高精度、多币种支持的场景种安全有效的进行金额处理。
    hwdq0012
        24
    hwdq0012  
       10 天前
    c#里用 decimal 类型,十进制浮点
    pkoukk
        25
    pkoukk  
       10 天前
    https://pkg.go.dev/github.com/shopspring/decimal
    用这个,它同时支持 MYSQL
    pkoukk
        26
    pkoukk  
       10 天前
    @zhangfeiwudi 业务决定,不同场景不同处理方式,可能都会有。
    james122333
        27
    james122333  
       10 天前 via Android
    在前公司用 math/big 并封装成一 struct
    本来想用 decimal 但搞计算的用 math/big 就算了
    理由是精度够高了
    至于延后计算可能要另外找库或自己刻 没特别研究过 肯定是种专业的数学库
    ixcode
        28
    ixcode  
       10 天前
    bigint + decimal 加密货币小数点后 18 位都能算
    sampeng
        29
    sampeng  
       10 天前
    so ?你要用毛吗?哈哈哈哈哈哈
    mindddd
        30
    mindddd  
       10 天前
    @kk2syc 这个老哥可以
    lesismal
        31
    lesismal  
       10 天前
    除了 blockchain 动辄多少个 0 的大范围数值,其他普通领域 int64 保平安足够了
    mengzhuo
        32
    mengzhuo  
       10 天前
    某鹅某部门就是分为单位,int64
    veni2023
        33
    veni2023  
       10 天前
    @w568w 这个库作者好像没空维护了
    cheng6563
        34
    cheng6563  
       10 天前
    反正别出现浮点数就行了
    KingHL
        35
    KingHL  
       10 天前
    这个有什么鸡毛讨论的,普通货币有用 2 的大聪明出来说说?
    james122333
        36
    james122333  
       10 天前 via Android
    @james122333

    讲细一点 我跟着用而已
    realpg
        37
    realpg  
       10 天前
    @zhangfeiwudi #15
    正常来说财务账目上没有除法运算 设计上最后平衡都是加减(乘)
    除法无论怎么算都是不影响平账的
    如果除法能影响平账 那就是设计逻辑有问题 架构开除就好了
    james122333
        38
    james122333  
       10 天前 via Android
    细思了一下 其实这问题调用系统的 bc 命令也是个解法 还实现延后计算的问题 如果有 golang 版做成 lib 的 bc 会更好
    moudy
        39
    moudy  
       10 天前
    @sardina #10 记得巴菲特的公司曾经一天来回交易的流水把 8 位小数级别的误差给弄到小数点前面去了
    james122333
        40
    james122333  
       10 天前 via Android
    事实上 bc 是种语言 也有 function 可以用 任意精度到 2147483647 位
    https://zh.m.wikipedia.org/zh-cn/Bc%E8%AF%AD%E

    命令行 cp 值真的太高
    crackidz
        41
    crackidz  
       10 天前
    第一种,如果要用第二种,一定要用 decimal
    zbw0414
        42
    zbw0414  
       10 天前
    @w568w 学到了, 儿豁
    cslive
        43
    cslive  
       10 天前
    @zhangfeiwudi 有个算法叫银行家算法,6666.6666666 这到底是直接舍弃小数点后面的 还是进一位
    cslive
        44
    cslive  
       10 天前
    @cslive 发错了,根据银行家算法应该是进一位
    back0893
        45
    back0893  
       10 天前
    啊?只用 rmb 就分呗
    arsenal4
        46
    arsenal4  
       9 天前
    上面有人提到银行家算法。我想请教一下,银行家算法是通用做法吗?还是一种可选项?曾经被一个空降的 CTO 无情嘲讽,原话是『我从没见过一个电商平台使用四舍五入』
    lvlongxiang199
        47
    lvlongxiang199  
       9 天前
    @kk2syc 那你这咋进行加减乘除的计算呢 ?
    madku
        48
    madku  
       9 天前
    decimal
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2606 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 05:58 · PVG 13:58 · LAX 22:58 · JFK 01:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.