PHP浮点数

PHP面试中, 经常会被问到的一个问题

<?php
    $f = 0.58;
    var_dump(intval($f * 100));
?>

上面输出的结果是57, 而不是58, 为什么呢, 因为 你看似有穷的小数, 在计算机的二进制表示里却是无穷的(鸟哥的原话),0.58用二进制后, 重新计算出来的值是:0.57999999999999996, 所以乘以100之后,去整数部分,就是57了。

代码中的intval改为floor后,输出的结果也是57

名字解释

  • BC是Binary Calculator的缩写, 二进制计算器
  • Sign 符号
  • Exponent 指数
  • Fraction 小数
  • IEEE 英语:Institute of Electrical and Electronics Engineers 电子技术与电子工程师协会,简称为IEEE。 IEEE is the world’s largest technical professional organization dedicated to advancing technology for the benefit of humanity. Below, you can find IEEE's mission and vision statements.

参考文章,鸟哥的两篇文章外加IEEE 754

IEEE 754 全称为,IEEE二进制浮点数算术标准,

此标准中,规定了浮点数二进制表示的规范:

浮点数二进制表示包括三部分,

  1. 符号位, 用1个字节来表示
  2. 指数位,
  3. 有效数字

如:

  • 单精度浮点数共32位(bit),1bit的符号位,8bit指数位,23bit有效数字
  • 双精度浮点数共64位(bit),1bit的符号位,11bit指数位,52bit有效数字

浮点数表示为二进制的计算方式是: 浮点数二进制表示学习笔记

整数部分除以2取余,然后再用所得的商除以2取余,一直到商为0,并且逆序排列所得的余数; 小数部分乘以2取整数部分,然后再用新的小数部分乘以二,取整数,一直到新的小数部分为0, 或者达到了要求的精度为止, 并且顺序排列所得的整数部分。

浮点数转化为二进制的例子

  1. 10.625转化为二进制

整数部分10, 对2求余, 商继续对2求余,直到商为0, 再逆序排列每一步得到的余数

计算

余数

10/2

0

5

5/2

1

2

2/2

0

1

1/2

1

0

10的二进制表示为1010

小数部分0.625, 乘以2, 取整数部分,新的小数部分继续乘以2, 直到新的小数部分为0或者达到一定精度,再顺序排列每一步得到整数部分。

计算

整数部分

小数部分

0.625 * 2 = 1.25

1

0.25

0.25 * 2 = 0.5

0

0.5

0.5 * 2 = 1

1

0

0.625的二进制表示为101

  1. 0.58的二进制表示 比如要求的精度是用53位来表示这个小数,可以得到如下表格:

计算

整数部分

小数部分

0.58 * 2 = 1.16

1

0.16

0.16 * 2 = 0.32

0

0.32

0.32 * 2 = 0.64

0

0.64

0.64 * 2 = 1.28

1

0.28

0.28 * 2 = 0.56

0

0.56

0.56 * 2 = 1.12

1

0.12

0.12 * 2 = 0.24

0

0.24

0.24 * 2 = 0.48

0

0.48

0.48 * 2 = 0.96

0

0.96

0.96 * 2 = 1.92

1

0.92

0.92 * 2 = 1.84

1

0.84

0.84 * 2 = 1.68

1

0.68

0.68 * 2 = 1.36

1

0.36

0.36 * 2 = 0.72

0

0.72

0.72 * 2 = 1.44

1

0.44

0.44 * 2 = 0.88

0

0.88

0.88 * 2 = 1.76

1

0.76

0.76 * 2 = 1.52

1

0.52

0.52 * 2 = 1.04

1

0.04

0.04 * 2 = 0.08

0

0.08

0.08 * 2 = 0.16

0

0.16

0.16 * 2 = 0.32

0

0.32

0.32 * 2 = 0.64

0

0.64

0.64 * 2 = 1.28

1

0.28

0.28 * 2 = 0.56

0

0.56

0.56 * 2 = 1.12

1

0.12

0.12 * 2 = 0.24

0

0.24

0.24 * 2 = 0.48

0

0.48

0.48 * 2 = 0.96

0

0.96

0.96 * 2 = 1.92

1

0.92

0.92 * 2 = 1.84

1

0.84

0.84 * 2 = 1.68

1

0.68

0.68 * 2 = 1.36

1

0.36

0.36 * 2 = 0.72

0

0.72

0.72 * 2 = 1.44

1

0.44

0.44 * 2 = 0.88

0

0.88

0.88 * 2 = 1.76

1

0.76

0.76 * 2 = 1.52

1

0.52

0.52 * 2 = 1.04

1

0.04

0.04 * 2 = 0.08

0

0.08

0.08 * 2 = 0.16

0

0.16

0.16 * 2 = 0.32

0

0.32

0.32 * 2 = 0.64

0

0.64

0.64 * 2 = 1.28

1

0.28

0.28 * 2 = 0.56

0

0.56

0.56 * 2 = 1.12

1

0.12

0.12 * 2 = 0.24

0

0.24

0.24 * 2 = 0.48

0

0.48

0.48 * 2 = 0.96

0

0.96

0.96 * 2 = 1.92

1

0.92

0.92 * 2 = 1.84

1

0.84

0.84 * 2 = 1.68

1

0.68

0.68 * 2 = 1.36

1

0.36

0.58 的二进制为: 10010100011110101110000101000111101011100001010001111

如上表格是通过如下 程序简单生成的:

$f = 0.58;
$b = '';
$i = 0;
while(true) {
    if ($i > 52) {
        break;
    }
    $i++;
    $str = "$f * 2 ";
    $tmp = $f * 2;
    $int = intval($tmp);
    $b .= $int;
    $f = round($tmp - $int, 3);
    $str .= "= $tmp | $int | $f \n";
    echo $str;
    if ($f == 0) {
        break;
    }
}

浮点数比较

看似两个相等的浮点数,其实进行比较时, 可能不想等了。

$f = 0.58;
$f2 = 1 - 0.42;
var_dump($f == $f2);
printf("%.21f \n", $f);
printf("%.21f \n", $f2);

如上代码大家觉着会输出什么呢? 其实输出的结果是:

bool(false)
0.579999999999999960032
0.580000000000000071054

所以在做浮点数比较的时候,要谨慎处理, 或者round四舍五入之后再比较。

精度输出

for($i=1;$i<=55; $i++) {
    printf("%d %.{$i}f\n", $i, 0.58);
}

如上代码,输出结果为:

1 0.6
2 0.58
3 0.580
4 0.5800
5 0.58000
6 0.580000
7 0.5800000
8 0.58000000
9 0.580000000
10 0.5800000000
11 0.58000000000
12 0.580000000000
13 0.5800000000000
14 0.58000000000000
15 0.580000000000000
16 0.5800000000000000
17 0.57999999999999996
18 0.579999999999999960
19 0.5799999999999999600
20 0.57999999999999996003
21 0.579999999999999960032
22 0.5799999999999999600320
23 0.57999999999999996003197
24 0.579999999999999960031971
25 0.5799999999999999600319711
26 0.57999999999999996003197111
27 0.579999999999999960031971113
28 0.5799999999999999600319711135
29 0.57999999999999996003197111349
30 0.579999999999999960031971113494
31 0.5799999999999999600319711134944
32 0.57999999999999996003197111349436
33 0.579999999999999960031971113494365
34 0.5799999999999999600319711134943645
35 0.57999999999999996003197111349436454
36 0.579999999999999960031971113494364545
37 0.5799999999999999600319711134943645447
38 0.57999999999999996003197111349436454475
39 0.579999999999999960031971113494364544749
40 0.5799999999999999600319711134943645447493
41 0.57999999999999996003197111349436454474926
42 0.579999999999999960031971113494364544749260
43 0.5799999999999999600319711134943645447492599
44 0.57999999999999996003197111349436454474925995
45 0.579999999999999960031971113494364544749259949
46 0.5799999999999999600319711134943645447492599487
47 0.57999999999999996003197111349436454474925994873
48 0.579999999999999960031971113494364544749259948730
49 0.5799999999999999600319711134943645447492599487305
50 0.57999999999999996003197111349436454474925994873047
51 0.579999999999999960031971113494364544749259948730469
52 0.5799999999999999600319711134943645447492599487304688
53 0.57999999999999996003197111349436454474925994873046875
PHP Notice:  printf(): Requested precision of 54 digits was truncated to PHP maximum of 53 digits in /data/cweb/2870000/campus_debug/website/v1/campus.imqq.cn/yongdehu_dev_api/protected/test.php on line3
54 0.57999999999999996003197111349436454474925994873046875
PHP Notice:  printf(): Requested precision of 55 digits was truncated to PHP maximum of 53 digits in /data/cweb/2870000/campus_debug/website/v1/campus.imqq.cn/yongdehu_dev_api/protected/test.php on line3
55 0.57999999999999996003197111349436454474925994873046875

