Яке обгрунтування використовується, коли дизайнери мови програмування вирішують, на якій ознаці приймається результат роботи модуля?


9

Проходячи через модульну операцію (проспект, в який я входив, вивчаючи різницю між remіmod ), я натрапив:

У математиці результатом модульної операції є залишок поділу Евкліда. Однак можливі й інші конвенції. Комп'ютери та калькулятори мають різні способи зберігання та представлення номерів; таким чином, їх визначення модульної роботи залежить від мови програмування та / або базового обладнання.

Запитання:

  • Пройшовши Евклідовий відділ, я виявив, що продовження цієї операції завжди є позитивним (або 0). Яке обмеження базового комп'ютерного обладнання змушує дизайнерів мови програмування відрізнятися від математики?
  • Кожна мова програмування має заздалегідь визначене або невизначене правило, згідно з яким результат модульної операції отримує його знак. Яке обґрунтування прийняте під час прийняття цих правил? І якщо основне обладнання викликає занепокоєння, то чи не повинні змінювати правила відповідно до цього, незалежно від мови програмування?

1
У своєму коді мені майже завжди потрібна модуль, а не інша частина. Не маю уявлення, чому залишок настільки популярний.
CodesInChaos

8
Пов'язане У чому різниця? Залишився проти Модулу - Блог Еріка Ліпперта (один із дизайнерів C #, але я вважаю, що він приєднався до команди після прийняття цього рішення)
CodesInChaos

1
Якщо ви продовжуєте читати статтю у Вікіпедії (поза частиною, яку ви цитували), це пояснює те, що ви цитували досить добре. Що з цього пояснення вас бентежить?
Роберт Харві

1
Одне з пов'язаних питань - яка з цих операцій безпосередньо відображається в інструкціях CPU. У c його визначено реалізацію, яка відповідає філософії c безпосередньо відображення на апаратному забезпеченні на якомога більшій кількості платформ. Таким чином, він не визначає речі, які можуть відрізнятися між процесорами.
CodesInChaos

5
@BleedingFingers Програмування часто використовує ціле ділення, яке йде до нуля, наприклад (-3)/2 == -1. Це визначення може бути корисним. Коли ви хочете %бути узгодженими з цим підрозділом, x == (x/y)*y + x % yви закінчуєте визначення %використовуваного в C #.
CodesInChaos

Відповіді:


7

Апаратне забезпечення всіх сучасних комп’ютерів є достатньо потужним для здійснення модних операцій будь-якого знаку без впливу (або тривіального) продуктивності. Це не причина.

Загальне сподівання більшості комп'ютерних мов полягає в тому, що (div b) * b + (a mod b) = a. Іншими словами, розглянутий div та mod разом ділять ряд на частини, які можна надійно знову скласти разом. Ця вимога є чіткою у стандарті C ++. Концепція тісно пов'язана з індексуванням багатовимірних масивів. Я його часто використовував.

З цього видно, що div та mod збережуть знак a, якщо b є позитивним (як це зазвичай є).

Деякі мови надають функцію 'rem ()', яка пов'язана з модом і має деякі інші математичні обгрунтування. Мені ніколи не потрібно було цим користуватися. Дивіться, наприклад, frem () у Gnu C. [ред.]


Я думаю, що rem(a,b)це більше подібне, mod(a,b)якщо воно є позитивним чи mod(a,b) + bні.
user40989

3
(a div b) * b + (a mod b) = a- це, так дуже багато. Насправді, всупереч тому, як Вікіпедія описує розширення її до негативних чисел у поділі Евкліда (особливо "Залишок - це єдине з чотирьох чисел, яке ніколи не може бути негативним") мене бентежить, бо мене завжди вчили, що залишок може бути негативним у кожному класі математики на цьому рівні.
Ізката

@ user40989: Я сказав, що ніколи його не використовував. Дивіться редагування!
david.pfx

4

Для програмування зазвичай потрібно X == (X/n)*n + X%n; тому спосіб визначення модуля залежить від того, як було визначено ціле ділення.

Зважаючи на це, ви справді запитуєте " Яке обгрунтування використовується, коли дизайнери мови програмування вирішують, як працює ціле поділ? "

