Warum funktioniert die Modulteilung (%) nur mit ganzen Zahlen?


79

Ich bin kürzlich auf ein Problem gestoßen, das mit der Modulteilung leicht gelöst werden konnte, aber die Eingabe war ein Float:

Erstellen Sie mit einer periodischen Funktion (z. B. sin) und einer Computerfunktion, die sie nur innerhalb des Periodenbereichs berechnen kann (z. B. [-π, π]), eine Funktion, die jede Eingabe verarbeiten kann.

Die "offensichtliche" Lösung ist so etwas wie:

#include <cmath>

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

Warum funktioniert das nicht? Ich erhalte diesen Fehler:

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

Interessanterweise funktioniert es in Python:

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

20
π ist nicht gleich 3,14 und kann tatsächlich nicht als Gleitkommatyp dargestellt werden. Das Berechnen sin(x)großer Werte von xerfordert tatsächlich einen sehr schwierigen Prozess zur Reduzierung transzendentaler Argumente, der mit keiner endlichen Näherung von pi auskommt.
R .. GitHub STOP HELPING ICE

3
Dies ist mit ziemlicher Sicherheit eine Hausaufgabe, sodass Gleitkommafehler entweder außerhalb des Aufgabenbereichs liegen oder zu einer Diskussion strengerer numerischer Analysen führen sollen. In jedem Fall fmodist es wahrscheinlich, wonach der Ausbilder sucht.
Dennis Zickefoose

1
Es ist keine Hausaufgabe, es ist nur etwas, das beim Lesen einer anderen SO-Frage auftauchte ( stackoverflow.com/questions/6091837/… )
Brendan Long

2
OK, ich hätte in meiner Aussage genauer sein sollen. Mein Punkt war, dass, wenn das Argument unbegrenzt groß werden kann (nicht nur Exponenten mit doppelter Genauigkeit), keine endliche Approximation von pi ausreichen wird. Für double, ja, wird eine sehr sehr lange Annäherung von pi ausreichen.
R .. GitHub STOP HELPING ICE

1
@aschepler: Ich glaube nicht, dass du das Problem verstanden hast.
R .. GitHub STOP HELPING ICE

Antworten:


76

Weil der normale mathematische Begriff "Rest" nur für die Ganzzahldivision gilt. dh Division, die erforderlich ist, um einen ganzzahligen Quotienten zu erzeugen.

Um das Konzept des "Restes" auf reelle Zahlen zu erweitern, müssen Sie eine neue Art von "Hybrid" -Operation einführen, die einen ganzzahligen Quotienten für reelle Operanden erzeugen würde . Die Core C-Sprache unterstützt einen solchen Vorgang nicht, wird jedoch als Standardbibliotheksfunktion fmodsowie als remainderFunktion in C99 bereitgestellt . (Beachten Sie, dass diese Funktionen nicht identisch sind und einige Besonderheiten aufweisen. Insbesondere folgen sie nicht den Rundungsregeln der Ganzzahldivision.)


7
Von Wert aus der Definition von% im 98-Standard: "(a / b) * b + a% b ist gleich a." Für Gleitkommatypen ist (a/b)*bbereits gleich a[sofern eine solche Aussage für Gleitkommatypen gemacht werden kann], a%bwäre also niemals besonders nützlich.
Dennis Zickefoose

1
@Dennis: In der Tat ist der Rest in einem Feld algebraisch immer 0. Die am besten geeignete Definition des %Operators für Gleitkomma wäre vermutlich a-(a/b)*b0 oder ein sehr kleiner Wert.
R .. GitHub STOP HELPING ICE

7
@ Tennis: Sie können diese Formel leicht korrigieren, indem Sie "Boden (a / b) * b + a% b = a" verlangen. Beachten Sie, dass für ganze Zahlen Etage (a / b) = a / b ist.
Vog

Bei der Ganzzahldivision im C-Stil wird Trunc verwendet, nicht Floor, aber der Punkt bleibt erhalten.
Dan04

