I'm working on code that has to do the following with the result of a calculation:
If the result exceeds the limit that can be represented in PHP's integer type then throw an exception.
If the result doesn't exceed that limit but it did result in a float being generated, issue a warning and round the result to an integer.
I've implemented the following method to do this:
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;
}
However it fails unit testing when attempting to add 1 to PHP_INT_MAX. I tried the following in PHP interactive mode:
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)
So it looks like my detection code will only work if the result goes about 5 orders of magnitude out of range.
As I want sums that generate a float to pass provided the result can be rounded to an integer, simply throwing an exception if the result isn't an int wouldn't meet the requirements.
Is there a reliable way of detecting that a number has exceeded integer range, even by a small amount?
UPDATE: Further investigation shows that the value can go over by up to 1025 before it's actually considered bigger than 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)
UPDATE 2: I've implemented a provisional fix, but that fix is really hacky and inelegant so I'm leaving this question open in the hope that someone has a better suggestion.
if ((($result > static::MAX) || (($result == static::MAX) && ((string) $result != (string) static::MAX)))
|| (($result < static::MIN) || (($result == static::MIN) && ((string) $result != (string) static::MIN)))) {}
The idea is that if the numbers are mathematically the same according to a PHP comparison, but they're not the same after the numbers have been cast to string then they must have overflowed, but by less than can be detected with a > or < comparison. This seems to work in unit testing, but I really don't think this is the best solution and am currently constructing a more rigorous set of unit tests to see what happens with values just below the boundary, just above it, or exactly on it.
UPDATE 3: The above approach won't work with negative overflow. If the result triggers a negative overflow the result is a double, but its value is still the same as (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)