Чому поділ за модулем (%) працює лише з цілими числами?


79

Нещодавно я зіткнувся з проблемою , яку легко можна було вирішити за допомогою поділу за модулем, але вхідним сигналом було плаваюче:

Враховуючи періодичну функцію (наприклад sin) та комп’ютерну функцію, яка може обчислювати її лише в межах діапазону періодів (наприклад, [-π, π]), створіть функцію, яка може обробляти будь-який вхід.

"Очевидне" рішення є приблизно таким:

#include <cmath>

float sin(float x){
    return limited_sin((x + M_PI) % (2 *M_PI) - M_PI);
}

Чому це не працює? Я отримую цю помилку:

error: invalid operands of types double and double to binary operator %

Цікаво, що це працює в Python:

def sin(x):
    return limited_sin((x + math.pi) % (2 * math.pi) - math.pi)

20
π не дорівнює 3,14, і насправді його не можна представити як будь-який тип з плаваючою комою. Обчислення sin(x)великих значень xнасправді вимагає дуже складного процесу трансцендентального скорочення аргументів, який не може пройти при будь-якому кінцевому наближенні pi.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

3
Це майже напевно домашнє завдання, тому помилки з плаваючою комою або виходять за рамки завдання, або це повинно призвести до обговорення більш суворого чисельного аналізу. У будь-якому випадку, fmodшвидше за все те, що викладач шукає.
Денніс Зікфуз,

2
Це не домашнє завдання, це просто щось, що з’явилося під час читання чергового запитання SO ( stackoverflow.com/questions/6091837/… )
Брендан Лонг

2
Добре, я повинен був бути точнішим у своєму висловлюванні. Моя думка полягала в тому, що якщо аргумент може зростати необмежено великим (а не лише величиною експоненти подвійної точності), жодного кінцевого наближення pi буде недостатньо. Для подвійного, так, буде достатньо дуже довгого наближення пі.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

1
@aschepler: Я не думаю, що ви зрозуміли проблему.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕДІ

Відповіді:


76

Оскільки звичайне математичне поняття "залишок" застосовне лише до цілочисельного ділення. тобто поділ, який необхідний для генерування цілого числа.

Для того, щоб розширити поняття "залишок" до дійсних чисел, вам доведеться запровадити новий вид "гібридної" операції, яка генерує цілий коефіцієнт для реальних операндів. Мова Core C не підтримує таку операцію, але вона надається як стандартна fmodфункція бібліотеки , як і remainderфункція в C99. (Зверніть увагу, що ці функції не однакові і мають деякі особливості. Зокрема, вони не відповідають правилам округлення цілочисельного ділення.)


7
Варто, з визначення% у стандарті 98: "(a / b) * b + a% b дорівнює a." Для типів з плаваючою комою (a/b)*bвже дорівнює a[настільки, наскільки таке твердження може бути зроблено для типів із плаваючою комою], тому a%bніколи не буде особливо корисним.
Денніс Зікфуз,

1
@ Денніс: Дійсно, алгебраїчно, у полі залишок завжди дорівнює 0. Найбільш відповідним визначенням %оператора для плаваючої крапки, я вважаю, було б a-(a/b)*b, що було б або 0, або дуже мале значення.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

7
@Dennis: Ви можете легко виправити цю формулу, вимагаючи "нижній (a / b) * b + a% b = a". Зверніть увагу, що для цілих чисел підлога (a / b) = a / b.
вог

Цілочисельний поділ у стилі С використовує trunc, а не підлогу, але суть залишається.
dan04

18
-1 "Нормальне математичне поняття" залишок "застосовується лише до цілочисельного ділення", математичне поняття арифметики за модулем працює також для значень з плаваючою комою, і це одне з перших питань, яке Дональд Кнут обговорює у своїй класичній книзі " Мистецтво комп'ютерного програмування (том I). Тобто це колись були базові знання. Сьогодні студенти не отримують освіту, за яку платять, ІМХО.
Вітаю і hth. - Альф

52

Ви шукаєте fmod () .

Я думаю, щоб конкретніше відповісти на ваше запитання, у старих мовах %оператор просто визначався як цілочисельний модульний поділ, а в нових мовах вони вирішили розширити визначення оператора.

РЕДАГУВАТИ: Якби я ставлю вгадати, чому, я б сказав, що це тому, що ідея модульної арифметики бере свій початок у теорії чисел і стосується саме цілих чисел.


9
"старіші мови" - APL датується 1960-х роками, а його оператор за модулем "|" працює як з цілими числами, так і з даними з плаваючою комою (також зі скаляром, вектором, матрицею, тензором, ...). Немає поважних причин, що оператор модуля "%" C не міг би виконувати ту саму функцію, що і fmod, якщо використовувався з числами з плаваючою комою.
rcgldr

@rcgldr Цілі проектування не вимагали модуля з плаваючою комою. C був реалізований для компіляції Unix та обмеження кількості мови збірки, необхідної для ОС. "C є обов’язковою процедурною мовою. Він був розроблений для компіляції з використанням відносно простого компілятора, щоб забезпечити низькорівневий доступ до пам'яті, забезпечити мовні конструкції, які ефективно відповідають машинним інструкціям, і вимагати мінімальної підтримки часу виконання". en.wikipedia.org/wiki/C_(programming_language)
музикант

