可靠地检测整数上溢/下溢

时间:2015-02-22 12:56:29

标签: php floating-point integer integer-overflow

我正在处理必须执行以下计算结果的代码:

如果结果超出了可以用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;
}

但是在尝试向PHP_INT_MAX添加1时,单元测试失败。我在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个数量级时才会起作用。

因为我想要生成浮点数来传递总和,只要结果可以四舍五入为整数,如果结果不是int则抛出异常就不符合要求。

是否有一种可靠的方法来检测数字是否超过整数范围,即使是少量?

更新:进一步调查显示,在实际认为该值大于PHP_INT_MAX之前,该值可以超过1025。

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比较数字在数学上是相同的,但是在数字被转换为字符串之后它们不一样然后它们必须溢出,但是可以通过少于&GT;或者&lt;比较。这似乎适用于单元测试,但我真的不认为这是最好的解决方案,目前正在构建一套更严格的单元测试,以查看边界下方,正上方或正好位于其上的值会发生什么。

更新3 :上述方法不适用于负溢出。如果结果触发负溢出,则结果为double,但其值仍与(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)

1 个答案:

答案 0 :(得分:-1)

一旦我想到这一点,答案就非常简单了。所需要的只是将MIN和MAX常量重新定义为不是最大可能的正整数值和负整数值,而是将它们定义为当测试值和MIN / MAX值都被转换为浮动时的最大值,被测值仍然在MIN / MAX范围内。

实验表明,使极限512低于绝对极限可实现此目的。

const MAX   = PHP_INT_MAX - 512;
const MIN   = (PHP_INT_MAX * -1) + 512;

现在可以检测到该范围之外的任何值,无论是否发生了转换。

这种方法仍然存在一些问题(退避区域可能不需要像32位系统那么大),但它比类型杂耍和字符串比较更加优雅。