V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
drymonfidelia
V2EX  ›  JavaScript

避免 Math.ceil(1.1 * 100) == 111 的最佳实践是什么?用户支付金额不对不能入账,排查了半天才发现 JS 这个逆天设计,好像别的语言也有这样的

  •  1
     
  •   drymonfidelia · 63 天前 · 4984 次点击
    这是一个创建于 63 天前的主题,其中的信息可能已经有所发展或是发生改变。
    42 条回复    2024-10-08 16:29:07 +08:00
    rrfeng
        1
    rrfeng  
       63 天前 via Android   ❤️ 6
    基础没学好,回去重修。

    ---
    解决方法就是钱永远别用小数。
    AoEiuV020JP
        2
    AoEiuV020JP  
       63 天前   ❤️ 2
    第一反应精度丢失问题,转念想怎么会差这么多, 仔细一看,ceil ,你确定这不是设计如此有人贪这差值吗,
    Coelacanthus
        3
    Coelacanthus  
       63 天前
    计算金融不要用二进制浮点数,二进制浮点数的设计就没法精确表示十进制有理数。用十进制浮点数或者定点数,因为金融业务很少用到分以下,用定点数的比较多。十进制浮点数的支持状态也不太好。
    TomVista
        4
    TomVista  
       63 天前
    biginit
    icyalala
        5
    icyalala  
       63 天前   ❤️ 2
    "1.1" 是十进制,当这个值转为数字的时候是无限循环小数 1.0001100110011001100110011...
    舍入后就是 1.0001100110011001100110011001100110011001100110011010, 注意最后一位是向上舍入的。
    所以最开始从 "1.1" 这个字符串解析到数字的时候就已经不准确了。

    属实是计算机基础不扎实。
    pinocc012
        6
    pinocc012  
       63 天前
    内部数据应该用分为单位的整数吧,显示的时候转换
    c8c
        7
    c8c  
       63 天前
    `npm install decimal.js`
    xiangyuecn
        8
    xiangyuecn  
       63 天前   ❤️ 1
    既然用了浮点数 ceil ,哪就不得不得不掏出祖传的 0.30000000000000004.com

    php -r var_dump(ceil(1.1*100));
    //float(111)
    chobitssp
        9
    chobitssp  
       63 天前
    bignumber.js
    cmdOptionKana
        10
    cmdOptionKana  
       63 天前
    凡是涉及金额,都不能简单计算。

    一个金额,一个日期,这两个是很典型新手误区,表面上看起来没什么,实际上都藏着大坑。
    haolongsun
        11
    haolongsun  
       63 天前
    金额永远别用浮点数 用 decimal
    haolongsun
        12
    haolongsun  
       63 天前
    二进制只能近似存储小数,详细重新学习 IEEE754 ,还有以前面试区分培训和科班第一个就让说 IEEE754 ,不知道的一定是培训出来的,因为培训班不会说什么浮点数底层怎么实现,而是会说金额不让用 double ,科班必定知道,计组第一章就是吧
    importmeta
        13
    importmeta  
       63 天前
    只用数据库计算
    fiveStarLaoliang
        14
    fiveStarLaoliang  
       63 天前
    string 或者整形存储,计算时得注意精度丢失问题
    iOCZS
        15
    iOCZS  
       63 天前   ❤️ 2
    " JS 这个逆天设计",很野的说法
    msg7086
        16
    msg7086  
       63 天前   ❤️ 1
    与其说语言的逆天设计,不如说只知道给金额用浮点数的程序员比较逆天吧。
    IvanLi127
        17
    IvanLi127  
       63 天前
    这个不是常见的面试题么,没见过嘛?

    改用 Decimal.js 吧。https://www.npmjs.com/package/decimal.js/v/10.4.3

    其他语言也有类似的库,推荐直接用这种方案来做计算。
    masterclock
        18
    masterclock  
       63 天前   ❤️ 3
    https://github.com/kdeldycke/awesome-falsehood
    建议任何程序员写代码前都读一遍

    涉及金额,只设计人民币,可以考虑 decimal ,最好是专门的类型
    est
        19
    est  
       63 天前
    测了下 1.1 * 100 == 110.00000000000001 向上取整所以是 111 没问题啊。。。

    问题在于 LZ 你为啥要向上取整啊。。round 不行么。
    drymonfidelia
        20
    drymonfidelia  
    OP
       63 天前
    @est 因为商品价格 100 美元,税 10%,业务需求是向上取整到整数
    DOLLOR
        21
    DOLLOR  
       63 天前
    浮点运算你换什么语言都一样。
    涉及财务、金融的计算,*不能用浮点数*,要用*定点数*,这应该是程序员的常识、共识。
    ntedshen
        22
    ntedshen  
       63 天前   ❤️ 1
    “业务需求是向上取整到整数”?
    简单,
    Math.ceil(Math.round(1.1*10000)/100)
    然后下一届码农:md 屎山+1 (狗头
    drymonfidelia
        23
    drymonfidelia  
    OP
       63 天前   ❤️ 1
    @ntedshen 我真的改成了这样 (狗头
    mingl0280
        24
    mingl0280  
       63 天前
    涉及钱币为什么要用浮点数?
    rogerer
        25
    rogerer  
       63 天前
    @DOLLOR 但其实 Excel 里的财务函数也是用的浮点数,他们要求用户通过舍入函数自行控制精度。
    geelaw
        26
    geelaw  
       63 天前 via iPhone
    @drymonfidelia #20 正确的做法是金额存成 cPrice = 10000 美分,税计算为 ((cPrice * 10000 * bpTaxRate - 1) / 10000 + 1) 美分,其中 bpTaxRate 是税率的基点数(万分之几)。这里假设 cPrice 是非负数(销售),处理退款更麻烦。
    geelaw
        27
    geelaw  
       63 天前 via iPhone
    @geelaw #26 ugh 税是

    (cPrice * bpTaxRate - 1) / 10000 + 1

    脑子糊了
    whileFalse
        28
    whileFalse  
       63 天前 via Android
    lz 培训班的,鉴定完毕
    lovestudykid
        29
    lovestudykid  
       63 天前
    计算机就业状况还是太好了
    lithiumii
        30
    lithiumii  
       63 天前 via Android
    ceil 是上取整,你就算是不懂浮点数,用四舍五入这个 bug 也会少很多
    lee88688
        31
    lee88688  
       63 天前
    #22 的做法其实是正确的,不算什么屎山。使用二进制浮点数表示十进制浮点数在一定有效位数下可以认为是正确的,这个有效位数在 64 位浮点数下大概是 12 位。
    在这些位数下转换成整数,然后将整数和小数部分取出来做处理完全没问题,楼主只需要写一些注释,后续人理解没问题的。
    lee88688
        32
    lee88688  
       63 天前
    多说一句,这种是将浮点数进行最终处理和简单时的做法。因为这些误差在累积计算的时候可能会让偏差越来越大,因此计算的时候还是使用浮点数需要做额外的处理,最好还是使用十进制的库计算或者用 bigint 转化为整数计算。
    ahu
        33
    ahu  
       63 天前
    如 #8 所说,去看看这个网站:

    location.href = 'http://' + (.1 + .2) + '.com';
    Curtion
        34
    Curtion  
       63 天前
    这是 IEEE754 的问题,金额相关不要用浮点数,要么使用 Decimal ,要么浮点数拆开成两个整数分开存储
    mdn
        35
    mdn  
       63 天前
    采用 IEEE 754 浮点数运算的标准的语言 都会出现这个问题,包括 JavaScript Java Python 等
    建议使用数学库进行运算
    lyxxxh2
        36
    lyxxxh2  
       63 天前   ❤️ 1
    你不该说逆天设计,不是找喷吗。
    对金额敏感,用数学库,big.js 之类。
    wu67
        37
    wu67  
       63 天前
    npm install mathjs -S
    EndlessMemory
        38
    EndlessMemory  
       63 天前
    0.1+0.2==0.3 ?
    sastar
        39
    sastar  
       63 天前
    判断 0.1+0.2==0.3 的正确用法应该是 abs(0.1+0.2-0.3)<0.000001 ,具体在小数点后多少位取决于你对精度的要求
    winglight2016
        40
    winglight2016  
       63 天前   ❤️ 1
    有点不理解 lz 这个代码是前端还是后端?如果是后端,从数据库设计开始就肯定用整数保存金额啊,包括银行也是这样。

    如果是前端,那根本就不该去做计税这个操作——正确的设计是把用户输入全部发送到后端去计算。
    thtznet
        41
    thtznet  
       61 天前
    拿 JS 这种前端语言计算金额?还拿去对账?逆天考量。
    zhhbstudio
        42
    zhhbstudio  
       53 天前
    @xiangyuecn #8 好东西,收藏了,哈哈哈
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2451 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 04:51 · PVG 12:51 · LAX 20:51 · JFK 23:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.