V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
einsdisp
V2EX  ›  程序员

涉及金钱存储或计算操作时,你们一般都使用什么数据类型

  •  
  •   einsdisp · 2020-06-19 20:57:16 +08:00 · 10478 次点击
    这是一个创建于 1625 天前的主题,其中的信息可能已经有所发展或是发生改变。

    直接使用小数类型进行计算或存储(编程语言的 float 类型及数据库的 decimal 类型)

    还是乘以 100,以分为单位,使用整数类型进行计算及数据库存储?

    76 条回复    2020-06-30 15:54:44 +08:00
    wwti9
        1
    wwti9  
       2020-06-19 20:59:36 +08:00
    乘以 100
    yulitian888
        2
    yulitian888  
       2020-06-19 20:59:58 +08:00
    一万倍,长整型
    wujieyuan
        3
    wujieyuan  
       2020-06-19 21:02:50 +08:00
    我见过好多用 double 或者 String 的, 但是我觉得用分做单位,整数型最稳,除专业金融领域, 分单位足够用了
    cigarzh
        4
    cigarzh  
       2020-06-19 21:09:51 +08:00   ❤️ 1
    BigDecimal 一把梭,不要想自己搞什么花花肠子
    xuanbg
        5
    xuanbg  
       2020-06-19 21:17:26 +08:00
    金额使用 BigDecimal,金融行业 4 位小数,非金融 2 位小数。单价、利率什么的也用 BigDecimal,具体几位小数看需求。
    ackoly
        6
    ackoly  
       2020-06-19 21:18:54 +08:00 via iPhone
    用分一定要小心,见过本来要配置 100 元的,没加两个 00,变成 1 块。
    xuanbg
        7
    xuanbg  
       2020-06-19 21:19:19 +08:00
    double 、float 仅用于科学计算,用在业务系统里面就是搞笑。
    xuanbg
        8
    xuanbg  
       2020-06-19 21:21:36 +08:00
    用 string 就更搞笑了,完全是治标不治本。
    billlee
        9
    billlee  
       2020-06-19 21:52:50 +08:00
    不能一概而论,算加减法用定点数没有问题,但利息什么的要用乘法的时候,还是得用浮点数
    vanillaxxx
        10
    vanillaxxx  
       2020-06-19 22:00:17 +08:00 via iPhone
    bigdecimal
    pinkSlime
        11
    pinkSlime  
       2020-06-19 22:01:07 +08:00
    用 double 做清结算之类的 没被人砍死吗?
    还是想留后门自己薅羊毛
    Aruforce
        12
    Aruforce  
       2020-06-19 22:02:13 +08:00 via Android
    存储用分…计算的话最后用减……
    dswyzx
        13
    dswyzx  
       2020-06-19 22:37:57 +08:00
    数据库落库 decimal
    业务里各种千奇百怪的要求,四位小数,两位小数,不要小数.都要涉及取整操作.真烦
    3dwelcome
        14
    3dwelcome  
       2020-06-19 22:45:02 +08:00 via Android
    我要给 double 正名!按说精度也有 14 位,怎么就不能当存储类型了?
    统计有误差,是因为有计算中有误差会逐渐累计!你把误差去掉,或者用高精度大数类型替代一下就可以。
    光保存是没问题的。
    dodo2012
        15
    dodo2012  
       2020-06-19 22:48:59 +08:00
    decimal,,,
    liprais
        16
    liprais  
       2020-06-19 22:55:44 +08:00 via iPhone
    不用 decimal 早晚有问题
    zjsxwc
        17
    zjsxwc  
       2020-06-19 23:07:33 +08:00 via Android
    两位小数,decimal 逃
    Mac
        18
    Mac  
       2020-06-19 23:22:26 +08:00
    decimal
    rockyou12
        19
    rockyou12  
       2020-06-19 23:25:37 +08:00   ❤️ 4
    @3dwelcome 放屁,一看就是没踩过坑的,差一分财务都会对出来好不
    Sharuru
        20
    Sharuru  
       2020-06-19 23:42:33 +08:00 via Android
    BigDecimal 一把梭,多币种清算业务毫无压力。
    xingheng
        21
    xingheng  
       2020-06-19 23:44:29 +08:00
    万万没想到,居然还有这么多人写的系统用浮点类型,惊到了
    CEBBCAT
        22
    CEBBCAT  
       2020-06-20 00:00:28 +08:00
    @3dwelcome #14 学完了 IEEE 754 再来回帖吧……
    lxml
        23
    lxml  
       2020-06-20 01:48:04 +08:00
    @3dwelcome #14 你在逗我????来电商三天包你下岗……
    zjsxwc
        24
    zjsxwc  
       2020-06-20 02:08:08 +08:00 via Android   ❤️ 1
    @xuanbg #8 原文:“用 string 就更搞笑了,完全是治标不治本。”
    回复:

    我倒觉得如果不用 decimal 而用 ` 两位小数的 string `或者 `乘以 100 的 integer `,貌似也都可以,因为好像也不会碰到无脑 double 浮点数运算的误差问题。
    xuanbg
        25
    xuanbg  
       2020-06-20 07:17:15 +08:00
    @zjsxwc string 看上去是没有精度问题,但你总要计算的吧,转 double 计算吗?所以我说「治标不治本」
    susunus
        26
    susunus  
       2020-06-20 07:59:26 +08:00 via iPhone
    老实说目前用的就是 Double 存储的,计算的时候,用用 BigDecimal 包裹,处理了下精度问题,那位老哥帮忙科普下,这样有啥风险
    Building
        27
    Building  
       2020-06-20 08:06:20 +08:00 via iPhone
    @xuanbg 人家都用 String 存储了你非要转 Double 做运算,不知道谁傻。
    reself
        28
    reself  
       2020-06-20 08:18:41 +08:00 via Android   ❤️ 1
    @susunus 浮点数只能表示数轴上的离散点,计算结果只能近似到最近的点,除非刚好能用 2 进制表示,例如 0.5,否则都是近似值。数轴上的点数量是无穷的,用有穷的浮点数来做计算,有偏差的概非常大。所以浮点数只能用在能容忍偏差的场景,比如图形绘制。除了数值偏差,浮点数还有众所周知的不能用==判定相等的特性。
    LukeEuler
        29
    LukeEuler  
       2020-06-20 08:46:47 +08:00
    小数字用 decimal,大数字用 string,bytes 之类的,自己设计运算方法。
    存储的和传输内容都是整数,只有显示时用 string 形式的浮点数。
    mitu9527
        30
    mitu9527  
       2020-06-20 08:58:10 +08:00
    参考 Money 模式,底层一般用 64 位的整型就可以了;如果长整型还不够用,那就考虑 GMP 吧。
    darrenfang
        31
    darrenfang  
       2020-06-20 09:16:56 +08:00
    @3dwelcome #14 以前写的代码用 double,突然发现数据库里面存的数据变成了 100.00000009,哈哈哈哈哈
    xuanbg
        32
    xuanbg  
       2020-06-20 09:43:54 +08:00
    @Building string 能做四则运算???
    des
        33
    des  
       2020-06-20 09:50:07 +08:00
    @xuanbg 可以
    rekulas
        34
    rekulas  
       2020-06-20 10:04:56 +08:00
    @xuanbg decimal 也是 string 按我了解的 多种语言的高精度计算貌似都用到了字符串做中间过程
    des
        35
    des  
       2020-06-20 10:07:06 +08:00
    并不需要转数值再计算,可以看一下这个 https://oi-wiki.org/math/bignum/
    yadiman
        36
    yadiman  
       2020-06-20 10:13:41 +08:00
    某金融,13 位整形,minor unit,配合 decimal flag,然后转化成 major unit 。
    比如,美金,decimal flag 就是 2,所以,100 minior unit = 1 USD major unit
    越南盾,decima flag 就是 0,10000 minor unit 就是 10000 越南盾元。

    通货膨胀,经常爆了。。。
    PopRain
        37
    PopRain  
       2020-06-20 10:38:31 +08:00
    c# Decimal 值类型表示介于正 79,228,162,514,264,337,593,543,950,335 和负 79,228,162,514,264,337,593,543,950,335 之间的十进制数字。
    sql server money -922,337,203,685,477.5808 到 922,337,203,685,477.5807 (对于 Informatica,为 -922,337,203,685,477.58 到 922,337,203,685,477.58 。Informatica 仅支持两位小数,而不是四位。)

    我想不出来什么应用可以超出它的范围
    PopRain
        38
    PopRain  
       2020-06-20 10:40:49 +08:00
    😓,楼上的通货膨胀没有考虑到,以百万为货币单位的货币是很容易爆。。。
    mitu9527
        39
    mitu9527  
       2020-06-20 10:57:07 +08:00
    @PopRain 64 位整型,最大可以支持 900 万万亿,算上通货膨胀,也基本够用。真要是超出这个那就上 GMP ?
    U97F3
        40
    U97F3  
       2020-06-20 11:01:07 +08:00 via Android
    Numeric(19, 2)
    PopRain
        41
    PopRain  
       2020-06-20 12:30:42 +08:00
    @mitu9527 用整数也有问题,小数点的位置问题,常用货币 1 JPY=0.009357 USD,还有一些以亿元为单位的货币
    DOLLOR
        42
    DOLLOR  
       2020-06-20 13:15:06 +08:00
    @xuanbg
    当然可以。不仅四则,乘方、开方、对数、三角函数都可以。
    最简单的方法,模拟人类手工笔算一样操作每一位数就好了。只要资源足够,理论上没有精度上下限。
    Building
        43
    Building  
       2020-06-20 13:24:41 +08:00 via iPhone
    @xuanbg String 不能做四则运算......按你这个意思你是不是觉得计算机只要超过 UIntMax 的整数就没办法表示了?
    rapiz
        44
    rapiz  
       2020-06-20 14:00:05 +08:00   ❤️ 1
    一帖显示出 v2 上有多少人基本功不过关
    hantsy
        45
    hantsy  
       2020-06-20 14:13:22 +08:00
    用 Java 的话,有 Java Money 标准(JSR354),和 Java DateTime 一个时期的产品,可惜一直没进 JDK 。
    但是这个项目已经很成熟,https://javamoney.github.io/ ,官方提供了 JPA 扩展,也就是说直接用关系数据库没问题的。
    @cigarzh
    @xuanbg

    遇到金融数据,自然想到 BigDecimal 当然是最基本的常识。但是 Money 还包括了相应 Currency 的处理,特别是国际化方面。
    mitu9527
        46
    mitu9527  
       2020-06-20 15:04:57 +08:00
    @PopRain 从数学角度看,不管你怎么处理总会存在误差,1/3 永远都除不尽,总是在某个位置要截断。我们要做的是把问题解决到可接受范围内,而不是完美解决。所以靠长整型和 Money 模式可以解决绝大多数问题,性能也还不错;如果业务真的要处理津巴布韦这种货币了,用 GMP,支持任意长度的整型,本质上是字符串,就是性能差。
    PopRain
        47
    PopRain  
       2020-06-20 15:15:21 +08:00
    @mitu9527 我是支持用 Decimal 的,基本在业务范围内的问题都可以解决,而整数很多需要自己处理,遇到某些不熟悉的程序员,可能出现大问题,譬如上面说的 1 元=100
    xuanbg
        48
    xuanbg  
       2020-06-20 15:50:35 +08:00
    @mitu9527
    @hantsy
    @Building 你们啊,用第三库或者自己造个轮子让 string 能够进行计算我会不懂? BigDecimal 本身也是一个轮子,内部的实现还是 int 和 long 。

    难道你们还能造个不用 int 、long 、double 、float 的轮子不成?
    yuzo555
        49
    yuzo555  
       2020-06-20 16:06:17 +08:00
    没研究,但是如果强行用 double 的话,我感觉,除了:

    1.误差好像基本上都是小于 10^-7 级别 (0.0000001) 的误差,每次计算完后都按照要求精度 round 一下应该可以解决这个问题;
    2.比较相等 == 的时候需要额外处理下。

    还有哪些需要注意的吗。
    mitu9527
        50
    mitu9527  
       2020-06-20 16:23:33 +08:00
    @xuanbg 原谅 PHP 太 low,没有 BigDecimal,只能用 GMP 或者 BC Math 扩展。然后最懒的方式就是用现成的、实现 Money 模式的库,还可以搞定币种、最小金额单位和分配等问题。
    everhythm
        51
    everhythm  
       2020-06-20 16:26:23 +08:00
    用 int,不同币种可以指定价格等级,类似 appstore

    不然等着被疯狂薅 1 分钱羊毛
    cubecube
        52
    cubecube  
       2020-06-20 16:30:10 +08:00
    @3dwelcome 其实我原来在银行的时候,非核心的外部系统,C 开发的,double 用来处理也没啥问题。基本上有效位数是够的。
    chenuu
        53
    chenuu  
       2020-06-20 17:53:25 +08:00 via Android
    Long,经过我们系统的大概千亿级
    zhuweiyou
        54
    zhuweiyou  
       2020-06-20 19:35:40 +08:00
    运算还是存储,存储的话,什么都行。涉及运算要么用大数类,要么单位分 整数运算。
    Vegetable
        55
    Vegetable  
       2020-06-20 19:40:08 +08:00
    好奇为什么 Decimal 不是标准答案
    celeron533
        56
    celeron533  
       2020-06-20 19:44:17 +08:00
    decimal
    sxd96
        57
    sxd96  
       2020-06-20 19:52:08 +08:00 via iPhone
    不知道楼上在喷什么,人家说用 double 存储,存储有问题吗?计算的时候拿出来怎么样精确计算各有各的方法,谁都知道 double 直接算会出问题。
    devinww
        58
    devinww  
       2020-06-20 19:59:11 +08:00
    BigDecimal
    xuanbg
        59
    xuanbg  
       2020-06-20 20:02:25 +08:00   ❤️ 1
    @sxd96 当然有问题,因为 double 的 1 不等于 int 的 1 啊
    sagaxu
        60
    sagaxu  
       2020-06-20 20:04:28 +08:00 via Android
    int string 或者 double 都行,只要处理得当。但是最后你会发现,你重新发明了 decimal 的轮子
    sagaxu
        61
    sagaxu  
       2020-06-20 20:10:12 +08:00 via Android
    @cubecube double 到万亿这个数量级精度就不够了,比如 5 万亿加 0.1 分
    joesonw
        62
    joesonw  
       2020-06-20 22:05:15 +08:00
    @sxd96 存储的就是不精确的啊. 你肉眼看到是 0.5, 实际数据并不是刚刚好 0.5 啊. 是 toString 的算法让你觉得存储的是精确的而已.
    sxd96
        63
    sxd96  
       2020-06-20 23:19:58 +08:00 via iPhone
    @xuanbg

    @joesonw

    double 能不能确保存储进去和读取出来的值是一样的?

    toString 将 double 解析成我肉眼看见的值,我在用 bigdecimal 计算的时候能不能确保转换后的值和 toString 的值是一样的?
    知道个 overflow underflow 就看哪都是问题了?用脑子再多想两遍。
    sxd96
        64
    sxd96  
       2020-06-20 23:47:46 +08:00
    @sxd96 自己更正一点。
    double 的 frac 总共 52bit,精确表示数值加上前面的 sign 1bit 总共能精确表示正负 2^52 。
    假设再加上可能有的 4 位小数,能精确表示的金额只有正负 2^48=281,474,976,710,656 以及后面的 4 位小数。适用场景大大受限。

    事实上不做计算,仅做储存,能精确表示的范围也是相对固定的。但是我认为这个范围应该是够绝大多数公司撑到死了。
    sxd96
        65
    sxd96  
       2020-06-21 00:09:30 +08:00
    @sxd96
    脑子瓦特了,再更正一点。
    如果要准确表示上面假设里说的 4 位小数,要 10bit 不是 4bit 。那么金额的整数部分就只有 2^42 = 4,398,046,511,104 。

    在精确表示的范围内直接用 double 做算术运算也不会有问题的,也用不到什么 bigdecimal 。但是这个范围确实比我想当然的小了不少。4 个小数点存在的情况下,只能做到 4 万亿内的精确。

    但是上面说的所谓 0.5 存着不是 0.5 就是开玩笑,double 的 1 那就是 1 。
    用 decimal 肯定是第一反应也是最好的选择,当然没意见。但是看着这么多瞎说 double 溢出的,就很让人奇怪。
    realpg
        66
    realpg  
       2020-06-21 10:22:11 +08:00
    必须用整形,至于乘 100 、乘 10000 、乘 1000000,看项目精度要求
    ysweics
        67
    ysweics  
       2020-06-21 12:43:36 +08:00
    金额还是统一 BigDecimal, 用别的搞法,出现问题,涉及到钱的一般都是大锅,望慎重
    LnTrx
        68
    LnTrx  
       2020-06-21 14:03:18 +08:00
    @sxd96
    4 位小数应该是 14bit 吧?
    只是存储问题当然有限,关键是实际计算的中间过程,容易出现防不胜防的错误判断、超出范围和舍入误差累积
    参见: http://www.lahey.com/float.htm
    ytmsdy
        69
    ytmsdy  
       2020-06-21 21:23:35 +08:00 via iPhone
    财务金额要是用 decimal 或者 float 的,一看就知道没有被四舍五入收拾过得!
    财务上差一分钱会计和出纳都会拉着你查账查到半夜,而最后的结果往往都是四舍五入导致的!
    所以!一定要用 int !一定要用 int !一定要用 int
    ytmsdy
        70
    ytmsdy  
       2020-06-21 21:25:13 +08:00 via iPhone
    @3dwelcome 兄弟别在这里误导人!到时候因为四舍五入的问题查账查到半夜的不是你,而是停了你建议用 double 的兄弟!
    datou
        71
    datou  
       2020-06-22 05:12:22 +08:00
    货币都是有定义最小单位的

    为何要用浮点数?
    luxinfl
        72
    luxinfl  
       2020-06-22 17:54:40 +08:00
    自打做系统,金额类的用的一直是 bigdecimal 。但是我们数据库保存的是元,总感觉别扭
    3dwelcome
        73
    3dwelcome  
       2020-06-23 16:19:39 +08:00
    @darrenfang 100.00000009 这种末尾本来就是计算误差,是随机数,你取出来的时候,直接 round 后断尾,精确度就完全没问题。
    double 有效数是 14 位,不需要用全,保证前 12 位没有数字误差,就可以了。
    IEEE 数据格式本来就是一个工具,在不同人手里,呈现的结果并不一样。代码是死的,人是活的,工具不趁手可以稍加改造。有坑多研究,总有绕过去的办法。
    码农的天职不是无脑调用 API,而是解决各种棘手问题。
    3dwelcome
        74
    3dwelcome  
       2020-06-23 16:29:13 +08:00
    @ytmsdy 四舍五入还不是因为计算精度误差。我说了 double 只是储存,如果计算累积精度不够,可以找高精度类来替代。
    你们对 double 内部格式都没有真正理解,在没有小数位的情况下,整数数值储存部分 bit,和 INT 在二进制上表示,是一模一样的。
    比如 double 存 1024 和 int 存 1024,内部就是一样的。只不过 double 里有指数位的存在,会对比特位进行 shift 操作。
    mysunshinedreams
        75
    mysunshinedreams  
       2020-06-28 17:33:02 +08:00
    其实 String 就行,平时真要计算的时候,使用 joda Money 类。
    CantSee
        76
    CantSee  
       2020-06-30 15:54:44 +08:00
    数据存储使用的金额单位是分,代码中金额操作使用 BigDecimal;
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2564 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 10:27 · PVG 18:27 · LAX 02:27 · JFK 05:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.