可以将连续的截断整数除以乘法吗?

时间:2019-01-20 19:19:55

标签: c math division integer-division

在有理数的小学数学中,表达式(a / b) / c等同于a / (b * c),通过基本的代数运算即可。

/像C语言和大多数其他语言一样将整数除法截断时,情况是否相同?也就是说,我可以用所有除数的乘积将一个系列的除法替换为一个除法吗?

您可能会认为乘法没有溢出(如果乘法没有溢出,很明显它们并不等效)。

3 个答案:

答案 0 :(得分:6)

答案是“是”(至少对于非负整数)。这是从division algorithm开始的,该状态指出对于任何正整数a,d,我们都有a = dq+r用于q,r的唯一非负整数0 <= q <= d-1。在这种情况下,q a/d以整数除法。

a/b/c(整数除法)中,我们可以分两步来考虑它:

a = b*q_1 + r_1  // here q_1 = a/b and 0 <= r_1 <= b-1
q_1 = c*q_2 + r_2 // here q_2 = q_1/c = a/b/c and 0 <= r_2 <= c-1

但是然后

a = b*q_1 + r_1 = b*(c*q_2 + r_2) + r_1 = (b*c)*q_2 + b*r_2 + r1

请注意,0 <= b*r_2 + r_1 <= b*(c-1) + b-1 = bc - 1

由此可以得出,q_2a/(b*c)。因此a/b/c = a/(b*c)

答案 1 :(得分:0)

是,是整数。已经有人发布(并删除了?)一个示例,说明它可能无法在浮点上工作。 (尽管,它对于您的应用程序来说可能足够接近了。)

@JohnColeman有理论上的论据,但这是来自实验的论点。如果运行此代码:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define LIMIT 100
#define NUM_LIMIT 2000

int main() {
    for(int div1 = -LIMIT; div1 < LIMIT; div1++) {
        for(int div2 = -LIMIT; div2 < LIMIT; div2++) {
            if(div1 == 0 || div2 == 0) continue;
            for(int numerator = -NUM_LIMIT; numerator < NUM_LIMIT; numerator++) {
                int a = (numerator / div1) / div2;
                int b = numerator / (div1 * div2);
                if(a != b) {
                    printf("%d %d\n", a, b);
                    exit(1);
                }
            }
        }
    }
    return 0;
}

它会同时尝试除法,如果它们不同则报告错误。通过以下方式运行它不会报告错误:

  • div1,div2从-5到5,分子从-INT_MAX / 100到INT_MAX / 100
  • div1,div2从-2000到2000,以及分子从-2000到2000

因此,我敢打赌这将适用于任何整数。 (再次假设div1 * div2没有溢出。)

答案 2 :(得分:-2)

从实验上看这有助于查看如何应用数学。

软件不会更改数学,标识或使用变量进行简化或更改方程式的其他操作。软件执行要求的操作。定点可以完美地实现它们,除非您溢出。

根据数学而不是软件,我们知道b或c都不能为零。软件添加的是b * c也不能为零。如果溢出,那么结果是错误的。

a / b / c =(a / b)*(1 / c)=(a * 1)/(b c)= a /(b c)和您可以用您的编程语言实现a / b / c或a /(b * c)。在大多数情况下,如果您坚持使用整数,则由于未表示分数,结果是不正确的。如果您使用浮点运算,则出于相同的原因,在相当长的时间内,结果是不正确的,这与纯数学运算一样,没有足够的位来存储无限大或小的数字。那么您在哪里遇到这些限制?您可以通过一个简单的实验开始看到它。

编写一个程序,对a,b和c进行0到15之间所有可能的数字组合,计算a / b / c和a /(b * c)并进行比较。由于这些是4位数字,因此请记住,如果您想了解编程语言将要执行的操作,则中间值不能超过4位。仅打印除以零和不匹配的除。

立即您会发现,允许将任何一个值都设置为零,对于一个非常无趣的实验,您会得到很多除以零或0 / something_not_zero的有趣结果。

 1  2  8 : divide by zero
 1  3 11 :  1  0
 1  4  4 : divide by zero
 1  4  8 : divide by zero
 1  4 12 : divide by zero
 1  5 13 :  1  0
 1  6  8 : divide by zero
 1  7  7 :  1  0
 1  8  2 : divide by zero
 1  8  4 : divide by zero
 1  8  6 : divide by zero
 1  8  8 : divide by zero
 1  8 10 : divide by zero
 1  8 12 : divide by zero
 1  8 14 : divide by zero
 1  9  9 :  1  0
 1 10  8 : divide by zero
 1 11  3 :  1  0
 1 12  4 : divide by zero
 1 12  8 : divide by zero
 1 12 12 : divide by zero
 1 13  5 :  1  0
 1 14  8 : divide by zero
 1 15 15 :  1  0
 2  2  8 : divide by zero
 2  2  9 :  1  0
 2  3  6 :  1  0
 2  3 11 :  2  0

到目前为止,答案是匹配的。对于较小的a或特别是a = 1,有意义的结果将是0或1。两条路径都将带您到达那里。

 1  2  8 : divide by zero

至少对于a = 1,b = 1。 c = 1给出1,其余给出0。

2 * 8 = 16或0x10,这是太多位,因此它溢出,结果是0x0除以零,因此无论是浮点还是定点,您都必须查找。

 1  3 11 :  1  0

第一个有趣的人
1 /(3 * 11)= 1 / 0x21,表示1/1 = 1; 1/3 = 0,0 / 11 = 0。 所以他们不匹配。 3 * 11溢出。

