Почему деление модуля (%) работает только с целыми числами?

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фактически требует очень сложного трансцендентного процесса редукции аргумента, который не может обойтись никаким конечным приближением числа пи.
R .. GitHub ПРЕКРАТИТЕ ПОМОЩЬ ICE
3
Это почти наверняка домашнее задание, поэтому ошибки с плавающей запятой либо выходят за рамки задания, либо предназначены для обсуждения более строгого численного анализа. В любом случае, fmodэто, вероятно, то, что ищет инструктор.
Деннис Зикефуз
2
Это не домашнее задание, это просто что-то, что возникло при чтении другого вопроса SO ( stackoverflow.com/questions/6091837/… )
Брендан Лонг,
2
Хорошо, я должен был быть более точным в своем заявлении. Я хотел сказать, что если аргумент может стать неограниченно большим (а не только размером экспоненты с двойной точностью), никакого конечного приближения числа пи будет недостаточно. Для double, да, будет достаточно очень-очень длинного приближения числа Пи.
R .. GitHub НЕ ПОМОГАЕТ ICE
1
@aschepler: Не думаю, что вы поняли суть проблемы.
R .. GitHub НЕ ПОМОГАЕТ ICE

Ответы:

76

Потому что обычное математическое понятие «остаток» применимо только к целочисленному делению. т.е. деление, необходимое для создания целочисленного частного.

Чтобы распространить понятие «остаток» на действительные числа, вы должны ввести новый вид «гибридной» операции, которая будет генерировать целочисленное частное для реальных операндов. Базовый язык C не поддерживает такую ​​операцию, но он предоставляется как fmodфункция стандартной библиотеки , а также как remainderфункция в C99. (Обратите внимание, что эти функции не совпадают и имеют некоторые особенности. В частности, они не следуют правилам округления целочисленного деления.)

Муравей
источник
7
Конечно, из определения% в стандарте 98: «(a / b) * b + a% b равно a». Для типов с плавающей запятой (a/b)*bуже равно a[в той мере, в какой такой оператор может быть сделан для типов с плавающей запятой], поэтому a%bникогда не будет особенно полезен.
Деннис Зикефуз
1
@Dennis: Действительно, алгебраически в поле остаток всегда равен 0. %Я полагаю, что наиболее подходящим определением оператора для плавающей точки a-(a/b)*bбыло бы либо 0, либо очень маленькое значение.
R .. GitHub НЕ ПОМОГАЕТ ICE
7
@Dennis: Вы можете легко исправить эту формулу, потребовав, чтобы "floor (a / b) * b + a% b = a". Обратите внимание, что для целых чисел floor (a / b) = a / b.
vog
Целочисленное деление в стиле C использует усечение, а не пол, но суть остается.
dan04
18
-1 Что касается «обычное математическое понятие« остаток »применимо только к целочисленному делению», математическое понятие арифметики по модулю работает также и для значений с плавающей запятой, и это одна из первых проблем, которые Дональд Кнут обсуждает в своей классической книге « The Искусство программирования (том I). Т.е. когда-то это были базовые знания. Сегодня студенты не получают того образования, за которое платят, ИМХО.
Приветствия и hth. - Alf
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. - Alf
Запоздалый +1 от меня тоже. Раньше я писал на C для встроенных систем 6809 и Z80. Я никак не мог позволить себе место для включения библиотеки времени выполнения c. Даже пришлось написать свой собственный код запуска. Плавающая точка была роскошью, которую я не мог себе позволить :)
Ричард Ходжес
12

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

Марк Эллиот
источник
4
Это ответ на вопрос OP, но игнорирует фундаментальную проблему в том, что OP пытается сделать: sin(fmod(x,3.14))или даже sin(fmod(x,M_PI))не равно sin(x)для больших значений x. Фактически значения могут отличаться на целых 2,0.
R .. GitHub НЕ ПОМОГАЕТ ICE
2
@R ..: Верно, но это другой вопрос, и я не совсем уверен, что есть принятый ответ, хотя есть много исследований на эту тему
Марк Эллиот
@R - Я исправил уравнение, чтобы сделать это правильно. Фактическое уравнение не было сутью (это было довольно легко понять, когда у меня была функция, чтобы проверить его).
Брендан Лонг
Разве это не %оператор остатка и не оператор по модулю?
chux
7

Ограничения указаны в стандартах:

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

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

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

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

Решение состоит в том, чтобы использовать fmod, и именно поэтому операнды %ограничены целочисленным типом в первую очередь, согласно C99 Rationale §6.5.5 Мультипликативные операторы :

Комитет C89 отклонил расширение оператора% для работы с плавающими типами, поскольку такое использование дублирует возможности, предоставляемые fmod.

Ю Хао
источник
2

Оператор% дает вам НАПОМИНАНИЕ (другое название модуля) числа. Для C / C ++ это определено только для целочисленных операций. Python немного шире и позволяет вам получить остаток от числа с плавающей запятой для остатка от того, сколько раз число может быть разделено на него:

>>> 4 % math.pi
0.85840734641020688
>>> 4 - math.pi
0.85840734641020688
>>> 
Андрей
источник
2
Остаток - это не «другое название модуля» !! См .: stackoverflow.com/questions/13683563/… или из math-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 ++, ничто не мешает вам изменить устаревшее безоперационное значение, включив перегруженный оператор%.

С уважением

Любить
источник