V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
xiaoyanbot
V2EX  ›  问与答

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

  •  
  •   xiaoyanbot · 2017-10-06 11:44:25 +08:00 · 1446 次点击
    这是一个创建于 2637 天前的主题,其中的信息可能已经有所发展或是发生改变。

    疑问:

    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);
    
    
    第 1 条附言  ·  2017-10-06 12:55:16 +08:00
    10 条回复    2017-10-06 14:44:10 +08:00
    ovear
        1
    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
        2
    xiaoyanbot  
    OP
       2017-10-06 12:17:15 +08:00
    @ovear

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

    请问这个舍入处理的规则, 具体是什么规则? 什么时候进行 加 1 修正? 还是 任何时候都会进行处理?
    ovear
        3
    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
        4
    xiaoyanbot  
    OP
       2017-10-06 12:50:30 +08:00
    @ovear

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

    我的疑问是:

    按照 [尾数修正规则] 情况下, 1.23 * 100 这个操作,正好恰巧修正成了 123.000000000**** 这个结果,对吗?
    xiaoyanbot
        5
    xiaoyanbot  
    OP
       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
        6
    ovear  
       2017-10-06 12:58:28 +08:00
    @xiaoyanbot 首先,浮点数超出了精度范围的数字,是被认为无效的,或者说没有意义的。
    所以在有效范围内
    1.23 ( 14 位有效位数)

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

    IEEE 754 用的是 舍入处理吗?
    ovear
        8
    ovear  
       2017-10-06 13:30:54 +08:00
    @xiaoyanbot 没有强制规定
    azh7138m
        9
    azh7138m  
       2017-10-06 14:40:02 +08:00 via Android
    @xiaoyanbot 截断和舍入指的是 printf 这种函数在把它转换成字符表示时的处理方式
    ovear
        10
    ovear  
       2017-10-06 14:44:10 +08:00
    @azh7138m 不是的,是指的计算结果的存储
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5316 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 07:27 · PVG 15:27 · LAX 23:27 · JFK 02:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.