Review von euch erwünscht, double Rechnung falsch

Moin,

Java:
public interface Transformator {
    Square transformCircleToSquareByArea(Circle circle);
}

public class TransformatorImpl implements Transformator {
    @Override
    public Square transformCircleToSquareByArea(Circle circle) {
        return Square.byArea(circle.area);
    }
}

public class Circle {
    public final double diameter;
    public final double radius;
    public final double area;

    public Circle(double diameter) {
        this.diameter = diameter;
        this.radius = diameter / 2.0;
        this.area = radius * radius * Math.PI;
    }

    @Override
    public String toString() {
        return "Circle{" +
                "diameter=" + diameter +
                ", radius=" + radius +
                ", area=" + area +
                '}';
    }
}

public class Square {
    public final double sideLength;
    public final double area;
    public final double diagonal;

    public Square(double sideLength) {
        this.sideLength = sideLength;
        this.area = sideLength * sideLength;
        this.diagonal = Math.sqrt(2.0 * area);
    }

    public static Square byArea(double area) {
        double side = Math.pow(area, 0.5);
        return new Square(side);
    }

    public static Square byDiagonal(double diagonal) {
        double area = diagonal * diagonal / 2.0;
        double side = Math.pow(area, 0.5);
        return new Square(side);
    }

    @Override
    public String toString() {
        return "Square{" +
                "sideLength=" + sideLength +
                ", area=" + area +
                ", diagonal=" + diagonal +
                '}';
    }
}

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        System.out.println("Enter circle diameter:");
        double diameter = new Scanner(System.in).nextDouble();

        Circle circle = new Circle(diameter);
        Square square = new TransformatorImpl().transformCircleToSquareByArea(circle);

        System.out.println("You entered:");
        System.out.println("circle = " + circle);
        System.out.println("That has the same area as:");
        System.out.println("square = " + square);
    }
}

Verstehe nicht, was hier ab geht ... Wenn ich Durchmesser 5 eingebe, dann kommt raus:

Code:
Enter circle diameter:
5
You entered:
circle = Circle{diameter=5.0, radius=2.5, area=19.634954084936208}
That has the same area as:
square = Square{sideLength=4.43113462726379, area=19.63495408493621, diagonal=6.266570686577502}

Also 19.634954084936208 gegen 19.63495408493621 den Unterschied sieht hoffentlich jeder.
 

KonradN

Super-Moderator
Mitarbeiter
Das sind die normalen Ungenauigkeiten bei den Berechnungen mit double.
Da man nur eine bestimmte Genauigkeit hat, muss es zu diesen Ungenauigkeiten kommen.

Generell gilt hier, dass man mit einer höheren Genauigkeit rechnen muss als man haben möchte.
 

KonradN

Super-Moderator
Mitarbeiter
Die summieren sich doch auf Mensch
Was soll sich aufsummieren? Ungenauigkeiten werden sich mit jeder Berechnung potentiell vergrößern.

Machen wir einfach ein kleines Beispiel um es zu zeigen:
Java:
        double value = 5.0;
        double sqrt = Math.sqrt(value);
        System.out.println("value: " + value);
        System.out.println("squareroot: " + sqrt);
        System.out.println("square: " + (sqrt * sqrt));

value: 5.0
squareroot: 2.23606797749979
square: 5.000000000000001

Ist doch auch klar - die Wurzel auf 5 ist nicht in einer double darstellbar. Man hat also eine Näherungswert entsprechend der maximalen Genauigkeit. Der Wert ist also zu klein oder zu groß.
Dann wird dieser (hier zu große) Wert mit sich selbst multipliziert und die Ungenauigkeit nimmt zu, Und schon hat man eine noch größere Abweichung.

Wenn man nur eine Genauigkeit von z.B. 5 Stellen hätte, dann könnte man darauf gehen. Aber aufpassen - es kann ggf. kein 2.23606 speichern und man hat dann tatsächlich ein 2.236059999.... oder so

Aber man hat eine deutlich größere Genauigkeit im Double als man braucht und wenn man ständig auf die 5 Stellen "rundet", dann hat man in der Regel eine Genauigkeit, die ausreichend ist. Aber aufpassen: Wenn Du da mit 1_000_000 multiplizieren musst, dann hat man da die gewünschte Genauigkeit nicht mehr! Also man kann nicht pauschal nur die Nachkommastellen berücksichtigen.
 

