Hier ist eine kompaktere und mathematischere Beschreibung der Vorgänge. Sei und die Eingabe, bereits reduziert modulo , also und . (Code-weise bedeutet dies nach der Zeile.) Wir wollen berechnen , dh , wir wollen so finden, dass für einige .abma<mb<mb %= m
abmodmx=abrxx=qxm+rxqx
Der nicht überlaufende Fall
Im nicht überlaufenden Fall könnten wir einfach den Modul berechnen und damit fertig sein. Stattdessen berechnet der Code:
Jetzt ist . Ich habe mir hier allerdings Sorgen gemacht. Es könnte sein, dass nicht in die Mantisse unserer Gleitkommavariablen passt. Solange passt, ist dies jedoch kein Problem, da . Wir werden mit enden, wobei Rundungsfehler ist, dessen Größe kleiner als . Wir gehen weiter wie bisher:
y=⌊xm⌋=⌊qx+rxm⌋=qx+⌊rxm⌋=qx
x−ym=qxm+rx−qxm=rxxmx=ab<m2x′=x+eemy=⌊x′m⌋=⌊qx+rx+em⌋=qx+⌊rx+em⌋
In diesem Fall können wir den nicht eliminieren, weil, während und , es kann der Fall sein, dass oder , obwohl es sicherlich (streng) zwischen und , also ist entweder oder . Jetzt ist
⌊rx+em⌋rx<me<mrx+e>mrx+e<0−m2m⌊rx+em⌋0±1x−ym=qxm+rx−qxm−⌊rx+em⌋m=rx−⌊rx+em⌋m
Wenn Sie jetzt die Moduloperation ausführen, wird dieser zusätzliche Term jedoch aufgrund der von C gewählten
Konvention entfernt(-1)%m = -1
. Um eine Konvention zu erhalten, bei der wir immer eine positive Zahl zurückgeben, können wir dem Ergebnis hinzufügen , wenn es negativ ist.
m
Der überlaufende Fall
Nehmen wir an, wir machen den ganzzahligen arithmetischen Mod , z. B. , und nehmen wir was . Jetzt wird die Multiplikation abgeschlossen. Wir schreiben für eine ganze Zahl . Das heißt, die Berechnung ergibt die Zahl . Bei der Gleitkommaberechnung wird wie zuvor davon ausgegangen, dass das in die Mantisse passt (was für ein größeres Floats mit erweiterter Genauigkeit von 80 Bit erfordern kann ). Definieren Sie für den Modul und so dassN264ab>Nm2>N>mx=ab=z+kNk<ma*b
zmmz=qzm+rzkN=qNm+rNx=qzm+qNm+rz+rN. Wie zuvor kann das Speichern von als Gleitkommavariable zu einem Rundungsfehler also wieder . Wenn wir die gleiche Berechnung wie zuvor durchführen, sieht es so aus:
Ich habe hier ein Kaninchen aus dem Hut gezogen. Hier wir mod und mod also . Nach wie vor modifizieren wir um zu bekommenx|e|<mx′=x+e
z−⌊x′m⌋m=qzm+rz−qzm−qNm−⌊rz+rN+em⌋m=rz−qNm−⌊rz+rN+em⌋m=rz+rN−⌊rz+rN+em⌋m
NN qNm+rN=kN=0qNm=−rNmrz+rN=xmodm , und wie zuvor kann die C-Konvention dazu führen, dass dies negativ ist, was korrigiert werden muss.
Es gibt noch ein mögliches Problem. könnte . Dies verursacht kein Problem, es sei denn, in diesem Fall würden Sie die falsche Antwort erhalten. Für bedeutet dies . (Abgesehen davon, wenn , verursacht es kein Problem, da wir am Ende .) Wenn wir die Gleitkomma-Hardware so konfigurieren, dass sie , so dass , wird dieser Fall nicht auftreten. (Vergessen Sie jedoch nicht meine Einschränkung, dass die Mantisse halten kann !).⌊rz+rN+em⌋22m>NN=264m>2632m=N0e≤0m
Anschließen an die meisten / niedrigstwertigen Bits
Um den von Ihnen zitierten Teil spezifisch anzusprechen, betrachten Sie eine Ganzzahl größer als 2, die wir als Basis betrachten, wie in "Basis ". Im üblichen Fall wird die Zahl als . In einer Gleitkomma-Darstellung würden wir dies als schreiben . Nehmen wir nun an, wir wollten zwei (positive) einzelne Basis- Ziffern multiplizieren. Das Ergebnis würde höchstens zwei Ziffern erfordern, z. B. wobei (wie in einer [Standard] Basis- Darstellung erforderlich ) und . Wenn wir uns darauf beschränken, nur eine Basis zu speichern,B10B=102121=2B+12.1×B1BcB+dB0≤c<B0≤d<BB Ziffer des Ergebnisses gibt es zwei offensichtliche Möglichkeiten, entweder oder .cd
Die Wahl von entspricht der Arbeit mit Mod als , was bei der Ganzzahlarithmetik der Fall ist. (Übrigens führt die Ganzzahlmultiplikation auf Baugruppenebene häufig zu diesen beiden Ziffern.)dBcB+d=dmodB
Gleitkomma-Arithmetik entspricht dagegen effektiv der Wahl von , kompensiert dies jedoch durch Inkrementieren des Exponenten. Tatsächlich stellen wir das Ergebnis als aber da wir nur eine Basis- Ziffer speichern können , wird dies nur . (In der Praxis betrachten wir Zahlen als mehrstellige Zahlen in einer kleinen Basis (dh 2) und nicht als 1- oder 2-stellige Zahlen in einer großen Basis. Dadurch können wir einige der höheren Ziffern von if speichern Sie werden nicht benötigt, um zu speichern , aber im schlimmsten Fall geht alles verloren. Keines voncc.d×B1Bc×B1dcdcgeht verloren, bis wir im Exponenten keinen Platz mehr haben. Für den obigen Code ist dies kein Problem.)
Solange im Gleitkommaformat originalgetreu dargestellt werden kann, kann der Ausdruck als Extrahieren dieser oberen Ziffer in Basis . Sie können den obigen Code und die Mathematik als das Zusammenspiel zwischen Basis- und Basis- Darstellungen einer Zahl anzeigen .m⌊abm⌋mNm
Praktische Aspekte
Basierend auf Abschnitt 5.2.4.2.2 dieses Entwurfs scheint der C11-Standard nur long double
eine Mantisse mit einer Länge von ungefähr 33 Bit zu erfordern . (Insbesondere scheint nur die Mindestanzahl von Dezimalstellen angegeben zu werden, die originalgetreu dargestellt werden können.) In der Praxis verwenden die meisten C-Compiler bei der Ausrichtung auf Allzweck-CPUs und insbesondere CPUs der x86-Familie IEEE754-Typen. In diesem Fall double
wird effektiv eine 53-Bit-Mantisse haben. CPUs der x86-Familie unterstützen ein 80-Bit-Format mit einer Mantisse mit effektiv 64-Bit, und mehrere, aber nicht alle Compiler haben dies long double
beim Targeting von x86 angegeben. Der Gültigkeitsbereich des Codes hängt von diesen Implementierungsdetails ab.
a*b - c*m
int64_t
int64_t
%m
a
,b
,m
wo und der Wert ist , dh wo und . Kann dieser Fall passieren? Wenn ja, warum liefert dieser Code in dieser Situation das richtige Ergebnis?a*b - c*m
int64_t