Dlaczego dzielenie modułu (%) działa tylko w przypadku liczb całkowitych?

79

Niedawno napotkałem problem, który można łatwo rozwiązać za pomocą dzielenia modułu, ale dane wejściowe były zmiennoprzecinkowe:

Biorąc pod uwagę funkcję okresową (np. sin) I funkcję komputera, która może ją obliczyć tylko w zakresie okresu (np. [-Π, π]), utwórz funkcję, która może obsłużyć dowolne dane wejściowe.

„Oczywistym” rozwiązaniem jest coś takiego:

#include <cmath>

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

Dlaczego to nie działa? Otrzymuję ten błąd:

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

Co ciekawe, działa w Pythonie:

def sin(x):
    return limited_sin((x + math.pi) % (2 * math.pi) - math.pi)
Brendan Long
źródło
20
π nie jest równe 3,14 i faktycznie nie jest reprezentowane jako żaden typ zmiennoprzecinkowy. Obliczanie sin(x)dla dużych wartości xfaktycznie wymaga bardzo trudnego transcendentalnego procesu redukcji argumentów, którego nie da się obejść z żadnym skończonym przybliżeniem liczby pi.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
3
Jest to prawie na pewno zadanie domowe, więc błędy zmiennoprzecinkowe albo wykraczają poza zakres zadania, albo ma to prowadzić do dyskusji na temat bardziej rygorystycznej analizy numerycznej. Tak czy inaczej, fmodjest to prawdopodobnie to, czego szuka instruktor.
Dennis Zickefoose
2
To nie jest praca domowa, to po prostu coś, co pojawiło się podczas czytania innego pytania SO ( stackoverflow.com/questions/6091837/… )
Brendan Long
2
OK, powinienem być bardziej precyzyjny w swoim oświadczeniu. Chodziło mi o to, że jeśli argument może rosnąć bez ograniczeń (nie tylko o podwójnej precyzji), to żadne skończone przybliżenie liczby pi nie wystarczy. W przypadku podwójnego, tak, wystarczy bardzo, bardzo długie przybliżenie liczby pi.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
1
@aschepler: Myślę, że nie zrozumiałeś problemu.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE

Odpowiedzi:

76

Ponieważ zwykłe matematyczne pojęcie „reszty” ma zastosowanie tylko do dzielenia liczb całkowitych. tj. dzielenie wymagane do wygenerowania ilorazu całkowitego.

Aby rozszerzyć pojęcie „reszty” na liczby rzeczywiste, należy wprowadzić nowy rodzaj operacji „hybrydowej”, która generowałaby iloraz liczb całkowitych dla rzeczywistych operandów. Język Core C nie obsługuje takiej operacji, ale jest dostarczany jako standardowa fmodfunkcja biblioteczna , a także remainderfunkcja w C99. (Zauważ, że te funkcje nie są takie same i mają pewne cechy szczególne. W szczególności nie są zgodne z zasadami zaokrąglania dzielenia liczb całkowitych).

Mrówka
źródło
7
Warte, z definicji% w standardzie 98: „(a / b) * b + a% b równa się a”. Dla typów zmiennoprzecinkowych (a/b)*bjuż jest równa a[o ile taka instrukcja może być wykonana dla typów zmiennoprzecinkowych], więc a%bnigdy nie byłaby szczególnie przydatna.
Dennis Zickefoose
1
@Dennis: Rzeczywiście, algebraicznie reszta w polu jest zawsze równa 0. Najodpowiedniejsza definicja %operatora dla zmiennoprzecinkowego, jak przypuszczam, byłaby równa a-(a/b)*b0 lub bardzo mała wartość.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
7
@Dennis: Możesz łatwo naprawić tę formułę, wymagając, aby „podłoga (a / b) * b + a% b = a”. Zwróć uwagę, że dla liczb całkowitych floor (a / b) = a / b.
vog
Dzielenie liczb całkowitych w stylu C używa obcięcia, a nie podłogi, ale punkt pozostaje.
dan04
18
-1 Re "normalne matematyczne pojęcie" reszty "ma zastosowanie tylko do dzielenia liczb całkowitych", matematyczne pojęcie arytmetyki modulo sprawdza się również dla wartości zmiennoprzecinkowych i jest to jedna z pierwszych kwestii, które Donald Knuth omawia w swoim klasycznym The Sztuka programowania (tom I). To znaczy była to kiedyś podstawowa wiedza. Dzisiaj studenci nie otrzymują edukacji, za którą płacą, IMHO.
Pozdrawiam i hth. - Alf
52