其中

  1. i为54 和 55 时, PHP给了提示,并且输出结果和i=53时是一样的,最大支持小数点后53个小数。
  2. i 为1时,输出的是0.58四舍五入为只有一位小数的值,
  3. i 为17时才出现了0.57, 说明从16位之前2位之后的所有位四舍五入之后都是0.58

printf在输出浮点数时,会根据设定的位数来做四舍五入。

代码仅仅演示使用,文章内容不保证没有问题, 仅供参考。 欢迎交流指正。%

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java开发者杂谈

遍历算法(1)

遍历算法主要用在在处理迷宫问题,图,最短路径,以及枚举所有可能等问题上。下面我们通过一个简单的例子,来入门深度优先和广度优先算法: 1 package co...

38760
来自专栏落影的专栏

程序员进阶之算法练习(三十三)LeetCode专场

BAT常见的算法面试题解析: 程序员算法基础——动态规划 程序员算法基础——贪心算法 工作闲暇也会有在线分享,算法基础教程----腾讯课堂地址。 今天继续Lee...

12310
来自专栏Python小屋

Python模拟大整数乘法的小学竖式计算过程

让我们先看个图回顾一下小学学过的计算整数乘法的竖式计算过程 ? 然后再来看如何使用Python来模拟上面的过程,虽然在Python中计算任意大的数字乘法都没有问...

35850
来自专栏TensorFlow从0到N

讨厌算法的程序员 1 - 插入排序

什么是算法 在说插入排序之前,我们了解下《算法导论》对算法的从两种不同角度的定义。 一般性解释: 算法是定义良好的计算过程,它取一个或一组值作为输入,并产生...

34640
来自专栏好好学java的技术栈

“365算法每日学计划”:java语言基础题目及解答(11-15打卡)

自从开始做公众号开始,就一直在思考,怎么把算法的训练做好,因为思海同学在算法这方面的掌握确实还不够。因此,我现在想做一个“365算法每日学计划”。

12810
来自专栏owent

线段树相关问题 (引用 PKU POJ题目) 整理

如果(a < b – 1){分别计算a、b的次数和线段树[a + 1, b – 1)的次数,取大(小)的一项};

19720
来自专栏好好学java的技术栈

“365算法每日学计划”:java语言基础题目及解答(06-10打卡)

自从开始做公众号开始,就一直在思考,怎么把算法的训练做好,因为思海同学在算法这方面的掌握确实还不够。因此,我现在想做一个“365算法每日学计划”。

12320
来自专栏take time, save time

你所能用到的数据结构(四)

五、如何递,怎样归? 很多人看完递归的原理之后会有这种感觉,喔,这个原理我懂了,然后再找一道其余的题目看一看能不能写的出来,突然发现,我勒个去,还是不会。其实...

346100
来自专栏趣谈编程

快速排序(基础版)

16930
来自专栏欧阳大哥的轮子

常用的数学函数以及浮点数处理函数

在编程中我们总要进行一些数学运算以及数字处理,尤其是浮点数的运算和处理,这篇文章主要介绍C语言下的数学库。而其他语言中的数学库函数的定义以及最终实现也是通过对C...

21420

扫码关注云+社区

领取腾讯云代金券