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

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

提问人:Mmx 提问时间:3/29/2022 最后编辑:yiviMmx 更新时间:3/29/2022 访问量:125

问:

例如,我有 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);

样品在这里

PHP 优化 浮点 范围

评论

0赞 Mmx 3/29/2022
Q:您目前的方法中是否有任何“缓慢”或“低效”的地方?答:例如,如果范围为 100,则此结构看起来不太好。问:在比较之前,您有没有具体的原因将数字四舍五入?答:这是php:)1.49999 不等于 1.49999
0赞 user3783243 3/29/2022
所以这不是一个性能问题,而是一个数据准确性问题?
0赞 Mmx 3/29/2022
性能问题也是,也许有内置功能或一些技巧

答:

1赞 IMSoP 3/29/2022 #1

您当前的范围可能会有差距:不会是 ,但也不会是。您尝试使用 处理这个问题,但结果仍然是浮点数,二进制浮点数不能准确表示小数。这并不是PHP所独有的,基本上每一种编程语言都会遇到这种情况(极少数编程语言有固定十进制数的单独类型,交易性能的准确性)。1.250001<= 1.25>= 1.26round()

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

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

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

因此,它简化为:

$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
   }
}

评论

0赞 Mmx 3/29/2022
好主意一下子就来了。关于范围,这是可以讨论的。谢谢你的回答
0赞 IMSoP 3/29/2022
“关于范围,它是可讨论的”——我的示例代码中的范围与您的范围相同,只是它们避免了意外排除既不是也不是的边界值的可能性。另请参阅编辑,了解对每个范围仅使用单个测试的进一步优势。<=1.25>=1.26
0赞 Mmx 3/29/2022
您的循环解决方案是完美的。这是我的具体任务“范围”1.25499999应该在0-1.25范围内,例如它是钱
0赞 IMSoP 3/29/2022
@Mmx 如果可能的话,尽量将金额处理为整数(例如便士/美分数)而不是浮点数。正如我在答案中所说,将值传递给无法摆脱浮点不准确性,因为结果仍然是一个浮点数。round()
1赞 yivi 3/29/2022 #2

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

对于你的“范围”,你实际上有三个边界: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
*/

正如在这里工作所看到的。

请注意,另一个答案中的解决方案无法考虑 4 范围内的数字。

在本练习中,我将低于 0 的数字视为“超出范围”,并将它们置于“范围 -1”中。究竟如何处理这些取决于你。

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

1赞 chux - Reinstate Monica 3/29/2022 #3

除了讨论四舍五入的薄弱概念的其他好答案之外:

性能问题也是,也许有内置功能或一些技巧

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

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

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

对于固定的 4 组,只需对中间的一组进行测试,然后对剩余的四分之一进行测试。