Verwirrung bei PHP (Rechnung)
steffen
- php
Hi, habe ein Script gebastelt das ein kleiner Rechner ist nun habe ich ein wirklich unerfreuliches Erlebnis!
Ich errechne mir 2 Werte! ($s1 sowie $s2) Wenn ich nun $s1 den Wert direkt zuweise (1.10526315789) funktioniert die Rechnung, aber lasse ich mir diesen Wert ausrechnen stimmt dieser nicht o_O
Vielleicht weiß jemand die Erklärung wieso?
<?
$go=1;
$a=34;
$v=19;
$lp=21;
$gegnera=38;
$gegnerv=15;
$gegnerlp=21;
$s1=$gegnerlp/($a-$gegnerv); // Dieser Wert ist 1.10526315789
$s2=$lp/($gegnera-$v);
function test($gegnera,$v){
if($gegnera-$v<1){ return 1; } else { return $gegnera-$v; }
}
function test2($a,$gegnerv){
if($a-$gegnerv<1){ return 1; } else { return $a-$gegnerv; }
}
if ($go){
echo "s1 = ".$s1."<br>";
echo "s2 = ".$s2."<br>";
if($s1>$s2) {
echo "<b><font color="red">Du verlierst!</font> Dein Gegner überlebt mit ".floor($gegnerlp-(test2($a,$gegnerv)*$s2))." LP</b>";
} else {
echo $lp."<br>";
echo (test($gegnera,$v)*$s1)."<br>";
echo ($lp-(test($gegnera,$v)*$s1))."<br>";
echo floor($lp-(test($gegnera,$v)*$s1));
}
}
?>
Lasse ich mir den Wert ausrechnen gibt er das aus:
s1 = 1.10526315789
s2 = 1.10526315789
21
21
-3.5527136788E-15
-1
gebe ich den Wert direkt ein kommt das dabei heraus:
s1 = 1.10526315789
s2 = 1.10526315789
21
20.9999999999
8.99973429114E-11
0
Das letztere ist das eigentliche Ergebnis das rauskommen soll ... Wieso passiert das?
mfg steffen
Hi,
Das letztere ist das eigentliche Ergebnis das rauskommen soll ... Wieso passiert das?
Weil Dezimalzahlen nicht exakt in den üblichen Binärformaten gespeichert werden können.
cu,
Andreas
Und wie bekomme ich das hin, dass es funktioniert?
Hallo steffen,
Und wie bekomme ich das hin, dass es funktioniert?
Baue Deine Operationen so um, dass Du stets mit Ganzzahlen arbeiten kannst, d.h. verzichte auf jegliche Division.
Eine Multiplikation mit dem kleinsten gemeinsamen Vielfachen aller Divisoren sollte genügen.
Freundliche Grüsse,
Vinzenz
echo floor($lp*100000000000-(test($gegnera*100000000000,$v*100000000000)*$s1*100000000000));
So hab ich es nun gemacht funtzt :P
geht es auch leichter? :)
Hallo steffen,
floor($lp*100000000000-(test($gegnera*100000000000,$v*100000000000)*$s1*100000000000));
So hab ich es nun gemacht funtzt :P
Glücksache! Und das ist mein Ernst. Bei anderen Zahlen kann es genausogut daneben gehen. Durch Multiplikation mit großen Zahlen wird die Genauigkeit nicht besser.
geht es auch leichter? :)
Du hast als Ausgangspunkt ganzzahlige Werte. Versuche doch stets bei Ganzzahlarithmetik zu bleiben.
$s1=$gegnerlp/($a-$gegnerv); // Dieser Wert ist 1.10526315789
$s2=$lp/($gegnera-$v);
Multipliziere beide Terme mit dem Produkt der beiden Divisoren:
$s1 = ($gegnerlp/($a-$gegnerv)) * ($a-$gegnerv) * ($gegnera -$v)
= $gegnerlp * ($gegnera - $v)
$s2 = ($lp / ($gegnera - $v)) * ($a-$gegnerv) * ($gegnera -$v)
= $lp * ($a - $gegnerv)
$s1 und $s2 sind jeweils mit dem gleichen Faktor multipliziert worden,
bleiben aber nun Ganzzahlen. Mit Ganzzahlen kann man exakt vergleichen, es entstehen keine Rundungsfehler. D.h. dieser Vergleich von $s1 und $s2 wird das korrekte Ergebnis zurückliefen.
Natürlich musst Du anschließend bei der Ausgabe, wieviele Punkte Unterschied es gibt, diesen Faktor wieder mit berücksichtigen. Das überlasse ich Dir als Übung. Nach dem Vergleich ist es jedoch irrelevant, ob Du Ganzzahl- oder Gleitpunktarithmetik verwendest, Du hast den richtigen Sieger ermittelt.
Bei den von Dir angegebenen Zahlen sollte eine typische vorzeichenbehaftete 32-Bit-Integer ausreichen (etwas über 2 Milliarden, genauer gesagt 2^31 - 1).
Freundliche Grüsse,
Vinzenz
Hi Vinzent,
können wir uns mal in Kontakt setzen, weil deine Lösung ist nicht gerade der bringer, weil ich mit $s1 ja noch weiter arbeite.
93464781 falls du ICQ besitzen solltest.
mfg
Hallo steffen,
Ich errechne mir 2 Werte! ($s1 sowie $s2) Wenn ich nun $s1 den Wert direkt zuweise (1.10526315789) funktioniert die Rechnung, aber lasse ich mir diesen Wert ausrechnen stimmt dieser nicht o_O
Vielleicht weiß jemand die Erklärung wieso?
Du operierst an der Genauigkeitsgrenze von Gleitpunktzahlen.
Was für uns Menschen ganz einfach ist, kann für den Computer schwer bis unmöglich werden. So ist es mit den üblicherweise verwendeten Gleitpunktzahlen nicht möglich, 0.1 d.h. ein Zehntel exakt darzustellen, 0.25 stellt jedoch kein Problem dar.
Lies bitte dazu "Datentypen in C", http://pronix.linuxdelta.de/C/standard_C/c_programmierung_8.shtml#6. Das Problem ist nicht eines der Programmiersprachen, sondern eines der Zahldarstellung.
Freundliche Grüsse,
Vinzenz
Moin,
Lies bitte dazu "Datentypen in C", http://pronix.linuxdelta.de/C/standard_C/c_programmierung_8.shtml#6. Das Problem ist nicht eines der Programmiersprachen, sondern eines der Zahldarstellung.
Und deswegen kann man 'einfach' die Zahlendarstellung wechseln: http://php.net/bc
Hallo Henryk
Lies bitte dazu "Datentypen in C", http://pronix.linuxdelta.de/C/standard_C/c_programmierung_8.shtml#6. Das Problem ist nicht eines der Programmiersprachen, sondern eines der Zahldarstellung.
Und deswegen kann man 'einfach' die Zahlendarstellung wechseln: http://php.net/bc
Sicher sind die mathematischen Funktionen mit beliebiger Genauigkeit etwas Feines und sehr nützlich. In diesem speziellen Fall hier sehe ich jedoch keine Notwendigkeit, überhaupt damit zu arbeiten. Wenn ich durch Einsatz von etwas Gehirnschmalz mit Ganzzahlarithmetik auskomme, so ist das in meinen Augen viel besser.
Überleg' doch selbst: Kannst Du 1/3 mit Zahlen mit beliebiger Genauigkeit exakt darstellen? Was passiert bei folgenden Rechenschritten auch beim Einsatz von bc:
T0 = 1
T1 = T0 / 9
T2 = 9 * T1
oder vergleichbaren Schritten? Willst Du dynamisch ermitteln, wieviele Nachkommastellen ggf. erforderlich sind?
Schau Dir doch nochmals das extrem kritische Beispiel im Ausgangsposting an. Kannst Du mit BC sicherstellen, dass keine Rundungsfehler in die falsche Richtung auftreten?
Freundliche Grüße
Vinzenz