代码之家  ›  专栏  ›  技术社区  ›  ideasman42

C/C中有符号整数除法的快速下限++

  •  9
  • ideasman42  · 技术社区  · 7 年前

    在C中,可以进行楼层划分,例如:

    int floor_div(int a, int b) {
        int d = a / b;
        if (a < 0 != b < 0) {  /* negative output (check inputs since 'd' isn't floored) */
            if (d * a != b) {  /* avoid modulo, use multiply instead */
                d -= 1;        /* floor */
            }
        }
        return d;
    }
    


    Fast ceiling of an integer division in C / C++

    4 回复  |  直到 7 年前
        1
  •  7
  •   0___________    7 年前

    我认为,生成的代码中的汇编指令更少,实现结果的路径更快。

    int floor_div3(int a, int b) {
        int d = a / b;
    
    
        return d * b == a ? d : d - ((a < 0) ^ (b < 0));
    }
    
        2
  •  5
  •   linux_pangolin    4 年前

    div() 标准C中的函数

    我想你应该看看 div() <stdlib.h> . (它们是标准C函数,在标准的所有版本中都有定义,尽管链接到POSIX规范。)

    C11标准§7.22.6.2规定:

    这个 div 函数计算 numer / denom numer % denom

    注意,C11在§6.5.5中规定了整数除法(C99类似):

    / 算子是丢弃任何分数部分的代数商。 105)

    105)

    但C90(§6.3.5)更灵活,但用处不大:

    当整数被除法且除法不精确时。如果两个操作数都为正,则 算子是小于代数商的最大整数,并且 % 运算符为正。如果任一操作数为负,则 %

    floor_div()

    请求的计算代码 floor\u div() div() 整洁。

    int floor_div(int a, int b)
    {
        assert(b != 0);
        div_t r = div(a, b);
        if (r.rem != 0 && ((a < 0) ^ (b < 0)))
            r.quot--;
        return r.quot;
    }
    

    测试代码

    %4d %-4d

    #include <assert.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    static int floor_div(int a, int b)
    {
        assert(b != 0);
        div_t r = div(a, b);
        if (r.rem != 0 && ((a < 0) ^ (b < 0)))
            r.quot--;
        return r.quot;
    }
    
    static void test_floor_div(int n, int d)
    {
        assert(d != 0);
        printf(   "%3d/%-2d = %-3d (%3d)", +n, +d, floor_div(+n, +d), +n / +d);
        printf(";  %3d/%-3d = %-4d (%4d)", +n, -d, floor_div(+n, -d), +n / -d);
        if (n != 0)
        {
            printf(";  %4d/%-2d = %-4d (%4d)", -n, +d, floor_div(-n, +d), -n / +d);
            printf(";  %4d/%-3d = %-3d (%3d)", -n, -d, floor_div(-n, -d), -n / -d);
        }
        putchar('\n');
    }
    
    int main(void)
    {
        int numerators[] = { 0, 1, 2, 4, 9, 23, 291 };
        enum { NUM_NUMERATORS = sizeof(numerators) / sizeof(numerators[0]) };
        int denominators[] = { 1, 2, 3, 6, 17, 23 };
        enum { NUM_DENOMINATORS = sizeof(denominators) / sizeof(denominators[0]) };
    
        for (int i = 0; i < NUM_NUMERATORS; i++)
        {
            for (int j = 0; j < NUM_DENOMINATORS; j++)
                test_floor_div(numerators[i], denominators[j]);
            putchar('\n');
        }
    
        return 0;
    }
    

    测试输出

      0/1  = 0   (  0);    0/-1  = 0    (   0)
      0/2  = 0   (  0);    0/-2  = 0    (   0)
      0/3  = 0   (  0);    0/-3  = 0    (   0)
      0/6  = 0   (  0);    0/-6  = 0    (   0)
      0/17 = 0   (  0);    0/-17 = 0    (   0)
      0/23 = 0   (  0);    0/-23 = 0    (   0)
    
      1/1  = 1   (  1);    1/-1  = -1   (  -1);    -1/1  = -1   (  -1);    -1/-1  = 1   (  1)
      1/2  = 0   (  0);    1/-2  = -1   (   0);    -1/2  = -1   (   0);    -1/-2  = 0   (  0)
      1/3  = 0   (  0);    1/-3  = -1   (   0);    -1/3  = -1   (   0);    -1/-3  = 0   (  0)
      1/6  = 0   (  0);    1/-6  = -1   (   0);    -1/6  = -1   (   0);    -1/-6  = 0   (  0)
      1/17 = 0   (  0);    1/-17 = -1   (   0);    -1/17 = -1   (   0);    -1/-17 = 0   (  0)
      1/23 = 0   (  0);    1/-23 = -1   (   0);    -1/23 = -1   (   0);    -1/-23 = 0   (  0)
    
      2/1  = 2   (  2);    2/-1  = -2   (  -2);    -2/1  = -2   (  -2);    -2/-1  = 2   (  2)
      2/2  = 1   (  1);    2/-2  = -1   (  -1);    -2/2  = -1   (  -1);    -2/-2  = 1   (  1)
      2/3  = 0   (  0);    2/-3  = -1   (   0);    -2/3  = -1   (   0);    -2/-3  = 0   (  0)
      2/6  = 0   (  0);    2/-6  = -1   (   0);    -2/6  = -1   (   0);    -2/-6  = 0   (  0)
      2/17 = 0   (  0);    2/-17 = -1   (   0);    -2/17 = -1   (   0);    -2/-17 = 0   (  0)
      2/23 = 0   (  0);    2/-23 = -1   (   0);    -2/23 = -1   (   0);    -2/-23 = 0   (  0)
    
      4/1  = 4   (  4);    4/-1  = -4   (  -4);    -4/1  = -4   (  -4);    -4/-1  = 4   (  4)
      4/2  = 2   (  2);    4/-2  = -2   (  -2);    -4/2  = -2   (  -2);    -4/-2  = 2   (  2)
      4/3  = 1   (  1);    4/-3  = -2   (  -1);    -4/3  = -2   (  -1);    -4/-3  = 1   (  1)
      4/6  = 0   (  0);    4/-6  = -1   (   0);    -4/6  = -1   (   0);    -4/-6  = 0   (  0)
      4/17 = 0   (  0);    4/-17 = -1   (   0);    -4/17 = -1   (   0);    -4/-17 = 0   (  0)
      4/23 = 0   (  0);    4/-23 = -1   (   0);    -4/23 = -1   (   0);    -4/-23 = 0   (  0)
    
      9/1  = 9   (  9);    9/-1  = -9   (  -9);    -9/1  = -9   (  -9);    -9/-1  = 9   (  9)
      9/2  = 4   (  4);    9/-2  = -5   (  -4);    -9/2  = -5   (  -4);    -9/-2  = 4   (  4)
      9/3  = 3   (  3);    9/-3  = -3   (  -3);    -9/3  = -3   (  -3);    -9/-3  = 3   (  3)
      9/6  = 1   (  1);    9/-6  = -2   (  -1);    -9/6  = -2   (  -1);    -9/-6  = 1   (  1)
      9/17 = 0   (  0);    9/-17 = -1   (   0);    -9/17 = -1   (   0);    -9/-17 = 0   (  0)
      9/23 = 0   (  0);    9/-23 = -1   (   0);    -9/23 = -1   (   0);    -9/-23 = 0   (  0)
    
     23/1  = 23  ( 23);   23/-1  = -23  ( -23);   -23/1  = -23  ( -23);   -23/-1  = 23  ( 23)
     23/2  = 11  ( 11);   23/-2  = -12  ( -11);   -23/2  = -12  ( -11);   -23/-2  = 11  ( 11)
     23/3  = 7   (  7);   23/-3  = -8   (  -7);   -23/3  = -8   (  -7);   -23/-3  = 7   (  7)
     23/6  = 3   (  3);   23/-6  = -4   (  -3);   -23/6  = -4   (  -3);   -23/-6  = 3   (  3)
     23/17 = 1   (  1);   23/-17 = -2   (  -1);   -23/17 = -2   (  -1);   -23/-17 = 1   (  1)
     23/23 = 1   (  1);   23/-23 = -1   (  -1);   -23/23 = -1   (  -1);   -23/-23 = 1   (  1)
    
    291/1  = 291 (291);  291/-1  = -291 (-291);  -291/1  = -291 (-291);  -291/-1  = 291 (291)
    291/2  = 145 (145);  291/-2  = -146 (-145);  -291/2  = -146 (-145);  -291/-2  = 145 (145)
    291/3  = 97  ( 97);  291/-3  = -97  ( -97);  -291/3  = -97  ( -97);  -291/-3  = 97  ( 97)
    291/6  = 48  ( 48);  291/-6  = -49  ( -48);  -291/6  = -49  ( -48);  -291/-6  = 48  ( 48)
    291/17 = 17  ( 17);  291/-17 = -18  ( -17);  -291/17 = -18  ( -17);  -291/-17 = 17  ( 17)
    291/23 = 12  ( 12);  291/-23 = -13  ( -12);  -291/23 = -13  ( -12);  -291/-23 = 12  ( 12)
    
        3
  •  2
  •   ideasman42    7 年前

    地板除法可以通过使用除法和模来执行。

    没有理由避免模调用,因为现代编译器优化除法&模分解为一个除法。

    int floor_div(int a, int b) {
        int d = a / b;
        int r = a % b;  /* optimizes into single division. */
        return r ? (d - ((a < 0) ^ (b < 0))) : d;
    }
    
        4
  •  1
  •   kevinjwz    6 年前

    “楼层除法”的余数要么为0,要么与除数具有相同的符号。

    (the proof)  
    a: dividend  b: divisor
    q: quotient  r: remainder
    
    q = floor(a/b)
    a = q * b + r  
    r = a - q * b = (a/b - q) * b  
                    ~~~~~~~~~
                        ^ this factor in [0, 1)
    

    / % 在C/C++中,在C99/C++11之后标准化为“向零截断”。(在此之前,库函数 div 在C和 std::div

    让我们比较一下“楼层除法”和“截断除法”,重点是余数的范围:

           "floor"     "truncate"
    b>0    [0, b-1]    [-b+1, b-1]
    b<0    [b+1, 0]    [b+1, -b-1]
    

    • 设a,b=被除数和除数;
    • 设q0,r0=商和“截断除法”的余数。

    假设b>0,不幸的是,r0在[-b+1,-1]中。然而,我们可以很容易地得到r:r=r0+b,并且r保证在[1,b-1]中,这在“地板”范围内。情况b也是如此<0

    void floor_div(int& q, int& r, int a, int b)
    {
        int q0 = a / b;
        int r0 = a % b;
        if (b > 0){
            q = r0 >= 0 ? q0 : q0 - 1;
            r = r0 >= 0 ? r0 : r0 + b;
        }
        else {
            q = r0 <= 0 ? q0 : q0 - 1;
            r = r0 <= 0 ? r0 : r0 + b;
        }
    }
    

    与名人相比 (a < 0) ^ (b < 0) 方法,该方法有一个优点:如果除数是编译时常数,则只需进行一次比较即可修复结果。