1
@harper - Оскільки C включає арифметику з плаваючою комою, таку як додавання, віднімання, множення та ділення, використовуючи той самий синтаксис, що і для цілих чисел, я не розумію, чому він також не міг включити модуль із використанням того самого синтаксису (%) . Вибір включити його чи ні видається довільним.
rcgldr

16

Я не можу точно сказати точно , але я думаю, це в основному історично. Досить багато ранніх компіляторів C взагалі не підтримували плаваючу крапку. Він був доданий пізніше, і навіть тоді не як повністю - в основному був доданий тип даних, і найбільш підтримуються примітивні операції на мові, але все інше залишилося в стандартній бібліотеці.


1
+1 за першу розумну відповідь, яку я бачу під час читання списку. Насправді, прочитавши їх усі, це єдина розумна відповідь.
Вітаю і hth. - Альф

Запізнілий +1 від мене теж. Раніше я писав мовою C для вбудованих систем 6809 та Z80. Я ніяк не міг дозволити собі місця для включення бібліотеки середовища виконання c. Навіть довелося писати власний стартовий код. Плаваюча точка була розкішшю, яку я не міг собі дозволити :)
Річард Ходжес

12

Оператор модуля %в C та C ++ визначений для двох цілих чисел, однак існує fmod()функція, доступна для використання з подвійними.


4
Це відповідь на запитання OP, але ігнорує основну проблему того, що OP намагається зробити: sin(fmod(x,3.14))або навіть sin(fmod(x,M_PI))не дорівнює sin(x)великим значенням x. Насправді значення можуть відрізнятися на цілих 2,0.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

2
@R ..: Правильно, але це вже інше питання, і я не зовсім впевнений, що є прийнята відповідь, хоча є багато досліджень на цю тему
Марк Елліот,

@R - Я зафіксував рівняння, щоб зробити це правильно. Фактичне рівняння не було суттю (це було досить легко зрозуміти, коли у мене була функція, щоб перевірити це).
Брендан Лонг

Хіба не %оператор залишку і не оператор за модулем?
chux

7

Обмеження полягають у стандартах:

C11 (ISO / IEC 9899: 201x) §6.5.5 Мультиплікативні оператори

Кожен з операндів повинен мати арифметичний тип. Операнди оператора% повинні мати цілочисельний тип.

C ++ 11 (ISO / IEC 14882: 2011) §5.6 Мультиплікативні оператори

Операнди * та / повинні мати арифметичний або нумераційний тип; операнди% повинні мати інтегральний тип або тип нумерації. Звичайні арифметичні перетворення виконуються на операндах і визначають тип результату.

Рішення полягає у використанні fmod, і саме тому операнди of %, насамперед, обмежуються цілочисельним типом, відповідно до C99 Обґрунтування §6.5.5 Мультиплікативні оператори :

Комітет C89 відхилив розширення оператора% для роботи над плаваючими типами, оскільки таке використання дублює можливість, надану fmod



2

Оператор% дає вам ЗАСТАВКУ (інша назва модуля) числа. Для C / C ++ це визначено лише для цілочисельних операцій. Python трохи ширший і дозволяє отримати залишок від числа з плаваючою точкою до залишку того, скільки разів число можна розділити на нього:

>>> 4 % math.pi
0.85840734641020688
>>> 4 - math.pi
0.85840734641020688
>>> 

2
Залишок - це не "інша назва модуля" !! Див: stackoverflow.com/questions/13683563 / ... або через математичного POV: math.stackexchange.com/questions/801962 / ... Простіше кажучи: по модулю і інші є лише те ж саме для позитивних чисел і ще один приклад, що залишок Байдуже » t дозволяють обійти компас (проти годинникової стрілки). Будь ласка, виправте це, оскільки я маю ощадливий голос проти:P
GitaarLAB

2

%Оператор не працює в C ++, коли ви намагаєтеся знайти залишок двох чисел , які є обидва типи FloatабоDouble .

Отже, ви можете спробувати використовувати fmodфункцію з math.h/ cmath.hабо ви можете використовувати ці рядки коду, щоб уникнути використання цього заголовного файлу:

float sin(float x) {
 float temp;
 temp = (x + M_PI) / ((2 *M_PI) - M_PI);
 return limited_sin((x + M_PI) - ((2 *M_PI) - M_PI) * temp ));

}


1

"Математичне поняття модульної арифметики працює і для значень з плаваючою комою, і це одне з перших питань, яке Дональд Кнут обговорює у своєму класичному" Мистецтві комп'ютерного програмування "(том I). Тобто це колись були базові знання".

Оператор модуля з плаваючою точкою визначається наступним чином:

m = num - iquot*den ; where iquot = int( num/den )

Як зазначалося, відмова оператора% щодо чисел із плаваючою комою, схоже, пов'язана зі стандартами. CRTL надає "fmod", а також, як правило, "залишок", щоб виконати% на числах fp. Різниця між цими двома полягає в тому, як вони обробляють проміжне округлення "iquot".

'залишок' використовує округлення до найближчого, а 'fmod' використовує просте скорочення.

Якщо ви пишете власні числові класи на C ++, ніщо не заважає вам внести зміни до застарілої операції, включивши перевантажений оператор%.

З найкращими побажаннями

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