Я работаю над кодом, который должен сделать следующее с результатом вычисления:
Если результат превышает предел, который может быть представлен в целочисленном типе PHP, тогда выдается исключение.
Если результат не превышает этот предел, но это привело к генерации с плавающей точкой, выдайте предупреждение и округлите результат до целого числа.
Я реализовал следующий метод для этого:
const MAX = PHP_INT_MAX;
const MIN = (PHP_INT_MAX * -1) -1;
private function validateResult ($result)
{
// Check that we still have an integer
if (!is_int ($result))
{
// If the result is out of bounds for an integer then throw an exception
if (($result > static::MAX) || ($result < static::MIN ))
{
// We've gone out of bounds
throw new exception\AmountRangeException ("New value exceeds the limits of integer storage");
}
// If the result can be rounded into an integer then do so and issue
// a warning.
trigger_error ("A non-integer value of $result resulted and has been rounded", E_USER_NOTICE);
$result = (int) round ($result);
}
return $result;
}
Однако он не проходит модульное тестирование при попытке добавить 1 к PHP_INT_MAX. Я попробовал следующее в интерактивном режиме PHP:
php > var_dump (PHP_INT_MAX);
int(9223372036854775807)
php > var_dump (PHP_INT_MAX + 1);
double(9.2233720368548E+18)
php > var_dump ((PHP_INT_MAX + 1) > PHP_INT_MAX);
bool(false)
php > var_dump ((PHP_INT_MAX + 10) > PHP_INT_MAX);
bool(false)
php > var_dump ((PHP_INT_MAX + 100) > PHP_INT_MAX);
bool(false)
php > var_dump ((PHP_INT_MAX + 1000) > PHP_INT_MAX);
bool(false)
php > var_dump ((PHP_INT_MAX + 10000) > PHP_INT_MAX);
bool(true)
Похоже, мой код обнаружения будет работать только в том случае, если результат выйдет за пределы порядка 5 порядков.
Поскольку я хочу, чтобы суммы, которые генерируют число с плавающей точкой, передавались при условии, что результат может быть округлен до целого числа, просто выдается исключение, если результат не является целым числом, не отвечающим требованиям.
Есть ли надежный способ обнаружить, что число превысило целочисленный диапазон, даже на небольшое количество?
ОБНОВИТЬДальнейшие исследования показывают, что значение может превысить 1025, прежде чем оно будет считаться большим, чем PHP_INT_MAX.
php > var_dump ((PHP_INT_MAX + 1025) > PHP_INT_MAX);
bool(false)
php > var_dump ((PHP_INT_MAX + 1026) > PHP_INT_MAX);
bool(true)
ОБНОВЛЕНИЕ 2Я реализовал временное исправление, но это исправление действительно хакерское и не элегантное, поэтому я оставляю этот вопрос открытым в надежде, что у кого-то есть лучшее предложение.
if ((($result > static::MAX) || (($result == static::MAX) && ((string) $result != (string) static::MAX)))
|| (($result < static::MIN) || (($result == static::MIN) && ((string) $result != (string) static::MIN)))) {}
Идея состоит в том, что если числа математически совпадают в соответствии с PHP-сравнением, но они не совпадают после того, как числа были преобразованы в строку, то они должны быть переполнены, но менее чем могут быть обнаружены с помощью> или < сравнение. Кажется, что это работает в модульном тестировании, но я действительно не думаю, что это лучшее решение, и в настоящее время я создаю более строгий набор модульных тестов, чтобы увидеть, что происходит со значениями чуть ниже границы, чуть выше нее или точно на ней ,
ОБНОВЛЕНИЕ 3: Вышеупомянутый подход не будет работать с отрицательным переполнением. Если результат вызывает отрицательное переполнение, результат удваивается, но его значение остается таким же, как (PHP_INT_MAX * 1) — 1
php > var_dump ((PHP_INT_MAX * -1) - 1);
int(-9223372036854775808)
php > var_dump ((PHP_INT_MAX * -1) - 2);
double(-9223372036854775808)
Оказывается, ответ был невероятно прост, когда я об этом подумал. Все, что нужно, это переопределить константы MIN и MAX, чтобы они не были максимально возможными положительными и отрицательными целочисленными значениями, а чтобы определить их как самые большие значения, которые, когда проверяемое значение и значения MIN / MAX оба приводятся к плавающему, тестируемое значение будет по-прежнему в диапазоне MIN / MAX.
Эксперименты показали, что ограничение 512 от абсолютного предела достигает этого.
const MAX = PHP_INT_MAX - 512;
const MIN = (PHP_INT_MAX * -1) + 512;
Теперь любое значение вне этого диапазона может быть обнаружено независимо от того, происходит ли приведение к типу с плавающей точкой или нет.
У этого подхода все еще есть некоторые проблемы (зона отката, вероятно, не должна быть такой большой в 32-битной системе), но это гораздо более элегантное решение, чем жонглирование типов и сравнение строк.
Других решений пока нет …