(求解读源码) PHP 的 * 运算符会对 float 精度问题进行猜测修正吗?

2017-10-06 11:44:25 +08:00
 xiaoyanbot

疑问:

1.23 这个数值,在 float 在内部二进制转换时已经失真,为什么乘以 100 之后,最终的高精度表示结果 不是 1.2299999**** 类似这样的,而是 1.23000000000 这个结果呢?

PHP 的乘法运算符进行了猜测修正吗?
为什么乘以 100,最终结果是保真的, 乘以 10,不是保真的呢?
乘法运算符的代码: https://github.com/php/php-src/blob/master/Zend/zend_multiply.h
求大神解读。

乘 100 执行结果为:


乘 10 执行结果为:

 [运行结果在 PHP5.4、5.6 和 7.1 上无差异,另外, 乘以 10 的结果是不保真的] 

运行代码为:

<?php

echo "<meta charset='utf-8'>";

$a = 1.23;
$b = 100;  //第二次运行 $b = 10;

echo '
<pre>
$a = 1.23;
$b = 100;
</pre><hr>
';

echo '$a 的高精度数值为:';
printf("%0.30f", $a);
echo '<hr>';
echo '$b 的高精度数值为:';
printf("%0.30f", $b);

echo "<br><br><hr>";

echo '$a 的数据类型:  ';
var_dump($a);echo "<hr>";
echo '$b 的数据类型:  ';
var_dump($b);echo "<hr>";

$total = $a * $b;

echo '执行的计算为:
<pre>
$total = $a * $b;
</pre>
';

echo "<br><br><hr>";

echo '计算结果:$total 为  ';
var_dump($total);echo "<hr>";
echo '高精度计算结果:  ';
printf("%0.99f", $total);

1388 次点击
所在节点    问与答
10 条回复
ovear
2017-10-06 12:10:27 +08:00
http://www.cnblogs.com/FlyingBread/archive/2009/02/15/660206.html

5.2 浮点数的乘除法
( 1 )阶码运算:阶码求和(乘法)或阶码求差(除法)
即 [Ex+Ey]移= [Ex]移+ [Ey]补
[Ex-Ey]移= [Ex]移+ [-Ey]补
( 2 )浮点数的尾数处理:浮点数中尾数乘除法运算结果要进行舍入处理
例题:X=0 .0110011*211,Y=0.1101101*2-10 求 X*Y
解:[X]浮:0 1 010 1100110
[Y]浮:0 0 110 1101101
( 1 )阶码相加
[Ex+Ey]移=[Ex]移+[Ey]补=1 010+1 110=1 000
1 000 为移码表示的 0
( 2 )原码尾数相乘的结果为:
0 10101101101110
( 3 )规格化处理:已满足规格化要求,不需左规,尾数不变,阶码不变。
( 4 )舍入处理:按舍入规则,加 1 进行修正
所以 X ※ Y= 0.1010111*20
xiaoyanbot
2017-10-06 12:17:15 +08:00
@ovear

~~~
舍入处理:按舍入规则,加 1 进行修正
~~~

请问这个舍入处理的规则, 具体是什么规则? 什么时候进行 加 1 修正? 还是 任何时候都会进行处理?
ovear
2017-10-06 12:22:33 +08:00
@xiaoyanbot 这个跟 PHP 没有关系,是计算方法产生的误差。
http://218.5.241.24:8018/C35/Course/ZCYL-HB/WLKJ/jy/Chap02/2.7.2.htm
详细可以看这里


很显然 LZ 对高精度有一定误解,注意一下 PHP 官网的这段话

http://php.net/manual/zh/language.types.float.php

浮点数的字长和平台相关,尽管通常最大值是 1.8e308 并具有* 14 位十进制数字的精度*( 64 位 IEEE 格式)。



```
<?php
$a = 1.23;
var_dump($a);
printf('%.13f', $a);
```

lz 执行下这个就知道了
xiaoyanbot
2017-10-06 12:50:30 +08:00
@ovear

谢谢解答。
精度丢失这个问题我知道, 我的代码里也有如上您给出的代码的表示。

我的疑问是:

按照 [尾数修正规则] 情况下, 1.23 * 100 这个操作,正好恰巧修正成了 123.000000000**** 这个结果,对吗?
xiaoyanbot
2017-10-06 12:53:47 +08:00
~~~

(2) 尾数处理
浮点加减法对结果的规格化及舍入处理也适用于浮点乘除法。
第一种简单的办法是,无条件地丢掉正常尾数最低位之后的全部数值。这种办法被称为截断处理,其好处是处理简单,缺点是影响结果的精度。
第二种简单办法是,运算过程中保留中移出的若干高位的值,最后再按某种规则用这些位上的值修正尾数,这种处理方法被称为舍入处理。
当尾数用原码表示时,舍入规则比较简单。最简便的方法,是只要尾数最低位为 1,或移出的几位中有为 1 的数值位,就使最低位的值为 1。另一种是 0 舍 1 入法,即当丢失的最高位的值为 1 时,把这个 1 加到最低数值位上进行修正,否则舍去丢失的各位的值。这样处理时,舍入效果对负数是相同的,入将使数的绝对值变大,舍则使数的绝对值变小。
当尾数是用补码表示时,所用的舍入规则,应该与用原码表示时产生相同的处理效果,具体规则是:
当丢失的各位均为 0 时,不必舍入;
当丢失的最高位为 0,以下各位不全为 0 时,或者丢失的最高位为 1,以下各位均为 0 时,则舍去丢失位上的值;
当丢失的最高位为 1,以下各位不全为 0 时,则执行在尾数最低位入 1 的修正操作。

~~~
ovear
2017-10-06 12:58:28 +08:00
@xiaoyanbot 首先,浮点数超出了精度范围的数字,是被认为无效的,或者说没有意义的。
所以在有效范围内
1.23 ( 14 位有效位数)

1.2999999999999 ( 14 位有效位数)
的 IEE 754 表示是不同的,他们不是一个数字。
所以这样的结果再加上上面的计算方法,计算结果是能确保修正到 [预期值] 的。
xiaoyanbot
2017-10-06 13:06:17 +08:00
@ovear
课件中提到 两种尾数修正方法: [截断处理] 和 [舍入处理]

IEEE 754 用的是 舍入处理吗?
ovear
2017-10-06 13:30:54 +08:00
@xiaoyanbot 没有强制规定
azh7138m
2017-10-06 14:40:02 +08:00
@xiaoyanbot 截断和舍入指的是 printf 这种函数在把它转换成字符表示时的处理方式
ovear
2017-10-06 14:44:10 +08:00
@azh7138m 不是的,是指的计算结果的存储

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/395625

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX