代码之家  ›  专栏  ›  技术社区  ›  Nikita Kniazev

证明溢出检查表达式正确

  •  1
  • Nikita Kniazev  · 技术社区  · 6 年前

    我有一个C++函数,它包含一个表达式(溢出检查),这是可以用手证明的。我有一个优化的想法,这对我来说似乎是正确的,我找不到一个反例,但我想确保它是正确的。我听说过Z3,它看起来非常适合。我写了一个公式,Z3说 unsat 但问题是我不相信我得到的结果,因为我不完全理解我是否做得对(基于我以前获得的非凡结果的恐惧,但这是我的错,我认识到了)。

    C++函数:

    template <typename T>
    bool add(int radix, int digit, T& n)
    {
        assert(radix > 2);
        assert(radix <= 36);
        assert(digit >= 0);
        assert(digit < radix);
    
        T max = std::numeric_limits<T>::max();
        assert(max >= radix);
    
        // the overflows check
        if ((n > (max / radix)) || ((n * radix) > (max - digit)))
            return false;
    
        n = n * radix + digit;
        return true;
    }
    

    我想证明(除法是整数,不是实数部分):

    (n > (max / radix)) || ((n * radix) > (max - digit)) & =; n > ((max - digit) / radix)

    或者更一般地说,如果这些表达式在 (n * radix) > max (n * radix + digit) > max

    Z3代码我有:

    (declare-const radix Int)
    (assert (>= radix 2))
    (assert (<= radix 36)) ; this is the upper bound we officially support
    
    (declare-const digit Int)
    (assert (>= digit 0))
    (assert (< digit radix))
    
    (declare-const max Int)
    (assert (> max 0))
    (assert (>= max radix)) ; this is a reasonable requirement
    ;(assert (>= max 256)) ; the smallest upper bound for C++ fundamentals, but UDTs can have it lower
    
    (declare-const n Int)
    (assert (<= n max))
    ;(assert (>= n 0)) ; not really, but usually
    
    ; our current check
    ;(assert (not (or
    ;  (> n (div max radix))
    ;  (> (* n radix) (- max digit))
    ;)))
    ; our optimized check
    (assert (not
      (> n (div (- max digit) radix))
    ))
    
    (assert (or
      (> (* n radix) max)           ; check no mul overflow
      (> (+ n digit) max)           ; check no add overflow
      (> (+ (* n radix) digit) max) ; check no muladd overflow
    ))
    
    (check-sat)
    (get-model)
    (exit)
    

    https://rise4fun.com/Z3/po1h

    1 回复  |  直到 6 年前
        1
  •  2
  •   alias    6 年前

    我觉得不错。在风格上,我会这样写:

    (define-fun oldCheck () Bool
      (or (> n (div max radix)) 
          (> (* n radix) (- max digit))))
    
    (define-fun newCheck () Bool
          (> n (div (- max digit) radix)))
    
    (assert (distinct oldCheck newCheck)) 
    

    这就清楚了你在检查什么。您可以放心,您的优化是很好的去!

    关于distinct的注释

    这个 distinct 谓词在smtlib文档的第37页定义: http://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2017-07-18.pdf

    如果你把它精确地传递给两个参数,它本质上等同于否定的等价。但是,对于>2参数,其行为是不同的:如果传递更多参数,它将确保成对不等式。(也就是说,所有的问题都必须是不同的。)它在许多问题上都非常有用。

    将2个参数传递给 = 也是可能的,它确保所有参数都相等。但是请注意,当您有>2个参数时,被否定的相等和不同变为不同的:例如, 2 2 3 ,并不都是平等的,但它们不是 不同的 要么。我希望能说清楚。

    关于溢出/下溢检查的说明

    Patrick提出了溢出检查和使用机器整数的问题,他认为应该担心这些情况是正确的。我认为Nikita已经通过确保明确的界限来处理特定的用例了。然而,一个人不能太小心!出于这些目的,Z3实际上内置了溢出检查原语。详情请参见Nikolaj的这篇精彩论文: https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/z3prefix.pdf

    Z3提供的原语有:

    • bvsmul_noovfl :有符号乘法无溢出
    • bvsmul_noudfl :有符号乘法无下溢
    • bvumul_noovfl :无符号乘法无溢出

    但是,请参阅有关可以用于检测其他操作溢出的逻辑公式的文章。(上面三个相当复杂,所以它们是原始支持的。对于其他情况,用户可以直接进行检查。)