Szukasz fmod () .

Myślę, że aby dokładniej odpowiedzieć na twoje pytanie, w starszych językach %operator został po prostu zdefiniowany jako całkowity podział modularny, aw nowszych językach zdecydowano się rozszerzyć definicję operatora.

EDYCJA: Gdybym miał się założyć, dlaczego, powiedziałbym, że to dlatego, że idea arytmetyki modularnej wywodzi się z teorii liczb i zajmuje się konkretnie liczbami całkowitymi.

Doug Stephen
źródło
9
"starsze języki" - APL sięga lat sześćdziesiątych XX wieku, a jego operator modulo "|" działa zarówno z liczbami całkowitymi, jak i zmiennoprzecinkowymi (również z danymi skalarnymi, wektorami, macierzami, tensorami, ...). Nie ma dobrego powodu, dla którego operator modulo "%" w C nie mógłby wykonać tej samej funkcji co fmod, jeśli jest używany z liczbami zmiennoprzecinkowymi.
rcgldr
@rcgldr Cele projektowe nie wymagały modulo zmiennoprzecinkowego. C został zaimplementowany w celu kompilacji Uniksa i ograniczenia ilości języka asemblera potrzebnego dla systemu operacyjnego. „C jest koniecznym językiem proceduralnym. Został zaprojektowany do kompilacji przy użyciu stosunkowo prostego kompilatora, w celu zapewnienia niskiego poziomu dostępu do pamięci, zapewnienia konstrukcji językowych, które wydajnie odwzorowują instrukcje maszynowe i wymagały minimalnej obsługi w czasie wykonywania”. en.wikipedia.org/wiki/C_(programming_language)
harper
1
@harper - Ponieważ C zawiera arytmetykę zmiennoprzecinkową, taką jak dodawanie, odejmowanie, mnożenie i dzielenie, używając tej samej składni, co w przypadku liczb całkowitych, nie rozumiem, dlaczego nie mógł również uwzględnić modulo przy użyciu tej samej składni (%) . Wybór, czy to uwzględnić, czy nie, wydaje się arbitralny.
rcgldr
16

Nie mogę powiedzieć na pewno , ale myślę, że to głównie historia. Sporo wczesnych kompilatorów C w ogóle nie obsługiwało zmiennoprzecinkowych. Został dodany później, a nawet wtedy nie tak kompletnie - dodano głównie typ danych i najbardziej prymitywne operacje obsługiwane w języku, ale wszystko inne pozostawiono w bibliotece standardowej.

Jerry Coffin
źródło
1
+1 za pierwszą rozsądną odpowiedź, którą widzę, czytając listę. Właściwie, po przeczytaniu ich wszystkich, jest to jedyna rozsądna odpowiedź.
Pozdrawiam i hth. - Alf
Spóźnione +1 ode mnie. Kiedyś pisałem w C dla systemów wbudowanych 6809 i Z80. Nie mogłem sobie pozwolić na miejsce na dołączenie biblioteki wykonawczej c. Musiałem nawet napisać własny kod startowy. Floating point to luksus, na który nie mogłem sobie pozwolić :)
Richard Hodges
12

Operator modulo % w C i C ++ jest zdefiniowany dla dwóch liczb całkowitych, jednak istnieje fmod()funkcja dostępna do użycia z podwójnymi.

