【问题标题】:Reliably detect integer overflow/underflow可靠地检测整数上溢/下溢
【发布时间】:2015-04-23 20:19:31
【问题描述】:

我正在编写必须对计算结果执行以下操作的代码:

如果结果超出了 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 个数量级时才会起作用。

如果结果可以四舍五入为整数,我希望生成浮点数的总和传递,如果结果不是 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 比较,数字在数学上是相同的,但在将数字转换为字符串后它们不一样,那么它们一定是溢出了,但小于可以用 a 检测到> 或

更新 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)

【问题讨论】:

    标签: php floating-point integer integer-overflow


    【解决方案1】:

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

    实验表明,使限制 512 短于绝对限制可以实现这一点。

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

    现在,无论是否发生强制转换为浮点数,都可以检测到超出该范围的任何值。

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

    【讨论】:

      猜你喜欢
      • 2018-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多