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 · 6721 次点击
    这是一个创建于 2814 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 。
    em2046
        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   ·   我们的愿景   ·   实用小工具   ·   5430 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 48ms · UTC 07:05 · PVG 15:05 · LAX 00:05 · JFK 03:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.