如何比较浮点数是否与一组浮点范围?
How to compare if a floating point number against a set of floating point ranges?
例如,我有 4 个范围:
- 0 - 1.25
- 1.26 - 2.45
- 2.46 - 5
- 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);
您当前的范围可能存在差距: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 个,只需测试中间一个,然后是剩余的四分之一。
例如,我有 4 个范围:
- 0 - 1.25
- 1.26 - 2.45
- 2.46 - 5
- 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);
您当前的范围可能存在差距: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 个,只需测试中间一个,然后是剩余的四分之一。