17
-1 Re "der normale mathematische Begriff" Rest "gilt nur für die Ganzzahldivision", der mathematische Begriff der Modulo-Arithmetik funktioniert auch für Gleitkommawerte, und dies ist eines der ersten Themen, die Donald Knuth in seinem Klassiker The Kunst der Computerprogrammierung (Band I). Dh es war einmal Grundwissen. Heute erhalten die Schüler meiner Meinung nach nicht die Ausbildung, für die sie bezahlen, IMHO.
Prost und hth. - Alf

52

Sie suchen nach fmod () .

Um Ihre Frage genauer zu beantworten, wurde der %Operator in älteren Sprachen nur als ganzzahlige modulare Unterteilung definiert, und in neueren Sprachen wurde beschlossen, die Definition des Operators zu erweitern.

EDIT: Wenn ich eine Vermutung wetten würde, warum, würde ich sagen, dass die Idee der modularen Arithmetik aus der Zahlentheorie stammt und sich speziell mit ganzen Zahlen befasst.


9
"ältere Sprachen" - APL stammt aus den 1960er Jahren und sein Modulo-Operator "|" funktioniert sowohl mit Ganzzahlen als auch mit Gleitkommadaten (auch mit Skalar, Vektor, Matrix, Tensor, ...). Es gibt keinen guten Grund, warum der Modulo-Operator "%" von C nicht dieselbe Funktion wie fmod hätte ausführen können, wenn er mit Gleitkommazahlen verwendet worden wäre.
rcgldr

@rcgldr Für die Entwurfsziele war kein Gleitkomma-Modulo erforderlich. C wurde implementiert, um Unix zu kompilieren und die für das Betriebssystem erforderliche Assemblersprache zu begrenzen. "C ist eine zwingende prozedurale Sprache. Sie wurde entwickelt, um mit einem relativ einfachen Compiler kompiliert zu werden, um einen einfachen Zugriff auf den Speicher zu ermöglichen, Sprachkonstrukte bereitzustellen, die Maschinenanweisungen effizient zuordnen, und um eine minimale Laufzeitunterstützung zu erfordern." en.wikipedia.org/wiki/C_(programming_language)
Harper

1
@harper - Da C Gleitkomma-Arithmetik wie Addieren, Subtrahieren, Multiplizieren und Dividieren mit derselben Syntax wie für Ganzzahlen enthält, verstehe ich nicht, warum es nicht auch Modulo mit derselben Syntax (%) enthalten konnte. . Die Wahl, ob es aufgenommen werden soll oder nicht, scheint willkürlich.
rcgldr

16

Ich kann nicht wirklich für sagen sicher , aber ich würde vermuten , es meist historisch ist. Nicht wenige frühe C-Compiler unterstützten Gleitkomma überhaupt nicht. Es wurde später hinzugefügt, und selbst dann nicht als vollständig - meist der Datentyp wurde hinzugefügt, und die meisten primitiven Operationen in der Sprache unterstützt, aber alles andere auf der Standard - Bibliothek verlassen.


1
+1 für die erste vernünftige Antwort, die ich sehe, wenn ich die Liste durchlese. Nachdem ich sie jetzt alle gelesen habe, ist dies die einzig vernünftige Antwort.
Prost und hth. - Alf

Eine verspätete +1 von mir auch. Ich habe in C für eingebettete 6809- und Z80-Systeme geschrieben. Auf keinen Fall könnte ich mir den Platz leisten, um die c-Laufzeitbibliothek einzuschließen. Musste sogar meinen eigenen Startcode schreiben. Fließkomma war ein Luxus, den ich mir nicht leisten konnte :)
Richard Hodges

12

Der Modulo-Operator %in C und C ++ ist für zwei Ganzzahlen definiert. fmod()Für die Verwendung mit Doubles steht jedoch eine Funktion zur Verfügung.


4
Dies ist die Antwort auf die Frage von OP, ignoriert jedoch das grundlegende Problem bei dem, was OP zu tun versucht: sin(fmod(x,3.14))oder ist sogar sin(fmod(x,M_PI))nicht gleich sin(x)für große Werte von x. Tatsächlich können sich die Werte um bis zu 2,0 unterscheiden.
R .. GitHub STOP HELPING ICE