Blender3D

Top Contributor
Also 19.634954084936208 gegen 19.63495408493621 den Unterschied sieht hoffentlich jeder.
Der Post von @KonradN trifft es auf den Punkt.
Vielleicht hier noch ein Beispiel zur Umwandlung eines Quadrates in einen flächengleichen Kreis. Kein Computer schafft das bis auf die letzte Kommastelle.
Warum ?
Weil der Kreis mit der transzendenten Zahl Pi verbunden ist.
1696770957528.png

Die notwendige Konstante r im obigen Beispiel zeigt, dass es unmöglich ist, einen Kreis mit der Fläche 25 exakt mit einer reellen Zahl hinzuschreiben. Warum weil sie Pi also eine transzendente Zahl beinhaltet.
https://de.wikipedia.org/wiki/Quadratur_des_Kreises
 
Aber ich verstehe noch nicht, weshalb scheinbar beide Methoden immer das exakt gleiche Ergebnis liefern:

Java:
        for (int i = -20; i <= 20; i++) {
            double a = Math.sqrt(i);
            double b = Math.pow(i, 0.5);
            System.out.println((Double.isNaN(a) && Double.isNaN(b) || a == b) + " " + a + " " + b);
        }

Wie ist sqrt und pow low level implementiert worden?
 

KonradN

Super-Moderator
Mitarbeiter
Wieso erwartest Du denn da unterschiedliche Ergebnisse? Math.sqrt(i) und Math.pow(i, 0.5) sind nun einmal mathematisch die gleiche Operation und da ist die Erwartungshaltung doch, dass das Ergebnis die double Zahl ist, die eben dem mathematischen Ergebnis am nächsten kommt.

@Konrad "Apple s Jünger" scheint „Ihn :mad:“ wieder entdeckt zu haben.
;)
Ja, aber das ändert doch nichts an der Frage. Die Fragestellung, ob ein neuer User im Forum nun unser spezieller Freund ist oder nicht, ist für mich erst einmal uninteressant. Wenn ich zu einer Fragestellung etwas sachliches beitragen kann, dann mache ich es. So lange es nicht zu direkten Angriffen kommt, ist es mir egal.
 

httpdigest

Top Contributor
Wie ist sqrt und pow low level implementiert worden?
Schaue einfach in die OpenJDK Sourcen.
Für Math.pow(double, double) gibt es einen Spezialfall für (x, 0.5), der pow(x, 0.5) als sqrt(x) implementiert:
Und in HotSpot sind eigentlich die meisten Operationen auch @IntrinsicCandidates, also es gibt da dann eine native Implementierung und die Default-Implementierung, die als Fallback im OpenJDK als Java-Code implementiert ist, wird hier nie verwendet.
In HotSpot sind die ganze Intrinsics hier registriert: https://github.com/openjdk/jdk/blob/master/src/hotspot/share/classfile/vmIntrinsics.hpp#L167
 

Marinek

Bekanntes Mitglied
Ja, aber das ändert doch nichts an der Frage. Die Fragestellung, ob ein neuer User im Forum nun unser spezieller Freund ist oder nicht, ist für mich erst einmal uninteressant. Wenn ich zu einer Fragestellung etwas sachliches beitragen kann, dann mache ich es. So lange es nicht zu direkten Angriffen kommt, ist es mir egal.
Wenn der User eins bewiesen hat, dann dass seine "zweite Chancen" komplett aufgebraucht sind in allen Foren, in denen er sich anmeldet.

Auf seine Beiträge zu reagieren, wohlwissend, was dann kommt, ist einfach nur schlecht.
 

KonradN

Super-Moderator
Mitarbeiter
So lange es sachlich bleibt, bevorzuge ich einen Thread im Forum, in dem eine Frage beantwortet wird als ein Thread, in dem eine Frage unbeantwortet bleibt.

Aber das kann jeder so sehen, wie er will und ich denke nicht, dass es sich lohnt. dies im Detail weiter zu diskutieren.
 
Wenn der User eins bewiesen hat, dann dass seine "zweite Chancen" komplett aufgebraucht sind in allen Foren, in denen er sich anmeldet.
Em, nur um das besser zu verstehen, du kannst globale Bans aussprechen, weil du der Kaiser von China bist, stimmt's? ;)

Für Math.pow(double, double) gibt es einen Spezialfall für (x, 0.5), der pow(x, 0.5) als sqrt(x) implementiert:
Danke. Das hatte ich durch die Ausgabe vermutet, konnte es aber nicht nachweisen.

Also wird intern für pow(x, 0.5) sqrt(x) aufgerufen ... Weshalb nicht umgekehrt?
 
Danke, ich glaube, ich habe eine Erklärung gefunden:

https://en.wikipedia.org/wiki/Square_root#Computation :

The time complexity for computing a square root with n digits of precision is equivalent to that of multiplying two n-digit numbers.

Das scheint kleiner zu sein als die Zeitkomplexität der fractional exponent power function:


Allerdings bin ich da etwas unsicher ... Also O(2(n log n)) versus O(sqrt n) , ersteres wächst wohl schneller: https://www.wolframalpha.com/input?i=n+log_2+n+=+sqrt+n

?
 

httpdigest

Top Contributor
Vermutlich weil diverse CPUs eine Wurzel für Fließkommazahlen berechnen können. So hast z.B. der x86 Assembler FSQRT:
x86 instruction listings - Wikipedia
Genau. Wobei heute eigentlich niemand mehr die x87 Co-/Hilfsprozessor Funktionen wie FSQRT verwendet.
Zumindest nicht, seit es die SSE2 Extension gibt. Hier wird dann (wie auch in meinem Link oben zu sehen ist) sqrtsd verwendet, was auch von Hotspot generiert wird.
 
Merci. Dann wäre jetzt doch alle Fragen geklärt ...

Ich glaube, das wissen nur ein paar Intel und AMD Engineers. Ich wüsste auch nicht, wofür das zu wissen jetzt noch nützlich ist.
Es ist wichtig, ein Verständnis dafür zu erlangen, welche Bits da genau hin und her flitzen. :p Ich strebe an, ein System ganzheitlich zu betrachten. Ähnlich wie ein Arzt den kompletten Menschen sieht und nicht nur den symptomatischen ... äh, keine Ahnung ... Fußpilz or whatever. Ich denke, du auch?

Wir sind doch high level verwöhnt ... Wir steigen in ein Flugzeug ein und reisen damit von A nach B. Wir (also der/die Pilot) sollten aber auch wissen, wie es zu steuern ist, wenn der Computer oder ein Triebwerk mal ausfällt ... Oder nicht?
 

KonradN

Super-Moderator
Mitarbeiter
Also, wenn die CPU ausfällt, ist die Frage, wie die Wurzel im Prozessor implementiert ist, so ziemlich das Letzte, das mich interessiert 🤣
Wobei das doch etwas ist, das in Mission Impossible und anderen Hollywood Filmen immer gut kommt:
Das Flugzeug hat einen Schaden und dann klettert der Pilot mit Werkzeugkiste (Held des Filmes) aus dem kaputten Fenster, klettert auf den Flügel und repariert das Triebwerk

Und das ist auch als Java Entwickler relevant. Oder hast Du noch keine CPUs aufgeschliffen um an die Cores zu kommen um diese dann mit einer in den Lötkolben eingespannten Injektionsnadel die Cores zu verändern / zu optimieren?

Ok, es ist etwas tricky, wenn man bei einer mehrlagigen Platine die Leitungen anzupassen. Aber wozu hat man im Grundstudium 1 Semester Elektrotechnik, 2 Semester technische Informatik und natürlich die ganzen Grundlagen zur Signalübertragung und so ...

Was etwas fehlt sind das Diplom E-Technik bezüglich Hochfrequenz. Aber das ist nichts, was man nicht mit paar YT Videos sich selbst beibringen könnte...
 
Musste schmunzeln. 😊 Danke für den Beitrag.

Es ging mich doch nur um einen Laufzeitvergleich beider Funktionen und ob man daraus ableiten könnte, weshalb man sich für sqrt(x) entschieden hatte. ;) Meist geschieht doch nix ohne Grund...
 

Oneixee5

Top Contributor
Merci. Dann wäre jetzt doch alle Fragen geklärt ...


Es ist wichtig, ein Verständnis dafür zu erlangen, welche Bits da genau hin und her flitzen. :p Ich strebe an, ein System ganzheitlich zu betrachten. Ähnlich wie ein Arzt den kompletten Menschen sieht und nicht nur den symptomatischen ... äh, keine Ahnung ... Fußpilz or whatever. Ich denke, du auch?

Wir sind doch high level verwöhnt ... Wir steigen in ein Flugzeug ein und reisen damit von A nach B. Wir (also der/die Pilot) sollten aber auch wissen, wie es zu steuern ist, wenn der Computer oder ein Triebwerk mal ausfällt ... Oder nicht?
Ich stell mir immer vor, ich müsste mit jemandem zusammenarbeiten, der ständig so etwas von sich gibt:
itcrowd-richardayoade.gif
 
is mir noch nicht passiert, sieht nach Impulskontrollstörung aus... Aber so etwas ist für normale Menschen bestimmt lustig oder so.

****

Edit: Kann jemand hierüber lachen? is mit der v3.5 erstellt.

1696865316145.png

Ich fand alle drei nicht lustig.
 
Zuletzt bearbeitet:

AndiE

Top Contributor
Die Abweichung im Eingangspost beträgt 2*10^-15/20=20*10^-18. Das entspricht einer Abweichung von 2 mm bei 10^15 mm. das sind 10^9 km, also eine Milliarde km. Bei 0,3*10^6 km/s, die das Licht benötigt, sind das rund 3300 s, die das Licht für diese Strecke braucht. Da die Sonne nur etwa 480 Lichtsekunden entfernt ist, rden wir hier größenordnungsmßig von einer Abweichung von 2 cm bei etwa einer 7fachen Länge der Entfernung Erde-Sonne.

Das ist doch mehr als Haarspalterei.
 
Das ist doch mehr als Haarspalterei.

@AndiE Rechne mal 1000-mal hin und her. Dann hast du eine Fehlerkumulierung ... Das multipliziert sich auf.

Oder anders, drehe ein Objekt mal 1000-mal alternierend um +- 180° ... danach ist es nicht mehr von vorn/hinten zu sehen, sondern seitlich. :(

@Apples Jünger Würde ich mich so verhalten wie du, wäre ich schon dreimal gesperrt worden. Denk da mal drüber nach.
 

KonradN

Super-Moderator
Mitarbeiter
Oder anders, drehe ein Objekt mal 1000-mal alternierend um +- 180° ... danach ist es nicht mehr von vorn/hinten zu sehen, sondern seitlich. :(
Du musst halt darauf achten, dass Du einen Datentyp hast, der entweder:
  • Exakt ist. Ohne Abweichungen hast Du kein Problem
  • Du eine so große Genauigkeit hast, dass Du dann durch runden immer sicher gestellt hast, dass die Abweichung nie größer ist, als ein gewisses, noch akzeptables Maß.
 

AndiE

Top Contributor
@itsbeeroclock: Das ist eine Frage der Fehlerrechnung. Man kann doch leicht zeigen, dass

(a+e)*(b+e)=a*b+e(a+b)*e^2

ist.

Liegen die Werte also z.B. mit einer Genauigleit von e=10^-4 vor, z.B. a und b können zwischen 1000 und 1000.1 liegen, dann liegt a*b zwischen 1000000 und 10000200.1. Ich kann also davon ausgehen, dass die Folge 10000... ( 4 Stellen) konstant ist.

Bei der Darstellung, wird die Mantisse bei Double in 52 bit gehalten, was mit dem zusätzlichen bit etwa einer Genauigkeit von 8*10^15 entspricht.

Dabei muss man aber auch wissen, dass die Abbildung von Dezimalzahlen als Dualzahlen deshalb schwierig ist, weil 1/10 als Dualzahl ein unendlicher Bruch ist. Somit hat man da die bekannten Ungenauigkeiten.

Es ist deshalb immer ratsam, sich bei praktischen Aufgaben das mal auf einem Papier gegenzurechnen.

Wenn ich also z.B. eine Balkenlänge in mm angebe und einen Winkel, dann nutzt mir die Höhe( über Sinus berechnet) mir auch nur was, wenn sie in mm angegeben wird.

Sich damit zu beschäftigen ist schon wichtig, aber man muss es auch mals ausprobieren, welche Effekte zu erwarten sind.
 
@AndiE hat genau recht, der Error/Fehler pendelt sich in diesem Fall irgendwann ein ... :

Java:
public class Square {
    public final double sideLength;
    public final double area;
    public final double diagonal;

    public Square(double sideLength) {
        this.sideLength = sideLength;
        this.area = sideLength * sideLength;
        this.diagonal = Math.sqrt(2.0 * area);
    }

    public static Square byArea(double area) {
        double side = Math.sqrt(area);
        return new Square(side);
    }

    @Override
    public String toString() {
        return "Square{" +
                "sideLength=" + sideLength +
                ", area=" + area +
                ", diagonal=" + diagonal +
                '}';
    }
}

public class Circle {
    public final double diameter;
    public final double radius;
    public final double area;

    public Circle(double diameter) {
        this.diameter = diameter;
        this.radius = diameter / 2.0;
        this.area = radius * radius * Math.PI;
    }

    public static Circle byArea(double area) {
        return new Circle(Math.sqrt(area / Math.PI) * 2.0);
    }

    @Override
    public String toString() {
        return "Circle{" +
                "diameter=" + diameter +
                ", radius=" + radius +
                ", area=" + area +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        Circle circle = new Circle(5);
        Square square;
        for (int i = 0; i < 10; i++) {
            square = Square.byArea(circle.area);
            circle = Circle.byArea(square.area);
            System.out.println("i = " + i);
            System.out.println("Org circle = " + new Circle(5));
            System.out.println("New circle = " + circle);
            System.out.println("Error = " + (new Circle(5).area - circle.area));
        }
    }
}

Code:
i = 0
Org circle = Circle{diameter=5.0, radius=2.5, area=19.634954084936208}
New circle = Circle{diameter=5.000000000000001, radius=2.5000000000000004, area=19.634954084936215}
Error = -7.105427357601002E-15
i = 1
Org circle = Circle{diameter=5.0, radius=2.5, area=19.634954084936208}
New circle = Circle{diameter=5.000000000000002, radius=2.500000000000001, area=19.634954084936222}
Error = -1.4210854715202004E-14
i = 2
Org circle = Circle{diameter=5.0, radius=2.5, area=19.634954084936208}
New circle = Circle{diameter=5.000000000000002, radius=2.500000000000001, area=19.634954084936222}
Error = -1.4210854715202004E-14
i = 3
Org circle = Circle{diameter=5.0, radius=2.5, area=19.634954084936208}
New circle = Circle{diameter=5.000000000000002, radius=2.500000000000001, area=19.634954084936222}
Error = -1.4210854715202004E-14
i = 4
Org circle = Circle{diameter=5.0, radius=2.5, area=19.634954084936208}
New circle = Circle{diameter=5.000000000000002, radius=2.500000000000001, area=19.634954084936222}
Error = -1.4210854715202004E-14
...

Es gibt aber auch Berechnungen und Fälle, wo das nicht immer so der Fall ist. Zum Beispiel bei Drehungen und Benutzereingaben über die Maus usw. ... Der Benutzer/Mensch ist quasi immer mit einer der besten Zufallszahlengeneratoren. ;) (hierphilosophischendialogeinfügen...)
 

LimDul

Top Contributor
im Endeffekt ist es simple: Sobald ich nicht mehr mit Ganzen Zahlen rechne, muss ich mit Genauigkeitsproblemen rechnen, weil manche Dinge nicht genau darstellbar sind.

Das fängt mit irrationalen Zahlen an - PI und e als beste Beispiele und geht über in die rationale Zahlen, die in in der Floating-Point Schreibweise abhängig vom Zahlensystem (z.B. Dezimal vs. Binär) nicht genau darstellbar sind.

Man muss sich Gedanken machen, was will ich für Endergebnis haben bzgl. Genauigkeit und was bedeutet das für meine Berechnungen. Abhängig davon gibt es verschiedene Varianten:

  • float/double zu verwenden und das Endergebnis auf gewünschte Stellenzahl runden, so dass der Fehler immer kleiner ist als die relevante Rundungsstelle
  • BigDecimal verwenden und sich in jedem Rechenschritt Gedanken um die benötigte Genauigkeit machen
  • Andere Darstellungsformen (wie z.B. BCD) verwenden, die manche Probleme verhindern (dafür aber ggf. andere mit sich bringen)

Das sind halt Dinge, wo man sich einfach mal Gedanken machen muss.
 

Ähnliche Java Themen

Neue Themen


Oben