为毛 JS 的浮点这么鬼畜的啊 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a Javascript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
Javascript 权威指南第 5 版
Closure: The Definitive Guide
outlaws
V2EX    Javascript

为毛 JS 的浮点这么鬼畜的啊

  •  
  •   outlaws 2016-08-01 18:08:13 +08:00 7522 次点击
    这是一个创建于 3362 天前的主题,其中的信息可能已经有所发展或是发生改变。

    var a = 0.05
    a += 0.01

    这时候 a 会等于 0.060000000000000000000005

    第 1 条附言    2016-08-24 17:28:07 +08:00
    新手入门学艺不精,多谢大家指点
    45 条回复    2016-08-16 17:38:57 +08:00
    luban
        1
    luban  
       2016-08-01 18:10:55 +08:00   1
    二进制保存小数问题,基本上编程语言都有类似的问题
    lovedebug
        2
    lovedebug  
       2016-08-01 18:10:57 +08:00   1
    JS 是双精度浮点数,用的 IEEE754 ,因而浮点数数值计算本来就不可能精确
    zoudm
        3
    zoudm  
       2016-08-01 18:17:24 +08:00 via iPhone
    因为浮点数是实数的近似表示。计算的时候就会产生误差。跟具体什么语言没有关系。

    C 里面可以自己决定使用 32 位的 float 或 64 位的 double , python 默认都是 64 位的浮点数。

    定义一个 double d = 0.1 ,再 printf("%.20lf", d),就可以知道浮点数是无法准确表示 0.1 的。
    CodingPuppy
        4
    CodingPuppy  
       2016-08-01 18:18:20 +08:00 via Android
    跟语言没关系
    br00k
        5
    br00k  
       2016-08-01 18:27:12 +08:00 via iPhone
    和语言没关系
    InFaNg
        6
    InFaNg  
       2016-08-01 19:14:44 +08:00 via Android
    js 日常背锅
    laoyur
        7
    laoyur  
       2016-08-01 19:24:20 +08:00
    In [1]: a = 0.05

    In [2]: b = 0.01

    In [3]: a + b
    Out[3]: 0.060000000000000005

    Python 表示这个锅有 js 帮背,真舒坦
    exoticknight
        8
    exoticknight  
       2016-08-01 19:34:03 +08:00
    心痛 js
    SourceMan
        9
    SourceMan  
       2016-08-01 19:48:10 +08:00 via iPhone
    日常背锅
    aristotll
        10
    aristotll  
       2016-08-01 19:48:43 +08:00
    Js 表示虽然我设计的差 但真不是我的锅啊
    FrankFang128
        11
    FrankFang128  
       2016-08-01 19:51:38 +08:00
    楼主找 block 么
    billlee
        12
    billlee  
       2016-08-01 19:58:39 +08:00
    自己计算机基础都没学好,还怪 js
    iVanilla
        13
    iVanilla  
       2016-08-01 20:12:05 +08:00
    我用 PHP 无法还原这个过程,不知什么原因。
    <?php
    $a = 0.05;
    $a += 0.01;
    echo $a;
    ?>
    输出: 0.06
    YuJianrong
        14
    YuJianrong  
       2016-08-01 20:17:45 +08:00
    @iVanilla 因为转成十进制字符串的保留小数位数不同……

    LZ 标题都说 JS 的“浮点”(而不是官方听起来有些模糊的“数字” Number )了,还说这些难道是钓鱼?
    这钩也太直了吧。
    iVanilla
        15
    iVanilla  
       2016-08-01 20:25:13 +08:00
    @YuJianrong 如果数值大一些, PHP 也可以出现类似现象,鸟哥在他的博客有解释: http://www.laruence.com/2013/03/26/2884.html

    如果说这帖子是钓鱼的话,只能说钩直饵咸。
    11138
        16
    11138  
       2016-08-01 20:25:36 +08:00
    @iVanilla
    <?php
    $a = 1.002;
    $a -= 1.001;
    echo $a;
    ?>
    zhanglintc
        18
    zhanglintc  
       2016-08-01 20:34:15 +08:00   2
    不知道你有没有听说过一个网站:
    http://0.30000000000000004.com/
    iVanilla
        19
    iVanilla  
       2016-08-01 20:35:41 +08:00
    @11138 这个确实可以,但 LZ 这个用 PHP 怎么试都还原不出来,我尝试加大小数位数都不能复现。
    原因不明。
    iVanilla
        20
    iVanilla  
       2016-08-01 20:41:56 +08:00
    @zhanglintc 看了你这个链接之后用 PHP 复现成功。
    <?php
    ini_set('precision', 17);
    $a = 0.05;
    $a += 0.01;
    echo $a;
    ?>

    官方的解释: http://php.net/manual/zh/ini.core.php#ini.precision
    浮点数中显示有效数字的位数。

    我看了下, PHP7 的默认值是 14.
    zhanglintc
        21
    zhanglintc  
       2016-08-01 20:52:57 +08:00
    @iVanilla PHP 不是很熟哦. 不过你一说我突然想起来, 当时 PHP 这段我把我看笑了. PHP 居然如此机智的避开了这个问题.
    russj
        22
    russj  
       2016-08-01 20:55:30 +08:00
    心疼 js
    11138
        23
    11138  
       2016-08-01 20:55:31 +08:00
    @iVanilla 是的,这个 precision 默认值在 php.ini 里。
    ljcarsenal
        24
    ljcarsenal  
       2016-08-01 21:09:29 +08:00 via Android
    不如认真读一下 csapp
    y
        25
    y  
       2016-08-01 21:15:28 +08:00   6
    0.01 和 0.05 都是二进制下的无限循环小数,没法用 64 bit 表示
    所以只能作为双精度浮点数存在

    0.01 表示为 0x3f847ae147ae147b, 也就是二进制的 1.0100011110101110000101000111101011100001010001111011 * 2^-7

    0.05 表示为 0x3fa999999999999a, 也就是二进制的 1.1001100110011001100110011001100110011001100110011010 * 2^-5

    用竖式加起来

    1.111010111000010100011110101110000101000111101011100011 * 2^-5

    注意这个结果已经比原来的小数长了两位(末尾的 11 ),现在要做四舍五入

    0.06 是
    1.1110101110000101000111101011100001010001111010111000 * 2^-5


    0.060000000000000005 是
    1.1110101110000101000111101011100001010001111010111001 * 2^-5

    显然后者的误差更小。

    最后吐槽一下楼主的态度:
    实际答案明明是 0.060000000000000005 ,
    楼主原帖多了六个 0 ,复制粘贴都不会吗,一定要手打?

    (我一定是知乎玩多了...)
    kideny
        26
    kideny  
       2016-08-01 21:20:43 +08:00
    楼主这被打脸的不清啊。
    iVanilla
        27
    iVanilla  
       2016-08-01 21:37:38 +08:00
    @zhanglintc 我开始以为之前很多人在 bugs.php.ne 反馈了,然后官方把这个值改了,不过我去 https://secure.php.net/releases/ 下载了十多年前的 PHP4.3.11 和 PHP5.0.0 ,发现这个值都是 14 。
    @11138 很奇怪为什么你那个例子在不改 precision 的情况下能复现。
    hasbug
        28
    hasbug  
       2016-08-01 21:38:46 +08:00
    IEEE 754
    11138
        29
    11138  
       2016-08-01 21:56:23 +08:00
    @iVanilla 设置 precision 影响显示的值的位数(整数部分和小数点部分),但是开始和结尾部分的 0 不算数。
    <?php
    $num = 0.012345600000000000;
    ini_set("precision", "12");
    echo $num; // 0.0123456
    echo "\n";
    ini_set("precision", "3");
    echo $num; // 0.0123
    echo "\n";
    ini_set("precision", "5");
    echo $num; // 0.012346
    echo "\n";
    ?>
    y
        30
    y  
       2016-08-01 21:58:49 +08:00   1
    @iVanilla 顺便说一句,如果有人要开发一套类似于支付宝的系统(或者 V2EX 的货币系统),那货币的金额一定要是 “ 1 分钱” 的整数倍,而不是有 “ 0.01 元” 这样的数据。所有的数据都要用整型表示,显示的时候再加上小数点,否则时间长了算账的时候会疯掉的。

    举个例子,消失的一元钱:

    >>> 9999999999999999.0 + 1 - 9999999999999999
    0.0
    maomaomao001
        31
    maomaomao001  
       2016-08-01 22:16:29 +08:00 via Android
    @kideny 什么叫被大脸????
    他不是在好好的问问题吗????
    br00k
        32
    br00k  
       2016-08-01 22:19:07 +08:00
    iVanilla
        33
    iVanilla  
       2016-08-01 22:20:29 +08:00
    @br00k 我前面的回复有提到鸟哥这篇文章的 url 。
        34
    em2046  
       2016-08-01 22:30:23 +08:00
    @y 何不用定点数 decimal BigDecimal
    gilgamesh
        35
    gilgamesh  
       2016-08-01 22:44:09 +08:00
    ychongsaytc
        36
    ychongsaytc  
       2016-08-01 23:12:44 +08:00   1
    「你看似有穷的小数, 在计算机的二进制表示里却是无穷的」
    ---from http://www.laruence.com/2013/03/26/2884.html
    kzzhr
        37
    kzzhr  
       2016-08-01 23:27:27 +08:00 via Android
    相当一部分语言算不好 0.1+0.2-0.3 我记得
    jeffersonpig
        38
    jeffersonpig  
       2016-08-02 08:51:00 +08:00
    LZ 大一?
    FrankHB
        39
    FrankHB  
       2016-08-02 10:03:49 +08:00
    @lovedebug 你数一下 0 的个数大概就不能指望是 IEEE-754 的双精度了,起码是扩展双精度。
    @iVanilla PHP 还真有相关的 bug ,而且曾经造成了比较严重的后果,不过具体表现和系统相关: https://bugs.php.net/53632
    lovedebug
        40
    lovedebug  
       2016-08-02 11:10:06 +08:00
    @FrankHB 双精度是说 JS 用 64 位 IEEE754 存储浮点数
    onceyoung
        41
    onceyoung  
       2016-08-02 11:54:56 +08:00 via iPhone
    这个跟语言什么关系?
    winglight2016
        42
    winglight2016  
       2016-08-02 11:58:06 +08:00
    @y 我做的微信商城的确是这样设计的
    swen
        43
    swen  
       2016-08-02 12:58:11 +08:00
    @y 最后的结果看着好像卖萌
    intsilence
        44
    intsilence  
       2016-08-02 15:54:20 +08:00
    Javascript : 怪我咯?
    miss61008596
        45
    miss61008596  
       2016-08-16 17:38:57 +08:00
    c#的 decimal 和 Java 的 BigDecimal 之所以没有出现精度差异,只是因为在其内部作了相应处理,把这种精度差异给屏蔽掉了,

    Javascript decimal.js big.js

    http://mikemcl.github.io/decimal.js/
    http://mikemcl.github.io/big.js/

    -勤于思,敏于行。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3376 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 04:32 PVG 12:32 LAX 21:32 JFK 00:32
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86