Press "Enter" to skip to content

PHP浮点数的一个常见问题的解答

关于PHP的浮点数, 我之前写过一篇文章: 关于PHP浮点数你应该知道的(All ‘bogus’ about the float in PHP)
不过, 我当时遗漏了一点, 也就是对于如下的这个常见问题的回答:

<?php
    $f = 0.58;
    var_dump(intval($f * 100)); //为啥输出57
?>

为啥输出是57啊? PHP的bug么?
我相信有很多的同学有过这样的疑问, 因为光问我类似问题的人就很多, 更不用说bugs.php.net上经常有人问...
要搞明白这个原因, 首先我们要知道浮点数的表示(IEEE 754):
浮点数, 以64位的长度(双精度)为例, 会采用1位符号位(E), 11指数位(Q), 52位尾数(M)表示(一共64位).
符号位:最高位表示数据的正负,0表示正数,1表示负数。
指数位:表示数据以2为底的幂,指数采用偏移码表示
尾数:表示数据小数点后的有效数字.
这里的关键点就在于, 小数在二进制的表示, 关于小数如何用二进制表示, 大家可以百度一下, 我这里就不再赘述, 我们关键的要了解, 0.58 对于二进制表示来说, 是无限长的值(下面的数字省掉了隐含的1)..

0.58的二进制表示基本上(52位)是: 0010100011110101110000101000111101011100001010001111
0.57的二进制表示基本上(52位)是: 0010001111010111000010100011110101110000101000111101

而两者的二进制, 如果只是通过这52位计算的话,分别是:

0.58 -> 0.57999999999999996
0.57 -> 0.56999999999999995

至于0.58 * 100的具体浮点数乘法, 我们不考虑那么细, 有兴趣的可以看(Floating point), 我们就模糊的以心算来看... 0.58 * 100 = 57.999999999
那你intval一下, 自然就是57了....
可见, 这个问题的关键点就是: "你看似有穷的小数, 在计算机的二进制表示里却是无穷的"
so, 不要再以为这是PHP的bug了, 这就是这样的.....