然后继续。

那么增加ra的数量可能会使这更有趣?一个小的变量无论如何都会使结果大部分时间为0。

15  2  8 : divide by zero
15  2  9 :  7  0
15  2 10 :  3  0
15  2 11 :  2  0
15  2 12 :  1  0
15  2 13 :  1  0
15  2 14 :  1  0
15  2 15 :  1  0
15  3  6 :  7  0
15  3  7 :  3  0
15  3  8 :  1  0
15  3  9 :  1  0
15  3 10 :  1  0
15  3 11 : 15  0
15  3 12 :  3  0
15  3 13 :  2  0
15  3 14 :  1  0
15  3 15 :  1  0
15  4  4 : divide by zero
15  4  5 :  3  0
15  4  6 :  1  0
15  4  7 :  1  0
15  4  8 : divide by zero
15  4  9 :  3  0


15  2  9 :  7  0

15 /(2 * 9)= 15 / 0x12 = 15/2 = 7。 15/2 = 7; 7/9 = 0;

15  3 10 :  1  0
15  3 11 : 15  0

这两种情况都没有意思。

因此将程序更改为仅显示结果不匹配但又没有b * c溢出的程序。使用4位值与8位vs 128位进行此操作之间没有任何魔术或区别。...它只允许您获得更多可能有效的结果。

0xF * 0xF = 0xE1,您可以很容易地看到二进制形式的长乘法,最坏的情况是要覆盖所有可能的N位值,您需要2 * N位来存储结果而不会发生溢出。因此,为了进行除法运算,受N / 2个位数分母限制的N位分子可以覆盖每个位数的所有不动点值,并得到N位结果。 0xFFFF / 0xFF = 0x101。 0xFFFF / 0x01 = 0xFFFF。

因此,如果您要进行此数学运算并可以确保所有数字都不超过N位,则使用N * 2位数字进行数学运算。您将不会有任何乘法溢出,仍然需要除以零。

为通过实验证明这一点,请尝试从a,b,c的0到15的所有组合,但使用8位而不是4的变量进行数学运算(在每次除法之前检查除以零,然后将这些组合扔出),结果始终匹配。

那么有“更好的”吗?乘法和除法都可以在一个时钟中使用大量逻辑来实现,也可以在多个时钟中以指数形式实现,尽管您的处理器文档说这是一个时钟,但由于流水线的存在,它可能仍然是多个,并且它们可以隐藏2或4个周期的任何一个进入一些管道,并节省大量的芯片资源。或某些处理器根本不做划分以节省空间。您可以编译一些cortex-m内核,以实现单时钟或多时钟分频,从而节省空间,仅在有人执行乘法(谁在其代码中执行乘法或除法)时才会受到损害。当您执行类似操作时,您会看到

x = x / 5;

取决于目标以及可以/将实现为x = x *(1/5)的编译器和优化设置以及一些其他动作以使其正常工作。

unsigned int fun ( unsigned int x )
{
    return(x/5);
}

0000000000000000 <fun>:
   0:   89 f8                   mov    %edi,%eax
   2:   ba cd cc cc cc          mov    $0xcccccccd,%edx
   7:   f7 e2                   mul    %edx
   9:   89 d0                   mov    %edx,%eax
   b:   c1 e8 02                shr    $0x2,%eax
   e:   c3                      retq   

除可以在该目标上使用分频以外,还可以使用乘法,但是由于时钟或其他原因,乘法被认为更好。

所以您可能希望考虑到这一点。

如果执行a / b / c,则必须检查两次除零,但如果执行//(b + c),则只需检查一次。对于每个alu指令,如果时钟数为1或接近1,检查除数是否比数学本身花费更多。因此,理想情况下,乘法的效果更好,但是可能会有例外。

您可以使用带符号的数字重复所有这些操作。同样如此。如果它适用于4位,它将适用于8、16、32、64和128,依此类推...

7 * 7 = 0x31
-8 * -8 = 0x40
7 * -8 = 0xC8

这应该涵盖了极端情况,因此,如果使用的位数是最坏情况的两倍,则不会溢出。您仍然必须在每次除法之前检查除数是否为零,因此乘法解决方案只会导致对零的一项检查。如果将所需位数增加一倍,则不必检查乘数是否有溢出。

这里没有魔术,这是并且已经通过基本数学解决了。什么时候溢出,不用铅笔和纸并且没有编程语言(或者像我这样做更快的计算器),您可以看到何时。您还可以使用更多该年级的学校数学。 b对N位的msbit是b [n-1] * 2 ^(n-1)对吗?具有4位数字(无符号),msbit为0x8,即1 * 2 ^(4-1);对于其余的b(b [3] * 2 ^ 3)+(b [2] * 2 ^ 2)+(b [1] * 2 ^ 1)+(b [0] * 2 ^ 0);对于C同样如此,因此当我们使用小学数学将它们相乘时,我们从(b [3] c [3])(2 ^(3 + 3))开始,如果您坐下来工作,最坏的情况不能超过2 ^ 8。也可以这样看:

     abcd
*    1111
=========
     abcd
    abcd
   abcd
+ abcd
=========
  xxxxxxx

7位,加上进位的可能性,使其总共8位。所有简单的小学数学,看看潜在的问题是什么。

实验将不会显示失败的位模式(除以零不会计数,这对于a / b / c = a /(b * c)也不起作用)。约翰·科尔曼斯(John Colemans)的回答是从另一个角度来看的,这可能有助于感觉到所有位模式都将起作用。尽管那都是正数。只要您检查所有溢出,此方法也适用于负数。