Sqrt algorithmus



  • Hi,
    Ich sehe mir gerade ein paar algorithmen an um sqrt zu berechnen.
    Sollte foldendes:
    xn_1 = xn - (xn_1 * xn_1 - a) / (2 * xn_1);
    Nicht ersetzt werden in:
    xn_1 = xn - (xn * xn - a) / (2 * xn);
    ?

    Warum wird xn_1 auf a gesetzt ?

    float my_sqrt_newton(float a) {
    	float eps = 0.00001;
    	float xn_1 = a;
    	float xn = 0;
    
    	if (a < 0) {
    		return -1;
    	}
    
    	while (eps < abs(xn_1 - xn)) {
    		xn = xn_1;
    		xn_1 = xn - (xn_1 * xn_1 - a) / (2 * xn_1);
    	}
    
    	return xn;
    }
    


  • xn_1 wird auf a gesetzt, weil du irgendwie ja deine Zahl in den Algorithmus bekommen willst.
    Ob du in Zeile 12 xn_1 oder xn verwendest ist egal da vorher xn = xn_1 gesetzt wird. Stilistisch schöner ist natürlich sich auf einen der beiden Werte festzulegen. Im Prinzip kannst du auch direkt mit a weiterrechnen und dir xn_1 komplett sparen.



  • Irgendwie ja aber mit welcher begruendung? 😉
    Warum kann ich mir das xn_1 sparen? Ich muss doch priefen ob xn - xn_1 kleiner als epsion ist!



  • Mikeyy schrieb:

    Hi,
    Ich sehe mir gerade ein paar algorithmen an um sqrt zu berechnen.
    Sollte foldendes:
    xn_1 = xn - (xn_1 * xn_1 - a) / (2 * xn_1);
    Nicht ersetzt werden in:
    xn_1 = xn - (xn * xn - a) / (2 * xn);
    ?

    Ist hier egal, weil DU eine Zeile vorher xn_1 und xn gleich machst.

    Mikeyy schrieb:

    Warum wird xn_1 auf a gesetzt ?

    Um nicht durch 0 zu teilen im ersten Schritt.
    Stell Dir das Newton-Verfahren gerne grafisch vor (wie in Wikipedia). Du hättest eine waagerechte Tangente und könntest gar nich so recht sagen, wo sie die x-Achse schneidet.

    Mikeyy schrieb:

    Warum kann ich mir das xn_1 sparen? Ich muss doch priefen ob xn - xn_1 kleiner als epsion ist!

    Sehe ich Gans genauso.

    Aber Du kannst Dir eps sparen!
    Mach die Schleife fußgesteuert (alter Ausdruck für do statt while).
    Dann biste bei der Abfrage mit xn+1 immer links von Xn. Hoffe ich. War doch so? Müßte es mir gerade mal grafisch vorstellen, aber nee, ist ja dein Job. 🤡
    Also wenn es so ist, (bin jetzt nicht ganz sicher, daß der Start so schon ausreichend ist), dann bleibt auch immer xn+1 immer links von Xn. Ist ja logisch, wenn man sich die so nach oben krumem Parabel vorstellt und was passoert, wenn man schon rechts ist und eine Tangente anlegt und schaut, wo die Tangente die x-Ache schneidet.
    Sodele, Du brummst einfach gegen die Rechengenauigkeit der Maschine!!! Irgenwann muss sie ja aufgeben und mal xn+1 rechts oder gleich Xn haben, weil sie nicht unendlich viele Stellen rechnet. Dann haste sie gepackt, also sozusagen ein automatisches dynamisches eps.

    Und kostet auch nicht viel mehr, Newton verdoppelt die genauen Stellen bei jedem Schleifenduchlauf, haste 5 sinds im nächsten Schritt 10, dann 20 und dann dürfte er wohl schon fertig sein auf normalen Maschinen. Sagen wir mal 2 Läufe mehr und dafür abs() eingespart. Also ich finde das lecker.



  • volkard schrieb:

    ... Du brummst einfach gegen die Rechengenauigkeit der Maschine!!! Irgenwann muss sie ja aufgeben und mal xn+1 rechts oder gleich Xn haben, weil sie nicht unendlich viele Stellen rechnet. Dann haste sie gepackt, also sozusagen ein automatisches dynamisches eps.

    .. könnte man meinen, man muss sich nur davor hüten am Ende die Zahlen auf Gleichheit zu prüfen. Probiert das z.B. einfach mal mit a=4000 aus. Dann bewegt sich der Algo recht zügig auf 63.245.. hin um am Ende ständig zwischen den Werten 63.2455559 und 63.2455521 hin und her zu springen. Nur gleich werden xn und xn_1 in diesem Fall nie! Einen 4 Byte breiten float mal vorausgesetzt.

    In dem Algo, den uns Mikeyy hier vorstellt, ist das Epsilon eine Konstante, die immer mit der absoluten Differenz der letzten beiden Ergebnisse verglichen wird. Damit ist das Epsilon für kleine Werte zu groß und für große Werte zu klein. Man probiere einfach mal 0.000009 oder 4e+9. Im ersten Fall ist das Ergebnis schlicht falsch und im zweiten Fall kehrt die Funktion nie zurück.

    Wenn schon mit Epsilon, dann ist es besser, die verblieben Differenz zu normieren:

    while (eps < abs(xn_1 - xn)/xn_1) { // Zeile 10
    

    bzw. gleich

    while (xn_1 * eps < abs(xn_1 - xn)) {
    

    Und noch besser, man baut die Schleife - genau wie von volkard vorgeschlagen - fußgesteuert um.

    Zum Schluss sollte man darauf achten, dass man bei Zwischenergebnissen keinen over- oder underflow erzeugt. Also statt

    xn_1 = xn - (xn_1 * xn_1 - a) / (2 * xn_1); // Zeile 12
    

    lieber

    xn_1 = (xn + a/xn) * 0.5;
    

    Gruß
    Werner



  • Werner Salomon schrieb:

    Probiert das z.B. einfach mal mit a=4000 aus. Dann bewegt sich der Algo recht zügig auf 63.245.. hin um am Ende ständig zwischen den Werten 63.2455559 und 63.2455521 hin und her zu springen. Nur gleich werden xn und xn_1 in diesem Fall nie! Einen 4 Byte breiten float mal vorausgesetzt.

    Hab lustigerweise fast immer Zyklen der Längen 1, 2, 4 oder 8. Auch ohne daß die interne Darstellung eine Rolex spielt, bei beschränkten Folgen auf natürlichen Zahlen (kein Überlauf).

    Werner Salomon schrieb:

    Zum Schluss sollte man darauf achten, dass man bei Zwischenergebnissen keinen over- oder underflow erzeugt. Also statt

    xn_1 = xn - (xn_1 * xn_1 - a) / (2 * xn_1); // Zeile 12
    

    lieber

    xn_1 = (xn + a/xn) * 0.5;
    

    Sehr feine rechnerische Umformung, die tut dem Rechner bestimmt gut. So ein Durch-Null-Teilen oder Beinahe-Durch-Null-Teilen ist halt dicht beieinander.

    Ist zugleich geometrisch auch als ein anderer Ansatz interpretierbar: Man vermutet von der Zahl a eine Wurzel xn. Dann müßte a/xn ja auch eine Wurzel sein. Das arithmetische Mittel der beiden ist eine noch bessere Vermutung.

    edit: Ich meinte: Man hat eine Rechtecksfläche a und eine Kantenlänge xn, die ist leider > (bzw <) als sqrt(a). Dann ist die andere Kante ja < (bzw >) als sqrt(a) und a/xn lang. Sicherlich ist die richtige Kantenlänge dazwischen. Wir nehmen als neue Schätzung das arithmetische Mittel der beiden Rechteckskanten.



  • volkard schrieb:

    Werner Salomon schrieb:

    Zum Schluss sollte man darauf achten, dass man bei Zwischenergebnissen keinen over- oder underflow erzeugt. Also statt

    xn_1 = xn - (xn_1 * xn_1 - a) / (2 * xn_1); // Zeile 12
    

    lieber

    xn_1 = (xn + a/xn) * 0.5;
    

    Sehr feine rechnerische Umformung, die tut dem Rechner bestimmt gut. So ein Durch-Null-Teilen oder Beinahe-Durch-Null-Teilen ist halt dicht beieinander.

    was das Teilen betrifft ist meine Änderung zum Ausgangsterm gleich 0. Und damit das nicht passiert wird das ja auch vorher abgefangen (s.o.)
    .. und was das 'fast =0' betrifft, so kommt es ja nur auf die Wahl des 'geeigneten' Startwertes an.

    volkard schrieb:

    edit: Ich meinte: Man hat eine Rechtecksfläche a und eine Kantenlänge xn, die ist leider > (bzw <) als sqrt(a). Dann ist die andere Kante ja < (bzw >) als sqrt(a) und a/xn lang. Sicherlich ist die richtige Kantenlänge dazwischen. Wir nehmen als neue Schätzung das arithmetische Mittel der beiden Rechteckskanten.

    so isses: http://de.wikipedia.org/wiki/Heron-Verfahren


Anmelden zum Antworten