Например, у меня есть 4 диапазона:
И у меня есть число с плавающей запятой для сравнения: 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()
, но результатом этого все еще является число с плавающей запятой и двоичная с плавающей запятой не точно представляет десятичные числа. Это не уникально для 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
}
}
Отличная идея сразу. Насчет полигонов обсуждаемо. Спасибо за ответ
Что значит "Насчет полигонов обсуждаемо". - диапазоны в моем примере кода такие же, как и в вашем, за исключением того, что они исключают возможность случайного исключения граничных значений, которые не являются ни <=1.25
, ни >=1.26
. См. также редактирование для дальнейшего преимущества использования только одного теста для каждого диапазона.
Ваше решение петли идеально. Это моя конкретная задача. «Диапазон» 1,25499999 должен быть в диапазоне 0-1,25, например, это деньги.
@Mmx Если это вообще возможно, попробуйте обрабатывать сумму денег как целое число (например, количество пенсов / центов), а не число с плавающей запятой. Как я сказал в ответе, передача значений в round()
не может избавиться от неточности с плавающей запятой, потому что результат по-прежнему является числом с плавающей запятой.
Ваша проблема заключается в плохо продуманных границах и попытке использовать равенство для сравнения с плавающей запятой. Округление не является решением: возвращаемое значение 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». Что именно с ними делать, решать вам.
Это работает для любого заданного набора границ (если они упорядочены) и не требует округления ни в какой точке, поскольку это спорно для сравнения. Число меньше границы или нет.
В дополнение к другим хорошим ответам, обсуждающим слабую идею округления:
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 человек просто проверьте среднюю, а затем оставшуюся четверть.
В: Есть ли что-то «медленное» или «неэффективное» в вашем текущем методе? О: Например, если диапазоны будут равны 100, эта архитектура будет выглядеть не очень хорошо. В: Есть ли у вас конкретная причина округлять число перед сравнением? О: Это php :) 1.49999 не равно 1.49999