modulare Multiplikation


7

Ich habe die Seite Modulare Multiplikation auf Wikipedia gelesen ... und konnte den Algorithmus zur Berechnung von nicht verstehen ab(modm).

uint64_t mul_mod(uint64_t a, uint64_t b, uint64_t m)
{
  long double x;
  uint64_t c;
  int64_t r;
  if (a >= m) a %= m;
  if (b >= m) b %= m;
  x = a;
  c = x * b / m;
  r = (int64_t)(a * b - c * m) % (int64_t)m;
  return r < 0 ? r + m : r;
}

Ich verstehe das:

ab(modm)((amodm)(bmodm))modm

Der Schritt, den ich nicht bekommen habe, ist c=x*b/mund später zu tun, r = (int64_t)(a * b - c * m) % (int64_t)m;wofür ist das?

Der Artikel sagt:

durch Anwendung des Tricks, dass durch Hardware die Gleitkomma-Multiplikation zu den höchstwertigen Bits des Produkts führt, während die ganzzahlige Multiplikation zu den niedrigstwertigen Bits führt

Ich kann nicht sehen, wie das zusammenhängt !!!

Antworten:


7

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 %= mabmodmx=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
xym=qxm+rxqxm=rxxmx=ab<m2x=x+eem
y=xm=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 istrx+emrx<me<mrx+e>mrx+e<0m2mrx+em0±1
xym=qxm+rxqxmrx+emm=rxrx+emm
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*bzmmz=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

zxmm=qzm+rzqzmqNmrz+rN+emm=rzqNmrz+rN+emm=rz+rNrz+rN+emm
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+em22m>NN=264m>2632m=N0e0m

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+dB0c<B0d<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 .mabmmNm

Praktische Aspekte

Basierend auf Abschnitt 5.2.4.2.2 dieses Entwurfs scheint der C11-Standard nur long doubleeine 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 doublewird 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 doublebeim Targeting von x86 angegeben. Der Gültigkeitsbereich des Codes hängt von diesen Implementierungsdetails ab.


Hervorragende Antwort! Ein Fall, über den ich mich immer noch am Kopf kratzt: Angenommen, ist so negativ, dass . Dann wird der Wert von wird , und somit im Bereich . Angenommen, dieser Wert ist . Dann bewirkt die Umwandlung in (ein signierter Typ), dass es sich um eine negative Zahl handelt. Warum macht der Code in diesem Fall das Richtige? Insbesondere wird nach der Umwandlung in (wenn es ), und dann wird die sollte -and-make-positive Betrieb ergeben statterx+e<0a*b - c*mrx+mm..2m1263int64_trx+mrx+m264int64_t263%mrx264modmrx. (Fortsetzung)
DW

Ich habe noch keinen Testfall gefunden, der dieses Verhalten auslöst. Insbesondere kann ich keine Werte für finden a, b, mwo und der Wert ist , dh wo und . Kann dieser Fall passieren? Wenn ja, warum liefert dieser Code in dieser Situation das richtige Ergebnis? rx+e<0a*b - c*m263rx+e<0rx+m263
DW

Nachdem ich 1 Milliarde Zufallswerte ausprobiert habe, kann ich keinen Testfall finden, der diese Bedingungen erfüllt. Vielleicht ist dieser Fall also unmöglich? Aber warum?
DW

Ich stimme zu, dass ich auch nicht sicher bin, was an den Rändern passiert. In diesem Fall haben wir , und so dass der Bereich von ist bis inklusive. Da verursacht dies in den Fällen , , keine Probleme . Wir haben jedoch keine also könnten wir Probleme haben, wenn , z. B. wenn für . Ich weiß nicht, ob wir werden0rz<m0rN<m|e|<mrz+rN+em12m<N1012m<NmN/2m263N=264habe aber Probleme; Ich glaube schon. Wenn andererseits der Rundungsmodus auf Abrunden eingestellt ist, haben wir und ich denke, es funktioniert bis zu . e0m=N1
Derek Elkins verließ SE

Danke für die Bearbeitung und den Kommentar. Ich sehe jedoch immer noch nicht, wie das den Fall anspricht, über den ich spreche. Der Fall , dass ich mir Sorgen mache, wo ist , und wo (so , dass die Besetzung zu Ausbeuten eine negative Nummer). Ihr Kommentar besagt, dass es im Fall keine Probleme gibt , aber ich verstehe nicht warum; Genau darum mache ich mir Sorgen. Ich habe Rundungsmodi in Betracht gezogen, aber soweit ich das beurteilen kann, ist der Standardrundungsmodus "Rund-auf-Gerade" und nicht "Rundung", sodass das Problem nicht gelöst zu werden scheint. rz+rN+em1rz+rN+m263int64_t1
DW
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.