在C#整数算法中,a / b / c是否始终等于a /(b * c)?


81

令a,b和c为非大正整数。使用C#整数算法,a / b / c是否总是等于a /(b * c)?对我来说,在C#中看起来像:

int a = 5126, b = 76, c = 14;
int x1 = a / b / c;
int x2 = a / (b * c);

所以我的问题是:x1 == x2a,b和c都适用吗?


3
这是一个数学问题,而不是编程问题。您能解释一下这个问题的编程特定部分吗?
奥德

38
当然,@ Oded在任何有理数的范围内,但这特别是指整数算术(在C#中)。使其与编程相关的IMO。也许a / b / c == a /(b * c)的规则适用于整数算术,也许仅适用于有理数算法。
Tim S.

43
这是关于C#的完全合理的问题,并且易于回答。
埃里克·利珀特

12
@Oded这是一个关于计算机算术以及它的行为是否与纯数学相同的问题。它不应该关闭。
Jeffrey Sax

4
我会对数学证明为什么(或实际上是否)忽略溢出实际上两者相等的数学证明很感兴趣,但是我还没有设法将它们放在一起。
罗林

Answers:


71

\分别表示整数除法(该C#/2个之间操作者int或多个),并让/分别表示通常的数学除法。然后,如果x,y,z正整数,而我们忽略了溢出

(x \ y) \ z
    = floor(floor(x / y) / z)      [1]
    = floor((x / y) / z)           [2]
    = floor(x / (y * z))
    = x \ (y * z)

哪里

a \ b = floor(a / b)

从线跳转[1]到线[2]上面解释如下。假设您有两个整数ab一个f范围内的小数[0, 1)。显而易见

floor(a / b) = floor((a + f) / b)  [3]

如果符合条件,则[1]标识a = floor(x / y)f = (x / y) - floor(x / y)b = z,则[3]表示[1][2]相等。

您可以将该证明推广为负整数(仍然忽略overflow),但是我将把它留给读者以保持重点。


关于溢出的问题-请参阅Eric Lippert的答案以获得良好的解释!他还在博客文章和答案中采取了更为严格的方法,如果您觉得我太过手摇,就应该研究一下。


1
哈哈,那就是我的追求:)
罗林

我喜欢您为此使用\和/。使事情变得更加清晰。
贾斯汀·摩根

@JustinMorgan该符号实际上在其他一些编程语言中使用(尽管我现在不记得是哪种语言)。
蒂莫西·希尔兹

1
@TimothyShields VB.net可以。
阿里·肖

我认为该说法是正确的,但您的证据似乎缺少关键一步。我可能会误解了您对第2行=>第3行的辩解。我解释它的方式floor(x / y) - (x / y)很小,z >= 1因此将其floor取为0,我们可以忽略它。这实际上并没有发生,因为它实际上是一个加数floor()(例如,考虑floor(1/2)vs floor(1/2 + 1/2))。
rliu

77

我非常喜欢这个问题,因此我将其作为主题 在2013年6月4日博客。感谢您提出的好问题!


大案件很容易得到。例如:

a = 1073741823; 
b = 134217727;
c = 134217727;

因为b * c溢出到负数。

我想补充到,在事实核对算术,之间的差异a / (b * c),并(a / b) / c可以是程序之间的区别在于作品和程序崩溃。如果是bc溢出的整数的范围那么前者将在检查范围内崩溃。

对于小的正整数(例如,足够小以适合短整数),应保持身份。


蒂莫西·希尔兹(Timothy Shields)刚刚发布了证明;我在这里提出另一种证明。假定这里的所有数字都是非负整数,并且所有操作都不会溢出。

的整数除法x / y找到值,qq * y + r == x0 <= r < y

所以师a / (b * c)认定的价值q1,使得

q1 * b * c + r1 == a

哪里 0 <= r1 < b * c

该部门( a / b ) / c首先找到的价值qt

qt * b + r3 == a

然后发现值q2,使得

q2 * c + r2 == qt

因此,用它代替,qt我们得到:

q2 * b * c + b * r2 + r3 == a

在哪里0 <= r2 < c0 <= r3 < b

两个相等的事物彼此相等,所以我们有

q1 * b * c + r1 == q2 * b * c + b * r2 + r3

假设q1 == q2 + x有一个整数x。代入并解决x

q2 * b * c + x * b * c + r1 = q2 * b * c + b * r2 + r3
x  = (b * r2 + r3 - r1) / (b * c)

哪里

 0 <= r1 < b * c
 0 <= r2 < c
 0 <= r3 < b

可以x大于零吗?不,我们有不平等:

 b * r2 + r3 - r1 <= b * r2 + r3 <= b * (c - 1) + r3 < b * (c - 1) + b == b * c

因此,该分数的分子始终小于b * c,因此x不能大于零。

可以x小于零吗?不,通过类似的论点,留给读者。

因此,整数x为零,因此q1 == q2


7
@JoseRuiSantos是的,但无论是x1 x2操作将在这种情况下,相同的崩溃
马克·Gravell

@JoseRuiSantos两种情况都不对吗?
Jodrell

vc 74的答案已删除,因此大多数人无法再看到您正在引用的示例。
加布

是的,两者都正确,如果或为零x1x2都会崩溃。为其他值时,表达式是更好的,因为将避免的可能整数溢出该了。bcx1( b * c)x2
Jose Rui Santos

关于溢出和校验算法的有趣观点,谢谢!
詹森·克雷斯

4

如果绝对值bc低于约sqrt(2^31)(约46 300),这样b * c将不会溢出,该值将总是匹配。如果b * c溢出,则可以在checked上下文中引发错误,或者您可以在上下文中获得错误的值unchecked


2

避免别人注意到的溢出错误,它们总是匹配的。

让我们假设a/b=q1,这意味着a=b*q1+r1,在哪里0<=r1<b
现在假设那a/b/c=q2意味着那q1=c*q2+r2在哪里0<=r2<c
这意味着a=b(c*q2+r2)+r1=b*c*q2+br2+r1
为了这个a/(b*c)=a/b/c=q2,我们需要0<=b*r2+r1<b*c
但是b*r2+r1<b*r2+b=b*(r2+1)<=b*c,根据需要,这两个操作匹配。

如果bc为负,这将不起作用,但是在这种情况下,我也不知道整数除法是如何工作的。


0

我将提供自己的有趣证明。这也忽略了溢出,仅不幸地处理了正数,但我认为证明是干净明确的。

目的是表明

floor(floor(x/y)/z) = floor(x/y/z)

/正常除法在哪里(在整个证明中)。

我们将a/b 唯一的商和余数表示为a = kb + r(这意味着我们k,r是唯一的,也要注意|r| < |b|)。然后我们有:

(1) floor(x/y) = k => x = ky + r
(2) floor(floor(x/y)/r) = k1 => floor(x/y) = k1*z + r1
(3) floor(x/y/z) = k2 => x/y = k2*z + r2

因此,我们的目标只是表明这一点k1 == k2。好吧,我们有:

k1*z + r1 = floor(x/y) = k = (x-r)/y (from lines 1 and 2)
=> x/y - r/y = k1*z + r1 => x/y = k1*z + r1 + r/y

因此:

(4) x/y = k1*z + r1 + r/y (from above)
x/y = k2*z + r2 (from line 3)

现在从(2)观察它r1是一个整数(fork1*z定义为整数)和r1 < z(也定义为整数)。此外,从(1)我们知道r < y => r/y < 1。现在考虑r1 + r/y来自(4)的和。该声明是,r1 + r/y < z并且从先前的声明中可以很明显地看出来(因为0 <= r1 < zr1是一个整数,所以我们有0 <= r1 <= z-1。因此0 <= r1 + r/y < z)。因此,r1 + r/y = r2根据定义r2(否则将有两个余数与余数x/y的定义相矛盾)。因此,我们有:

x/y = k1*z + r2
x/y = k2*z + r2

我们有我们期望的结论k1 = k2

上面的证明应该与否定一起工作,除了需要检查额外情况的几个步骤之外……但我没有检查。


0

计数器示例:INT_MIN / -1 / 2


“让a,b和c为非大整数。”

这是一个有趣的情况(即-INT_MIN是一个溢出)。谢谢!
Jason Crease
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.