Mark Elliot
źródło
4
To jest odpowiedź na pytanie OP, ale pomija fundamentalny problem w tym, co próbuje zrobić PO: sin(fmod(x,3.14))lub nawet sin(fmod(x,M_PI))nie jest równy sin(x)dla dużych wartości x. W rzeczywistości wartości mogą różnić się nawet o 2,0.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
2
@R ..: Tak, ale to inne pytanie i nie jestem do końca pewien, czy istnieje akceptowana odpowiedź, chociaż jest wiele badań na ten temat
Mark Elliot
@R - poprawiłem równanie, żeby zrobić to poprawnie. Faktyczne równanie nie było celem (dość łatwo było to rozgryźć, gdy miałem funkcję, aby je przetestować).
Brendan Long
Czy nie %jest operatorem reszty i nie jest operatorem modulo?
chux - Przywróć Monikę
7

Ograniczenia znajdują się w normach:

C11 (ISO / IEC 9899: 201x) §6.5.5 Operatory multiplikatywne

Każdy z operandów będzie miał typ arytmetyczny. Operandy operatora% są typu całkowitego.

C ++ 11 (ISO / IEC 14882: 2011) §5.6 Operatory multiplikatywne

Operandy * i / będą miały typ arytmetyczny lub wyliczeniowy; argumenty% są typu całkowego lub wyliczeniowego. Zwykłe konwersje arytmetyczne są wykonywane na operandach i określają typ wyniku.

Rozwiązaniem jest użycie fmod, i właśnie dlatego operandy %są w pierwszej kolejności ograniczone do typu całkowitego, zgodnie z uzasadnieniem C99 §6.5.5 Operatory multiplikatywne :

Komitet C89 odrzucił rozszerzenie operatora% do pracy nad typami zmiennymi, ponieważ takie użycie mogłoby powielić funkcję zapewnianą przez fmod

Yu Hao
źródło
3

próbować fmod

Justin
źródło
2

Operator% podaje REMAINDER (inną nazwę modułu) liczby. W przypadku C / C ++ jest to zdefiniowane tylko dla operacji na liczbach całkowitych. Python jest nieco szerszy i pozwala uzyskać pozostałą część liczby zmiennoprzecinkowej dla pozostałej części tego, ile razy można ją podzielić:

>>> 4 % math.pi
0.85840734641020688
>>> 4 - math.pi
0.85840734641020688
>>> 
Andrzej
źródło
2
Reszta nie jest „inną nazwą modułu” !! Zobacz: stackoverflow.com/questions/13683563/… lub z math-pov: math.stackexchange.com/questions/801962/ ... Mówiąc prościej: modulo i reszta są takie same tylko dla liczb dodatnich, a innym przykładem jest to, że reszta nie robi t pozwalam ci obejść kompas (przeciwnie do ruchu wskazówek zegara). Popraw to, ponieważ jestem oszczędny, aby głosować przeciw:P
GitaarLAB
2

%Operator nie działa w C ++, kiedy próbują znaleźć resztę z dwóch liczb, które są zarówno typu Floatlub Double.

Dlatego możesz spróbować użyć fmodfunkcji z math.h/ cmath.hlub możesz użyć tych linii kodu, aby uniknąć używania tego pliku nagłówkowego:

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 ));

}

Cholewka
źródło
1

„Matematyczne pojęcie arytmetyki modulo sprawdza się również w przypadku wartości zmiennoprzecinkowych i jest to jedna z pierwszych kwestii, które Donald Knuth omawia w swojej klasycznej sztuce programowania komputerowego (tom I). To znaczy była to kiedyś podstawowa wiedza”.

Operator modułu zmiennoprzecinkowego jest zdefiniowany w następujący sposób:

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

Jak wskazano, brak operacji operatora% na liczbach zmiennoprzecinkowych wydaje się być związany ze standardami. CRTL zapewnia „fmod”, a zwykle także „resztę”, aby wykonać% na liczbach fp. Różnica między tymi dwoma polega na tym, jak radzą sobie z pośrednim zaokrągleniem „iquot”.

„reszta” używa zaokrąglenia do najbliższej, a „fmod” używa prostego obcięcia.

Jeśli piszesz własne klasy numeryczne w C ++, nic nie stoi na przeszkodzie, aby zmienić starszą wersję bez operacji, włączając przeciążony operator%.

Z poważaniem

Miłość
źródło