85 Comments

  1. […] PHP 的浮点数是不能精确计算的,具体的可以看这篇文章。所幸的是,金额一般不会有太多的小数。那么存储的时候呢,一言以蔽之,以 分 为单位进行存储。在 MySQL 中,以 int 类型存储就行了(视情况选择字段类型)。 […]

  2. […] PHP 的浮点数是不能精确计算的,具体的可以看这篇文章。所幸的是,金额一般不会有太多的小数。那么存储的时候呢,一言以蔽之,以 分 为单位进行存储。在 MySQL 中,以 int 类型存储就行了(视情况选择字段类型)。 […]

  3. superwei
    superwei November 2, 2018

    $f = 0.57;
    var_dump(intval($f * 100)); //为啥输出56
    $f = 0.56;
    var_dump(intval($f * 100)); //为啥输出56
    $f = 0.55;
    var_dump(intval($f * 100)); //为啥输出55

  4. soul11201
    soul11201 October 18, 2018

    0.58 二进制结果有误,应为 ee8b69658
    #include
    int main() {
    double a = 0.58;
    printf(“%lu,%p”,sizeof(a), a);
    return 0;
    }
    $ ./a.out
    8,0x7ffee8b69658%

    • soul11201
      soul11201 October 18, 2018

      sorry 程序写错了。撤回我刚才说的。。。。。
      $ ./a.out
      8,3fe28f5c28f5c28f
      4,3f147ae1
      #include
      int main() {
      double a = 0.58, *aptr = &a;
      float af = 0.58, *afptr = &af;
      printf(“%lu,%lx\n”,sizeof(a), *(long int *)aptr);
      printf(“%lu,%x\n”,sizeof(af), *(int *)afptr);
      // printf(“%lu,%d”,sizeof(a),(int) (a * 100) );
      // int b = 21;
      // printf(“%lu, %p\n”, sizeof(b), b);
      // printf(“%lu, %d\n”, sizeof(b), (int)(float) b);
      return 0;
      }

  5. gfds
    gfds December 21, 2017

    f这个对我帮助比较大,哈哈。谢鸟哥~

  6. fdsafd
    fdsafd December 21, 2017

    这个对我帮助比较大,哈哈。谢鸟哥~

  7. dsafdsa
    dsafdsa December 21, 2017

    研究wp,想学点php,看到这么多人讨论的都是比较高深

  8. 天涯
    天涯 December 21, 2017

    研究wp,想学点php,看到这么多人讨论的都是比较高深的

  9. yungkit
    yungkit October 1, 2015

    其实就是精度截取的问题,哈哈

  10. master
    master March 19, 2015

    这个浮点数问题是,不是php特有的

  11. […] 之前在项目做订单模块的时候,经常会出现xx.01等到厘的金额,这块就会有浮点的陷阱,别小看几厘哦,在基数比较大的支付情况下,将会是很大的数字。有篇文章分享下给大家,摘自:风雪之隅。 […]

  12. seo Hitchin
    seo Hitchin August 24, 2014

    Nice blog here! Alsso your site loads up fast!
    Whhat host are you using? Can I get your affilijate link
    to your host? I wish mmy site loaded up ass fast as yours
    lol
    Feel free to visitt my web site – seo Hitchin

  13. Star
    Star October 23, 2013

    最近的 php5.4 应该修复了吧?

  14. 於志远
    於志远 July 28, 2013

    这个对我帮助比较大,哈哈。谢鸟哥~

  15. 微历史
    微历史 July 24, 2013

    研究wp,想学点php,看到这么多人讨论的都是比较高深的

  16. Anonymous
    Anonymous July 5, 2013

  17. 达达尼亚
    达达尼亚 July 3, 2013

    其实也不是都这样,为了保险起见,是不是都要 先用 round函数一下啊?

  18. 知知了了
    知知了了 July 3, 2013

    那为什么 var_dump(intval(0.58 * 1000));exit; 出来的不是579而是580呢?
    求解。。。

  19. dog
    dog June 29, 2013

    your blog is word press Version 3.0.3 .

  20. bliwy
    bliwy June 6, 2013

    perl对正则表达式的优化可以做到基于trie
    use Regexp::Optimizer;
    my $o = Regexp::Optimizer->new->optimize(qr/foobar|fooxar|foozap/);
    # $re is now qr/foo(?:[bx]ar|zap)/
    php可以做到么?有没有对应的第三方包?

  21. Beckham
    Beckham June 3, 2013

    大大 会不会在新的php版本中 有所更新或者其他什么表现?

  22. wclssdn
    wclssdn May 24, 2013

    var_dump((0.8) * 10 == (0.1 + 0.7) * 10); //false
    鸟哥. 这个是为啥啊? 求解答..

  23. […] Written by phpjava. Posted in 杂文分享 最近听到很多同事聊“浮点型”的数据好像经常会出现各种问题,在“Laruence”的PHP浮点数的一个常见问题的解答的博客中解释了这个东西,还是先说说小数如何用二进制表示的问题,后面具体说说浮点的问题 […]

  24. 大饼
    大饼 May 16, 2013

    计算机里,浮点数一般都是近似值的。除了0.5,0.25,0.125这样的正好是2的整数幂分之一的书是精确的。

  25. 红色石头
    红色石头 May 13, 2013

    新浪sae备案之后真是快啊,膜拜博客新浪首席…待业应届研究生~

  26. 霸气千秋
    霸气千秋 May 8, 2013

    之前在群里边看过这个问题,受限于计算机的计算模式

  27. vb2005xu
    vb2005xu May 8, 2013

    为什么 只有 0.58 0.57 有这个问题呢

  28. 蚂蚁
    蚂蚁 May 8, 2013

    这让我想起了一个基础的计算机试题,如果得到整数的算术平方根

  29. xuanskyer
    xuanskyer April 29, 2013

    鸟叔,0.58的二进制第一位不应该为1么?你说的“下面的数字省掉了隐含的1”是说第一位的1被省略?

  30. michael
    michael April 29, 2013

    昨天定程序就遇到这个问题,旁边的同事给我解决明白了,没想到鸟哥也出来科普了。呵呵

  31. abc
    abc April 27, 2013

    膜拜。

  32. Anonymous
    Anonymous April 27, 2013

    膜拜。

  33. lyongde
    lyongde April 23, 2013

    是这样的。

  34. karocxing
    karocxing April 19, 2013

    还有,echo intval(0.57 * 100) 和 var_dump(intval(0.57 * 100))结果都是56。。。
    0.58的也都是57。。。

  35. karocxing
    karocxing April 19, 2013

    话说,echo 出来是正确的几位同学,为什么要echo 0.58 * 100?
    而不是 echo intval(0.58 * 100) 呢?
    汗一个先。

  36. ppoo24
    ppoo24 April 16, 2013

    @pengzhen 不好意思,我刚才的推测,应该是错误的

  37. ppoo24
    ppoo24 April 16, 2013

    @pengzhen 因为这个例子是$f = 0.58;是有个赋值动作的,也就是0.58会被存储,而存储就将以二进制保存。你如果直接输出0.58 * 100这个操作,是不会有存储这步

  38. pengzhen
    pengzhen April 12, 2013

    还能再问你个问题吗,你博客里的那些php代码高亮是怎么实现的

  39. pengzhen
    pengzhen April 12, 2013

    那为什么直接echo出来的是正确的了,echo 0.58*100 得出的就是58,为什么不是57.9999999996

  40. gaodi07
    gaodi07 April 9, 2013

    小数点转二进制。。。不知道咋转,受教了

  41. Anonymous
    Anonymous April 8, 2013

    $ php -r “var_dump(intval(57.999999999999999));”;
    int(58)
    $ php -r “var_dump(intval(57.99999999999999));”;
    int(57)

  42. Anonymous
    Anonymous April 7, 2013

    看来还是计算机科班出身的好

  43. kevin
    kevin April 6, 2013

    php> = intval(0.58 * 100 )
    57
    php> = intval(0.58 * 100 . ” )
    58
    转string时发生了什么?btmath是如何解决这个问题的?

  44. wxf
    wxf April 1, 2013

    echo会自动判断这种情况吗?因为同样intval(.57*100)值是56,但是echo出来是57

  45. mcfog
    mcfog March 27, 2013

    javascript:alert(parseInt(0.58*100))
    JS的结果也是一样的

  46. bright
    bright March 26, 2013

    不仅仅php,java也是这样的~ 面试的人都喜欢这些啊。

  47. Anonymous
    Anonymous March 26, 2013

    另一个例子:
    $n=”19.99″;
    intval($n * 100); // prints 1998
    intval(strval($n * 100)); // prints 1999
    printf(“%.13f”, $n * 100);// prints 1998.9999999999998

  48. Melo618
    Melo618 March 26, 2013

    另一个例子:

  49. kevin
    kevin March 26, 2013

    function _intval($n){
    return floor(floatval($n));
    }
    这样如何?

  50. wy
    wy March 26, 2013

    别用intval,用round …

  51. 花生
    花生 March 26, 2013

    好东西,必须弄懂的

  52. xxx
    xxx March 26, 2013

    那这种情况如何避免? 浮点和整数之间的计算还是很多的..

  53. 梦康
    梦康 March 26, 2013

    汗,原来还得先转成二进制。小数转二进制,真不知道。说实话这个问题我们这种低级打字员用得比较少,但是知道也好。

  54. FtMan
    FtMan March 26, 2013

    受教,回头再详细查查

Leave a Reply to Melo618 Cancel reply

Your email address will not be published. Required fields are marked *