У цілочисельній арифметиці C # чи завжди a / b / c дорівнює a / (b * c)?


81

Нехай a, b і c - великі натуральні числа. Чи завжди a / b / c дорівнює a / (b * c) цілочисельній арифметиці C #? Для мене в C # це виглядає так:

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

Тож моє запитання: чи x1 == x2для всіх a, b та c?


3
Це питання математики, а не програмування. Чи можете ви пояснити, в чому полягає конкретна частина цього питання щодо програмування?
Почато

38
Звичайно, додано в область будь-якого раціонального числа, але це конкретно стосується цілочисельної арифметики (у C #). IMO, що робить це пов'язаним із програмуванням. Можливо правило, що a / b / c == a / (b * c), виконується в цілочисельній арифметиці, можливо, воно виконується лише в арифметиці раціонального числа.
Тім С.

43
Це цілком розумне запитання про C # і на нього легко відповісти.
Ерік Ліпперт,

12
@Oded Це питання про комп’ютерну арифметику та чи вона поводиться так само, як чиста математика. Його не слід закривати.
Джеффрі Сакс,

4
Мене цілком зацікавив би математичний доказ того, чому (чи справді), ігноруючи переливи, ці два фактично еквівалентні, але мені ще не вдалося скласти один.
Роулінг

Відповіді:


71

Нехай \позначає ціле ділення ( /оператор C # між двома ints) і нехай /позначає звичайне математичне ділення. Тоді, якщо 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]рівні.

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


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


1
Ха-ха, це те, чим я займався :)
Роулінг

Мені подобається ваше використання \ та / для цього. Робить речі набагато чіткішими.
Джастін Морган,

@JustinMorgan Нотація фактично використовується в деяких інших мовах програмування (хоча на даний момент я не пам’ятаю, які саме).
Тімоті Шилдс,

1
@TimothyShields VB.net робить.
Arie Xiao

Я думаю, що твердження відповідає дійсності, але, здається, у вашому доказі відсутній ключовий крок. Цілком можливо, я неправильно зрозумів ваше виправдання для рядка 2 => рядок 3. То, як я його інтерпретував, було floor(x / y) - (x / y)невеликим, z >= 1тому, приймаючи значення floor0, ми можемо його ігнорувати. Це насправді не випливає, оскільки це насправді додавання всередині floor()(тобто розглянемо floor(1/2)проти floor(1/2 + 1/2)).
rliu

77

Це питання мені так сподобалось, що я став предметом свого блогу 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.


7
@JoseRuiSantos так, але як x1 іx2 операція буде врізатися тотожне в цьому випадку
Marc Gravell

@JoseRuiSantos це не відповідає обом випадкам?
Джодрелл

Відповідь vc 74 видалено, тому більшість людей більше не можуть бачити приклад, на який ви посилаєтесь.
Гейб,

Це правильно, і те, x1і інше x2вийде з ладу, якщо bабо cдорівнює нулю. Для інших значень x1вираз є кращим, оскільки уникне можливого цілочисельного переповнення ( b * c)цього значення x2.
Хосе Руї Сантос

Цікавий момент щодо переповнення та перевіреної арифметики, дякую!
Джейсон Кріс

4

Якщо абсолютні значення bі cнижче приблизно 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 , як потрібно, обидві операції збігаються.

Це не працює , якщо bабо cнегативні, але я не знаю , як цілий підрозділ працює в цьому випадку або.


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є цілим числом (бо 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.

Наведений вище доказ повинен працювати з негативами, за винятком кількох кроків, які вам знадобляться для перевірки додаткових випадків ... але я не перевіряв.


0

приклад лічильника: INT_MIN / -1 / 2


"Нехай a, b і c - великі цілі натуральні числа."
Pang

Це цікавий випадок (тобто -INT_MIN - переповнення). Дякую!
Jason Crease
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.