Double ungenau? Rechenfehler?

Hi,
ich mache gerade einen kleinen Einheitenumrechner, der soweit auch ganz gut funktioniert.
Allerdings ist das Ergebnis nicht immer ganz genau. Hier erstmal etwas Code:

public class Converter {
	
	public static final double MILLIMETER = 0.001;
	public static final double CENTIMETER = 0.01;
	public static final double DEZIMETER = 0.1;
	public static final double METER = 1;
	public static final double KILOMETER = 1000;
	
	public static double[] units = {MILLIMETER, CENTIMETER, DEZIMETER, METER, KILOMETER};

	public static double convert(double value, double from, double into) {
		return (from/into)*value;
	}
}```

Als Ergebnis bekomme ich jetzt ab und zu, nicht immer, komische Ergebnisse, wie z.B. `51.00000000000004` obwohl 51.0 richtig wäre. Wo kommt die 4 her? Bei 52 stimmts wieder, bei 53 nicht mehr, und dannach stimmt wieder alles. (zumindest bis 60)

Kann mir jemand erklären woran das liegt?

Fliesskommazahlen sind prinzipbedingt ungenau, liegt u.a. an der Konvertierung vom Dualsystem ins Dezimalsystem, zB. kann man 1/3 Binaer praezise darstellen, Dezimal aber nicht.

Achso. Also wäre das Ergebnis eigentlich richtig, und wird nur in Dezimal falsch dargestellt?
Falls ich also damit weiterrechnen würde wäre diese Abweichnung nicht drin? Es ist zwar nur minimal, aber eigentlich ist doch keine Abweichung vorhanden oder?
Btw: wie sähe denn 1/3 in Binär aus?

Nein, das ist so wie eben 1/3 in dezimal nicht korrekt dargestellt werden kann. du musst im dezimal System immer auf eine Stelle Runden. z.B auf die vierte komma Stelle 0.3333 und dann ist auf einmal 3 * 1/3 nicht mehr genau 1 sondern 0.9999. Das selbe passiert eben im Binaersystem mit manchen Werten.

Um das Problem zu vermeiden, würde ich dir empfehlen mit long statt mit double zu rechnen (Ein- und Ausgabe können double bleiben) und die kleinste Einheit als Basistyp zu wählen (in diesem Fall also Millimeter).
Vom Design her hätte ich das Problem allerdings komplett anders angefasst.

Edit: will meinen: wenn du mit Festkommazahlen statt mit Gleitkommazahlen rechnest, dann umgehst du das Problem. Du musst dir nur über den erlaubten Wertebereich klar werden.

*** Edit ***
@maki : vielleicht stehe ich grad auf dem Schlauch, aber ich wüsste nicht, wie man 1/3 als Fließkommazahl exakt darstellen können soll.

Ich probier’s mal aus, danke :wink:
Ps: maki hat geschrieben man kann 1/3 Binär aber nicht Dezimal darstellen.

Eine Fließkommazahl ist für mich binär :wink: Bestehend aus Vorzeichen, Exponent und Mantisse.

Da ich nicht weiß was Mantisse ist halte ich mich da lieber raus. :wink:

Lesestoff
(bevor man auf die Nase fällt)

Und für die, die’s genau wissen wollen: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html :smiley:

[QUOTE=cmrudolph]Eine Fließkommazahl ist für mich binär :wink: Bestehend aus Vorzeichen, Exponent und Mantisse.[/QUOTE]…und genau dieses Wissen würde ich nutzen, wenns um Engineering von Einheiten geht… Exponenten aus den doubles extrahieren und entsprechend durch Addition bzw. Subtraktion verändern, bzw gleich mit Logarhitmen rechnen.


public class LengthEngineer {
	public enum Unit {
		METER(0, "m", "Meter"),
		MILLIS(-3, "mm", "Millimeter"),
		MICROS(-6, "um", "Micrometer"),
		PICOS(-9, "pm", "Picometer"),
		NANOS(-12, "nm", "Nanometer"),
		KILOS(3, "km", "Kilometer"),
		;

		private final int exponent;
		private final String unit, toString, name;

		private Unit(int exponent, String unit, String name) {
			this.exponent = exponent;
			this.unit = unit;
			this.name = name;
			toString = (ordinal() + 1) + ". " + name;
		}

		public String unit() {
			return unit;
		}

		public int exponent() {
			return exponent;
		}

		public static Unit getByOrdinal(int ordinal) {
			for(Unit u : values()) {
				if(u.ordinal() + 1 == ordinal) {
					return u;
				}
			}
			return null;
		}

		public static Unit getByName(String name) {
			for(Unit u : values()) {
				if(u.name.equals(name)) {
					return u;
				}
			}
			return null;
		}

		public static double claculate(double value, Unit fromUnit, Unit toUnit) {
			if(fromUnit == toUnit) {
				return value;
			}
			value = Math.log10(value) + fromUnit.exponent() - toUnit.exponent();
			value = Math.pow(10.0, value);
			return value;
		}

		@Override
		public String toString() {
			return toString;
		}
	}

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		for(Unit u : Unit.values()) {
			System.out.println(u);
		}
		System.out.print("Ausgangseinheit: ");
		Unit ae = null;
		while(ae == null) {
			ae = Unit.getByOrdinal(sc.nextInt());
		}
		System.out.print("Zieleinheit: ");
		Unit ze = null;
		while(ze == null) {
			ze = Unit.getByOrdinal(sc.nextInt());
		}
		System.out.print("Wieviel: ");
		double value = Unit.claculate(sc.nextDouble(), ae, ze);
		System.out.println(String.format("Ergebnis: %1.14f", value) + ze.unit());
		sc.close();
	}
}```Runden muss man zwar immer noch, aber zumindest wird's ein wenig genauer.

Das war jetzt aber gemein. Die Leuts, die sich da durcharbeiten, schulen danach zum Politiker um.