如何比较浮点数是否与一组浮点范围?

How to compare if a floating point number against a set of floating point ranges?

例如,我有 4 个范围:

  1. 0 - 1.25
  2. 1.26 - 2.45
  3. 2.46 - 5
  4. 5.01 - 无限

我有一个浮点数可以与之比较:1.2549999999。

我需要检查这个数字属于哪个范围。

我有以下代码,但我不确定它是否足够高效


$comparedNumber = 1.2549999999;

if (0 < $comparedNumber && round($comparedNumber, 2) <= round(1.25,2)) {
    $selectedRange = 'Range 1';
} elseif (  round(1.26,2) <= round($comparedNumber, 2)  && round($comparedNumber, 2) <= round(2.45,2)) {
    $selectedRange = 'Range 2';
} elseif (  round(2.46,2) <= round($comparedNumber, 2)  && round($comparedNumber, 2) <= round(5,2)) {
    $selectedRange = 'Range 3';
} elseif ( round(5.01,2) <= round($comparedNumber, 2) ) {
    $selectedRange = 'Range 4';
} else {
    $selectedRange = 'Range not exist';
}

print_r($selectedRange);

Sample here

您当前的范围可能存在差距:1.250001 不会是 <= 1.25,但也不会是 >= 1.26。您已尝试使用 round() 来处理它,但结果仍然是一个浮点数,并且 binary floating point does not accurately represent decimals。这不是 PHP 所独有的,基本上在每种编程语言中都会遇到这种情况(很少有固定十进制数字有单独的类型,以性能换取准确性)。

特别是,写 round(1.25, 2) 永远不会产生与写 1.25 不同的值,因为编译器已经选择了最接近 1.25 的浮点值。

简单的解决方法是每次都使用相同的边界,但在第二次提到时排除相等的值:而不是在第二个范围内使用 >= 1.26,而是使用 > 1.25。然而,这很明显你有多余的测试,因为如果某些东西没有落入 <= 1.25 桶,你已经知道它是 > 1.25,所以不需要再次测试.

为了可读性(以及不可估量的极小性能),我会为 round($comparedNumber, 2) 分配一个局部变量,而不是将其粘贴到每个检查中。您也可以决定不希望进行舍入 - 它的效果是将 1.251 放入“>0, <=1.25” 桶而不是“>1.25, <=2.45” 桶。

所以它简化为:

$comparedNumber = 1.2549999999;
$roundedNumber = round($comparedNumber, 2);

if ($roundedNumber <= 0) {
    $selectedRange = 'Range not exist';
} elseif ($roundedNumber <= 1.25) {
    $selectedRange = 'Range 1';
} elseif ($roundedNumber <= 2.45) {
    $selectedRange = 'Range 2';
} elseif ($roundedNumber <= 5) {
    $selectedRange = 'Range 3';
} else {
    $selectedRange = 'Range 4';
}

由于您现在只需要一个数字来定义每个范围,因此将其变成一个循环很简单:

foreach  ( $ranges as $rangeName => $rangeBoundary ) {
   if ( $roundedNumber <= $rangeBoundary ) {
      $selectedRange = $rangeName;
      break; // stops the loop carrying on with the next test
   }
}

你的问题是边界考虑不周,试图使用相等来比较浮点数。四舍五入不是解决方案:round() 的 return 值仍然是浮点数。

对于您的“范围”,您实际上有三个边界:1.26、2.46 和 5.01。

一个通用的解决方案是:

<?php

$numbers    = [1.2549999999, 1.28012, 2.01212, 4.012, 5.0000012, 5.012121001, -0.12];
$boundaries = [1.26, 2.46, 5.01];

function checkRange(float $number, array $boundaries): int {

    if ($number < 0) {
      return -1;
    }

    foreach ($boundaries as $i => $boundary) {
        if ($number < $boundary) {
            return $i + 1;
            break;
        }
    }
    return 4;
}

foreach ($numbers as $number) {
    echo "$number at Range ", checkRange($number, $boundaries), "\n";
}

/*
Output:
1.2549999999 at Range 1
1.28012 at Range 2
2.01212 at Range 2
4.012 at Range 3
5.0000012 at Range 3
5.012121001 at Range 4
-0.12 at Range -1
*/

工作中看到的 here

请注意,其他答案中的解决方案未能说明 4 范围内的数字。

对于这个练习,我将小于 0 的数字视为“超出范围”,并将它们放在“范围 -1”中。具体如何处理取决于您。

这适用于任何给定的边界集(只要它们是有序的),并且在任何时候都不需要舍入,因为它对比较没有实际意义。一个数字小于边界,或者不是。

除了讨论舍入的弱点的其他好答案:

Performance question too, maybe there are built in function or some trick

如果范围的数量很大,比如 10+,代码可以通过对限制列表进行二进制搜索来有效地确定范围。有 10 个限制,这最多需要 4 次迭代 O(log n),而不是 10 (O(n))。有 100 个限制,最多需要 7 个。

如果范围近似线性分布,平均 范围外观将是 O(1)。

对于现实生活中的分布,以上两种策略的组合是最好的。

固定一组 4 个,只需测试中间一个,然后是剩余的四分之一。