Насправді близько 7 варіантів:

  • кругла до негативної нескінченності
  • кругла до позитивної нескінченності
  • круглий до нуля
  • кілька версій "округлення до найближчого" (з різницею в тому, як округлюється щось на зразок 0,5)

Тепер розглянемо -( (-X) / n) == X/n. Я хотів би, щоб це було правдою, оскільки все інше здається непослідовним (це справедливо для плаваючої точки) та нелогічним (ймовірна причина помилок, а також потенційно пропущена оптимізація). Це робить перші 2 варіанти для цілого поділу (округлення до будь-якої нескінченності) небажаним.

Усі варіанти "раунду до найближчого" - це біль у шиї при програмуванні, особливо коли ви робите щось на зразок растрових зображень (наприклад offset = index / 8; bitNumber = index%8;).

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

Примітка. Ви також зазначите, що більшість процесорів (усі процесори, які мені відомі) роблять ціле поділ тим самим "круглим до нуля" способом. Це може бути з тих же причин.


Але у підрізаючого поділу є і свої непослідовності: він руйнується (a+b*c)/b == a % bі a >> n == a / 2 ** n, для якого розвідний поділ має звичну поведінку.
dan04

Ваш перший приклад не має сенсу. Ваш другий приклад - безлад для програмістів: для позитивного a і позитивного n це послідовно, для негативного a і позитивного n залежить від того, як визначено правильний зсув (арифметичний проти логічного), а для негативного n - порушений (наприклад 1 >> -2 == a / 2 ** (-2)).
Брендан

Перший приклад був помилковим помилкою: я мав на увазі (a + b * c) % b == a % b, тобто %оператор є дивідендом-періодиком, що часто важливо. Наприклад, за допомогою розділеного поділу day_count % 7дає вам день тижня, але з обрізанням поділу це перерва на дати до епохи.
dan04

0

По-перше, я повторю, що модуль b повинен дорівнювати a - b * (div b), і якщо мова цього не передбачає, ви перебуваєте в жахливому математичному безладі. Цей вираз a - b * (div b) насправді, скільки реалізацій обчислює модуль b.

Існують деякі можливі обґрунтування. Перший полягає в тому, що ви хочете отримати максимальну швидкість, тому div b визначається як все, що забезпечить використовуваний процесор. Якщо ваш процесор має інструкцію "div", то div b - це все, що робить інструкція div (до тих пір, поки це щось не зовсім божевільне).

Друга полягає в тому, що вам потрібно певну математичну поведінку. Давайте спочатку припустимо, що b> 0. Цілком розумно ви хочете, щоб результат ділення b округлювався до нуля. Отже, 4 діви 5 = 0, 9 дів 5 = 1, -4 дів 5 = -0 = 0, -9 дів 5 = -1. Це дає вам (-a) div b = - (a div b) та (-a) модуль b = - (модуль b).

Це цілком розумно, але не ідеально; наприклад (a + b) div b = (a div b) + 1 не відповідає, скажімо, якщо a = -1. При фіксованому b> 0 зазвичай є (b) можливі значення для такого, щоб div b дав такий же результат, за винятком випадків, коли є значення 2b - 1 a від -b + 1 до b-1, де div b дорівнює 0 Це також означає, що модуль b буде від'ємним, якщо a - негативним. Ми хотіли б, щоб модуль b завжди був числом в діапазоні від 0 до b-1.

З іншого боку, також цілком розумно вимагати, щоб під час проходження послідовних значень a модуль b повинен пройти через значення від 0 до b-1, а потім знову почати з 0. І запитувати, що (a + b) div b має бути (div b) + 1. Щоб досягти цього, потрібно, щоб результат діла b був округнутий до -безмежностей, тому -1 div b = -1. Знову ж таки, є і недоліки. (-a) div b = - (div b) не втримується. Повторне ділення на два або на будь-яке число b> 1, врешті-решт, не дасть результату 0.

Оскільки існують конфлікти, мови повинні вирішити, який набір переваг для них важливіший, і вирішити відповідно.

Що стосується від'ємного b, то більшість людей не можуть зв'язатись із тим, що в першу чергу повинні бути дів b та модуль b, тому простий спосіб - визначити, що div b = (-a) div (-b) та a modulo b = (-a) modulo (-b), якщо b <0, або будь-який природний результат використання коду для додатного b.

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