Как сравнить число с плавающей запятой с набором диапазонов с плавающей запятой?

Например, у меня есть 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);

Образец здесь

В: Есть ли что-то «медленное» или «неэффективное» в вашем текущем методе? О: Например, если диапазоны будут равны 100, эта архитектура будет выглядеть не очень хорошо. В: Есть ли у вас конкретная причина округлять число перед сравнением? О: Это php :) 1.49999 не равно 1.49999

Mmx 29.03.2022 15:27

То есть это не вопрос производительности, это вопрос точности данных?

user3783243 29.03.2022 15:31

Вопрос производительности тоже, возможно, есть встроенная функция или какой-то трюк

Mmx 29.03.2022 15:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
3
62
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ваши текущие диапазоны потенциально могут иметь пробелы: 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
   }
}

Отличная идея сразу. Насчет полигонов обсуждаемо. Спасибо за ответ

Mmx 29.03.2022 15:35

Что значит "Насчет полигонов обсуждаемо". - диапазоны в моем примере кода такие же, как и в вашем, за исключением того, что они исключают возможность случайного исключения граничных значений, которые не являются ни <=1.25, ни >=1.26. См. также редактирование для дальнейшего преимущества использования только одного теста для каждого диапазона.

IMSoP 29.03.2022 15:37

Ваше решение петли идеально. Это моя конкретная задача. «Диапазон» 1,25499999 должен быть в диапазоне 0-1,25, например, это деньги.

Mmx 29.03.2022 15:43

@Mmx Если это вообще возможно, попробуйте обрабатывать сумму денег как целое число (например, количество пенсов / центов), а не число с плавающей запятой. Как я сказал в ответе, передача значений в round() не может избавиться от неточности с плавающей запятой, потому что результат по-прежнему является числом с плавающей запятой.

IMSoP 29.03.2022 15:54
Ответ принят как подходящий

Ваша проблема заключается в плохо продуманных границах и попытке использовать равенство для сравнения с плавающей запятой. Округление не является решением: возвращаемое значение 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 человек просто проверьте среднюю, а затем оставшуюся четверть.

Другие вопросы по теме