2
@R ..: Richtig, aber das ist eine andere Frage und ich bin nicht ganz sicher, ob es eine akzeptierte Antwort gibt, obwohl es viel Forschung zu diesem Thema gibt
Mark Elliot

@R - Ich habe die Gleichung korrigiert, um es richtig zu machen. Die eigentliche Gleichung war nicht der Punkt (es war ziemlich einfach herauszufinden, sobald ich die Funktion hatte, mit der ich sie testen konnte).
Brendan Long

Ist nicht %der Restoperator und kein Modulooperator?
chux

7

Die Einschränkungen sind in den Standards enthalten:

C11 (ISO / IEC 9899: 201x) §6.5.5 Multiplikative Operatoren

Jeder der Operanden muss einen arithmetischen Typ haben. Die Operanden des Operators% müssen vom Typ Integer sein.

C ++ 11 (ISO / IEC 14882: 2011) §5.6 Multiplikative Operatoren

Die Operanden von * und / oder müssen vom Typ Arithmetik oder Aufzählung sein. Die Operanden von% müssen einen Integral- oder Aufzählungstyp haben. Die üblichen arithmetischen Konvertierungen werden an den Operanden durchgeführt und bestimmen den Typ des Ergebnisses.

Die Lösung ist die Verwendung fmod, weshalb die Operanden von %gemäß C99-Begründung §6.5.5 Multiplikative Operatoren in erster Linie auf den Integer-Typ beschränkt sind :

Das C89-Komitee lehnte es ab, den% -Operator auf Floating-Typen auszudehnen, da eine solche Verwendung die von fmod bereitgestellte Funktion duplizieren würde



2

Der Operator% gibt Ihnen einen REST (ein anderer Name für den Modul) einer Zahl. Für C / C ++ ist dies nur für ganzzahlige Operationen definiert. Python ist etwas breiter und ermöglicht es Ihnen, den Rest einer Gleitkommazahl für den Rest zu erhalten, wie oft die Zahl darin unterteilt werden kann:

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

1
Rest ist nicht 'ein anderer Name für Modul' !! Siehe: stackoverflow.com/questions/13683563/… oder von einem math-pov: math.stackexchange.com/questions/801962/… Einfach ausgedrückt : Modulo und Rest sind nur für positive Zahlen gleich, und ein anderes Beispiel ist, dass der Rest nicht ' Lassen Sie sich nicht um den Kompass herum bewegen (gegen den Uhrzeigersinn). Bitte korrigieren Sie das, da ich zu sparsam bin, um abzustimmen:P
GitaarLAB

2

Der %Operator funktioniert in C ++ nicht, wenn Sie versuchen, den Rest von zwei Zahlen zu finden, die beide vom Typ Floatoder sind Double.

Daher können Sie versuchen, die fmodFunktion von math.h/ zu verwenden, cmath.hoder Sie können diese Codezeilen verwenden, um die Verwendung dieser Header-Datei zu vermeiden:

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

"Der mathematische Begriff der Modulo-Arithmetik funktioniert auch für Gleitkommawerte, und dies ist eines der ersten Themen, die Donald Knuth in seinem Klassiker Die Kunst der Computerprogrammierung (Band I) behandelt. Das heißt, es war einst Grundwissen."

Der Gleitkommamoduloperator ist wie folgt definiert:

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

Wie angegeben, scheint das No-Op des% -Operators für Gleitkommazahlen standardbezogen zu sein. Die CRTL bietet 'fmod' und normalerweise auch 'rest', um% für fp-Zahlen auszuführen. Der Unterschied zwischen diesen beiden liegt darin, wie sie mit der Zwischenrundung "iquot" umgehen.

'rest' verwendet runde bis nächste und 'fmod' verwendet einfaches Abschneiden.

Wenn Sie Ihre eigenen numerischen C ++ - Klassen schreiben, hindert Sie nichts daran, das No-Op-Erbe zu ändern, indem Sie einen überladenen Operator% einfügen.

Freundliche Grüße

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.