В С# целочисленной арифметике, a/b/c всегда равно a/(b * c)?

Пусть a, b и c - не большие положительные целые числа. Всегда ли a/b/c равен a/(b * c) с С# целочисленной арифметикой? Для меня в С# это выглядит так:

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

Итак, мой вопрос: делает x1 == x2 для всех a, b и c?

Ответ 1

Пусть \ обозначает целочисленное деление (оператор С# / между двумя int s), а / обозначает обычное математическое деление. Тогда, если 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] выше объясняется следующим образом. Предположим, что у вас есть два целых числа a и b и дробное число 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] равны.

Вы можете обобщить это доказательство на отрицательные целые числа (все еще игнорирование переполнения), но я оставлю это для чтения, чтобы точка была простой.


В вопросе переполнения - см. ответ Эрика Липперта за хорошее объяснение! Он также берет гораздо более строгий подход в своем сообщении в блоге и отвечает, что-то, на что вы должны обратить внимание, если чувствуете, что я слишком волнительный.

Ответ 2

Мне так понравился этот вопрос, я сделал его предметом моего блога 4 июня 2013 г.. Спасибо за отличный вопрос!


Большие случаи легко найти. Например:

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

потому что b * c переполняется до отрицательного числа.

Я бы добавил, что тот факт, что в проверенной арифметике разница между a / (b * c) и (a / b) / c может быть разницей между программой, которая работает, и программой, которая сбой. Если произведение b и c переполняет границы целого числа, тогда первый будет сбой в проверенном контексте.

Для небольших положительных целых чисел, скажем, достаточно малых, чтобы вписаться в короткий, идентификация должна поддерживаться.


Тимоти Шилдс только что опубликовал доказательство; Я представляю здесь альтернативное доказательство. Предположим, что все числа здесь - неотрицательные целые числа и ни одно из операций переполнения.

Целочисленное деление x / y находит значение q такое, что q * y + r == x, где 0 <= 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 < c и 0 <= 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.

Ответ 3

Если абсолютные значения b и c ниже примерно sqrt(2^31) (около 46 300), так что b * c никогда не будет переполняться, значения всегда будут совпадать. Если b * c переполняется, тогда ошибка может быть вызвана в контексте checked, или вы можете получить неправильное значение в контексте unchecked.

Ответ 4

Избегая ошибок переполнения, замеченных другими, они всегда совпадают.

Предположим, что 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, если требуется, и две операции совпадают.

Это не работает, если b или c являются отрицательными, но я не знаю, как работает целочисленное деление в этом случае.

Ответ 5

Я предложу свое собственное доказательство для удовольствия. Это также игнорирует переполнение и, к сожалению, обрабатывает только положительные результаты, но я считаю, что доказательство чистое и ясное.

Цель состоит в том, чтобы показать, что

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 является целым числом (для k1*z является целочисленным по определению) и r1 < z (также по определению). Кроме того, из (1) известно, что r < y => r/y < 1. Теперь рассмотрим сумму r1 + r/y из (4). Утверждение состоит в том, что r1 + r/y < z и это ясно из предыдущих утверждений (потому что 0 <= r1 < z и r1 является целым числом, поэтому мы имеем 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.

Вышеприведенное доказательство должно работать с негативами, за исключением нескольких шагов, которые вам нужно будет проверить для дополнительного случая (я)... но я не проверял.

Ответ 6

встречный пример: INT_MIN/-1/2