【问题标题】:Understanding floating point numbers in php了解php中的浮点数
【发布时间】:2023-03-11 23:26:01
【问题描述】:

我知道这些问题可能会被问到很多,但从我的阅读和测试来看,这让我有点困惑,而且我所做的很多阅读让我更加困惑,因为它非常复杂。

有些人似乎对简单的比较有问题,但我自己没有问题。

例如...

$num1 = 27.64;
$num2 = 27.64;

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's "Good!"

...和

$num1 = 27.60;
$num2 = 27.6;

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's Good

...和

$num1 = 27.60;
$num2 = 57.60;

if ($num1 <= $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's Good

...和

$num1 = 25.00;
$num2 = 12.50 + 12.5;

if ($num1 == $num2) {
    echo 'Good!';
} else {
    echo 'Bad!';
}

// Echo's Good

然后我看到像http://patchlog.com/php/comparing-float-values-in-php/ 这样的页面似乎有简单的问题,但我不明白。

我只是想了解他的简单代码是如何遇到问题的,但我不喜欢我的。

【问题讨论】:

    标签: php floating-point floating-accuracy


    【解决方案1】:

    浮点错误仅在存在数学结果无法以浮点精确表示的运算时发生。错误被精确定义;它们不是随机的或任意的,因此当执行相同的操作时会产生相同的结果。

    在您的第一个示例中,您将“27.64”分配给 $num1 和 $num2。这里有一个操作:解析器必须解释字符串“27.64”并产生一个浮点结果。解析器很可能会生成最接近 27.64 的浮点数。 (作为十六进制浮点数,该数字为0x1.ba3d70a3d70a4p+4。“p”之前的部分是十六进制数字,带有小数部分。“p4”表示乘以24. 作为十进制数字,它是 27.6400000000000005684341886080801486968994140625。)并且在两种情况下产生相同的数字,因此 $num1 与 $num2 的比较表明它们彼此相等,尽管两者都不等于 27.64(因为 27.64 不能精确地用浮点数表示)。

    在您的第二个示例中,最接近数字“27.60”的值的浮点数与最接近数字“27.6”的值的浮点数相同,因为这两个数字代表相同的值。所以,同样,你会得到相同的结果。

    在您的第三个示例中,两个数字的值相距甚远,因此您得到不同的浮点数,比较表明它们不相等。

    在您的第四个示例中,所有值都可以用浮点数精确表示,因此没有错误。 25、12.50、12.5都是2的幂的小倍数(包括负指数的幂,比如0.5=2-1,在浮点类型的范围内,所以它们是可表示的。另外,12.50和12.5之和是完全可表示的,所以相加时没有舍入误差。因此,所有结果都是准确的,比较表明总和等于25。

    当人们期望从两个具有相同数学结果的不同计算中得到相同的结果时,就会出现问题。一个经典的例子是将“.3”与“.1+.2”进行比较。将数字“.3”转换为浮点会产生最接近的可表示值,即 0x1.3333333333333p-2 (0.299999999999999988897769753748434595763683319091796875),略低于 0.3。将“.1”转换为浮点会产生最接近的可表示值,即 0x1.999999999999ap-4 (0.1000000000000000055511151231257827021181583404541015625),略高于 0.1。将“.2”转换为浮点会产生最接近的可表示值,即 0x1.999999999999ap-3 (0.200000000000000011102230246251565404236316680908203125),略高于 0.2。添加后两个值会产生最接近它们总和的可表示值,即 0x1.3333333333334p-2 (0.3000000000000000444089209850062616169452667236328125)。如您所见,该总和与将“.3”转换为浮点获得的值不同,因此比较它们表明它们不相等。

    【讨论】:

      【解决方案2】:

      示例 1

      这些值将是相同的——您为每个变量分配相同的十进制文字。将其与此代码进行比较:

      $num1 = 27.64;
      $num2 = 10.0 + 2.88 + 2.88 + 2.88 + 9.0; //In decimal arithmetic adds to 27.64
      
      if ($num1 == $num2) {
          echo 'Good!';
      } else {
          echo 'Bad!';
      }
      
      // Echo's "Bad!"
      

      $num2 看起来应该是 27.64,但它确实增加了 27.639999999999997015720509807579219341278076171875 (这就是我在我的机器上用 Visual C++ 进行计算时得到的结果)。 $num1 = 27.6400000000000005684341886080801486968994140625(在我的机器上),所以它们不同。

      示例 2

      后面的 0 没有区别。

      示例 3

      数字不在浮点“公差”范围内,所以当然会有所不同。

      示例 4

      12.5 完全可以用浮点表示,所以 12.5 + 12.5 也是(0.5 是 2^-1)。

      【讨论】:

      • 很好的解释。您对使用浮点数有什么技巧可以避免此类问题吗?
      • @Brett:在 stackoverflow 和其他地方有大量关于这个主题的信息。我的博客exploringbinary.com(特别是以exploringbinary.com/topics/… 开头)是关于十进制到浮点转换期间发生的舍入错误的一个地方。
      【解决方案3】:

      你可以试试

       $a = '12.30';
      

      作为字符串获得完全匹配,否则默认情况下浮动框删除结尾'0'。

      【讨论】:

      • 只要你用它做任何数学运算,它就会崩溃。
      【解决方案4】:

      这是一个明显的例子:

      $a = 0;
      for ($i = 0; $i < 100000; $i++) {
          $a += 0.00001;
      }
      print("$a\n");
      

      您可能希望打印出1,但实际上输出是0.99999999999808

      (结果是在 x86_64 架构上)

      【讨论】:

        【解决方案5】:

        前两个具有编译器提供的值,它将两个数字解析为相同的位模式。

        我不会触及第三个,因为它的工作原理应该很明显。

        对于第四个,使用的值具有定义明确、完全准确的位模式。尝试更多地使用数字。

        【讨论】:

          【解决方案6】:

          浮点数越大(或越小),精度越低。精确度会因处理器架构而异。

          尝试在 1E30 或 1E-30 进行所有测试...

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-09-04
            • 1970-01-01
            • 2011-06-07
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-05-30
            • 1970-01-01
            相关资源
            最近更新 更多