PHP Fehlerhaftes Ergebnis durch Int-Typecast

M3Y3R

Well-known member
ID: 336361
L
8 Mai 2006
1.608
60
Hallo,

ich bereite mich gerade auf die Zend-PHP-Prüfung vor. Da ich ja nun nicht faul herum sitze, sondern mir auch den Study Guide und einiges im Web dazu durchlese habe ich nun folgende Frage gefunden:

PHP:
<?php echo (int)((0.1 + 0.7) * 10); ?>

Was ist das Ergebnis der oben stehenden Rechnung?

A: Das PHP-Script wirft eine Exeption und bricht ab
B: 7
C: 10
D: 8

Rein vom logischen her, wäre die Lösung 8. Probiert man aber das ganze aus, wird man feststelle, dass die Lösung B (7) richtig ist.

Ich habe verschiedene Ansätze (auch Teilrechnungen) versucht um logisch auf die 7 zu kommen, aber nichts hat geklappt. Herausgefunden habe ich aber, dass dies am Typecasten, also dem "(int)" liegt, doch warum ist dies so?

Würde das (int) jeden Wert in der Klammer Typecasten, müsste das Ergebnis eigentlich 0 sein.

Da ich nun nicht ganz nachvollziehen kann, wie PHP vorgeht, hoffe ich dass mir von euch evtl. jemand helfen bzw. mir die Vorgehensweise erklären kann!?

Wer kann mir hier freundlicherweise Erklären, wo das Problem liegt?

Danke im voraus!


MFG
Papenburger
 
Fließkommazahlen haben eine begrenzte Präzision. PHP nutzt normalerweise (dies hängt aber vom System ab) das IEEE 754 double precision Format, welches einen maximalen relativen Fehler von 1.11e-16 hat. Nicht elementare Rechenoperationen können einen größeren Fehler verursachen und bei Durchführung von mehreren Operationen können sich die Fehler addieren.

Weiterhin können viele rationale Zahlen, die zwar eine genaue Darstellung zur Basis 10 besitzen, wie beispielsweilse 0.1 oder 0.7, nicht genau als Gleitkommazahl zur intern genutzten Basis 2 dargestellt werden, unabhängig von der Größe der Mantisse. Daher können sie nicht ohne einen gewissen Präzisionsverlust in ihr internes binäres Gegenstück umgewandelt werden können. Dies kann zu verwirrenden Ergebnissen führen, so ergibt floor((0.1+0.7)*10) in der Regel 7 an Stelle der erwarteten 8, da die interne Repräsentation etwa wie folgt aussieht: 8

Quelle: https://www.php.net/manual/de/language.types.float.php#warn.float-precision
 
:oops: Das hätte ich ja auch selber mit den richtigen Suchbegriffen finden können... :oops:
Aber nun weiß ich wenigstens warum anstelle der erwarteten 8, eine 7 heraus kommt.

Auf Deutsch gesagt, kann 0.1 und 0.7 nicht korrekt in Binär umgewandelt werden, damit mit diesen Werten gerechnet werden kann. Durch dieses Fehlerhafte verhalten bzw. dieser kleinen Abweichung kommt anstelle der 8 dann die 7 als Ergebnis heraus.
 
was passiert denn wenn du folgendes ausgibst ?

echo (int)0.1
echo (int)0.7

dann würdest du vllauch so auf das ergebnis kommen ;)
 
was passiert denn wenn du folgendes ausgibst ?

echo (int)0.1
echo (int)0.7

Das ergibt jeweils 0, was mit 10 multipliziert ebenfalls 0 ergibt. Das bedeutet, dass dein Lösungsansatz nicht korrekt ist. In meinem Beispiel rechnet er ja auch erst 0.1 + 0.7, multipliziert dieses mit 10 und macht dann das Typecast...
 
was ergibt dann

echo (int)(0.1 + 0.7); ?

ich weiss das beides 0 ergibt, aber auch das ergebnis oben wird 0 ergeben.

echo ((0.1 + 0.7) * 10 );

wird sicher 8 ergeben...

Also liegt der Fehler wenn im Typecast und nicht in der internen Darstellung bei der Umrechnung zur Basis 2
Dafür gibt es einen Algorithmus, der wohl nicht Fehlerbehaftet sein wird ..., denn der ist nicht in PHP, sondern in der ALU vom PC - vll auch nicht dort, aber Rechenoperationen sind vorgegeben und werdenhalt benutzt
 
Hmm, das mir ja neu, das muss ich echt erst mal testen.
Das aber ja ein riesen Mist finde ich...
 
Also liegt der Fehler wenn im Typecast und nicht in der internen Darstellung bei der Umrechnung zur Basis 2

Nein, der Fehler liegt scheinbar viel mehr daran, wie PHP die Zahlen ausgibt.
Rechne ich das ganze in Java, welches nicht wie PHP automatisches Typecasting usw. macht, dann erhalte ich korrekterweise für (0.1 + 0.7) * 10 eine 7.999999999999999.
PHP gibt dies aber scheinbar direkt gerundet als 8 aus, es ist aber eine 7 Periode 9, was auch der Int-Cast beweist.

Aber auch mit Java bin ich gerade am hadern: 0.1 und 0.7 dürften eigentlich nicht direkt als diese Zahl zur Basis 10 darstellbar sein, so werden sie aber dargestellt (eventuell eine Compiler-Optimierung die ich gerade nicht umgehen kann). Erst wenn ich 0.1 + 0.7 rechne erhalte ich nicht mehr das genaue Ergebnis 0.8.


Sofern man weiß, dass bei Single-Fließkommazahlen der Ganzzahlanteil größer 2^53 ungenau abgebildet werden und dass sich nur der Nachkommaanteil aus Additionen nach dem Muster 2^(-x) korrekt abbilden lässt, ist man auf der sicheren Seite. Darüber hinaus zeigt ja auch das Java-Beispiel, dass man sehr schnell fälschlicherweise richtige Dinge präsentiert bekommt.
Das man sich in dieser Aufgabe darauf verlassen soll, dass die Fließkommazahl kleiner als die Basis 10 Kommazahl gebildet wird, ist mir irgendwie suspekt. Denn mir war bisher keine Regel bekannt, dass dies so abgebildet werden muss, sondern dass die Fließkommazahl immer Rounding-to-Nearest macht, hmm. Hab nun aber auch keine Lust die IEEE754 Specs zu lesen, zumal die sowieso niemand einhält.
 
Zuletzt bearbeitet:
@ice:

Seit wann macht PHP automatisches Typecast ? in PHP wurde doch das erste Mal versucht der Ansatz frei vom strikten Datentyp zu sein ;)

ja so is das mit den standards ....
alle wollen sie und dann hat man doch ne eigene Idee den standard zu verfeinern - obs dann wirklich das richtige ist (sehen wir ja an diesem beispiel) ;)

Super das es andere Programmiersprachen wenigstens richtig machen. Das bei einem integer cast allerdings bei 7.9999... auf 8 round nearest passiert wäre mir allerdings neu.
Denn das würde bedeuten, ich könnte mit dem Cast auch den Round befehl in PHP nutzen und käme so zumgleichen ergebnis, was beim besten willen nicht der Fall der PHP Autoren sein kann und sicher auch nicht ist ...

Dennoch ist dies sehr Suspekt als Beispiel !
Denn wenn beim umwandeln der flieskommazahlen dererlei fehlerbehaftung an board ist, könnte ich nie ein Verfahren entwickeln was auf Basis der Fliesskommazahlen basiert, weil auch JAVA ja eine 7,999999.. komischerweise ausgibt..

Shit happens anywhere
 
PHP gibt dies aber scheinbar direkt gerundet als 8 aus, es ist aber eine 7 Periode 9, was auch der Int-Cast beweist.

PHP gibt ja 7 aus und nicht 8.

Denn mir war bisher keine Regel bekannt, dass dies so abgebildet werden muss, sondern dass die Fließkommazahl immer Rounding-to-Nearest macht

Es passiert kein Rounding to Nearest.
https://codepad.org/AiQU9vR2

Deswegen macht das Ganze auch Sinn, dass 7 herraus kommt.
 
Seit wann macht PHP automatisches Typecast ? in PHP wurde doch das erste Mal versucht der Ansatz frei vom strikten Datentyp zu sein ;)
überall wo es denkt es sei nötig?

ja so is das mit den standards ....
alle wollen sie und dann hat man doch ne eigene Idee den standard zu verfeinern - obs dann wirklich das richtige ist (sehen wir ja an diesem beispiel) ;)
Das Problem am IEEE 754 Fließkommastandard ist einfach, dass diese Fließkommaoperationen nichtmal portabel sind und auf 2 Rechnern verschiedene Ergebnisse bringen können. Wir leben in einer wunderbaren Welt bei der man sich nichtmal darauf verlassen kann, dass solch eine primitive arithmetische Operation überall gleich ist...

Super das es andere Programmiersprachen wenigstens richtig machen. Das bei einem integer cast allerdings bei 7.9999... auf 8 round nearest passiert wäre mir allerdings neu.
Es passiert kein Rounding to Nearest.
https://codepad.org/AiQU9vR2
Nein nein, das macht PHP ja auch richtig, wie man sieht. Wenn du das Ergebnis aber ohne Cast mit einem var_dump() ausgibst, kommt ein 8.0 heraus, was eben nicht sein kann, da ein Integer Cast dann auch 8 ergeben müsste. Meine Vermutung war deshalb, dass PHP bei der Floating-Point Ausgabe rundet, eventuell weil es den Fall erkannt hat, dass dies 8.0 sein müsste und nicht 7.999999 (somit würde jede x.999999 Zahl aufgerundet ausgegeben). Was auch so zu sein scheint, mal wieder ein Beispiel für das komische Verhalten von PHP.


Nachtrag:
Ich habe versucht das ganze mal über den SourceCode herauszufinden, bin aber gescheitert. Die Implementierung von var_dump ruft eine php_printf Funktion auf. Diese ist in der php.h definiert, jedoch finde ich nicht, wo diese implementiert wird. Einen Git-Checkout für eine Texsuche zu machen, habe ich gerade keine Lust.
 
Zuletzt bearbeitet:
wenn php in c oder c++ entwickelt wurde, dann ist es sicher die standard funktion von der system.h aus der sprache, wo quasi mit dem php_printf denk ich per pointer drauf verwiesen wird...

wozu das einfache rad neu erfinden ;)

evetuell ist es eine abstract definierte oder gar virtual deklarierte funktion, das weißt dann auf jeden Fall auf das oben vermutete hin
 
weil PHP das im Quellcode dauernd macht :biggrin:

Also es hat mir keine Ruhe gelassen und ich habe nochmal weitergesucht:

Hier komme ich nun aber auch nicht weiter. So eine printf-Implementierung ist nicht gerade leserlich. Die var_dump-Implementierung hat an php_printf einen Precision-Wert übergeben und bis zu xbuf_format_converter weitergereicht, vermutlich regelt dieser bis zu welcher Genauigkeit ausgegeben wird und veranlasst für die 7.999999999999999 eine Rundung, aber mit Codezeilen kann ich es nun leider nicht belegen :(


@tobomator: gewonnen! PHP hat mal wieder eine Standard-C-Funktion neu erfunden *gg* Vermutlich um eine konistente Ausgabe auf allen Systemen zu haben, weil eventuell irgendwann mal die libc-Implementierungen für irgendeinen Fall verschiedene Ergebnisse produziert haben. C Codebases sind oft sowas von schlimm ^^
 
vermutlich ab zeile:
https://github.com/php/php-src/blob/master/main/spprintf.c#L291

wird es interessant, da wo es um die Nachkommastellen geht

aber auch schon weiter oben gibt es u_wide_int und wide_int (213 & 214) ...
wo man schauen müsste, wie diese eigentlich ihren Ursprung haben...

Gibt einiges, wo man noch ansetzen könnte, aber recht hast du mit dem teil, das die Autoren das Rad wirklich neu erfunden haben. Da es ja portiert wird, ist der Fehler sicherlich auch in PHP auf den Linux Distributionen zu finden oder wo PHP überall laufen kann
 
Mal ganz abgesehen von deinem Problem würden mich mal deine Motive für eine solche Qualifizierung interessieren. Was versprichst du dir davon? Im Internet liest man ja sehr unterschiedliche Meinungen, meist jedoch negative.

Ich habe persönlich auch schon mit dem Gedanken gespielt, konnte mich jedoch noch nicht dazu durch ringen.

mfg
 
Nö, das is das Syntax-Highlighting, was die Compiler-Präprozessor-Direktiven grau darstellt.
 
yep hast recht!
Gibts eigentlich nen korrekten Syntax Highlighting RA der selbst mit verschiedenen Kommentaren oder auskommetieren klar kommt ?
Egal das ist ja die Essenz dessen ;)

OK das wars auch für mich zu dem Thema, ich fang langsam an, PHP mit anderen Augen zu betrachten.
Wer weiß, wenn es bei der einen Stelle schon solche "Fehler" (gewollt oder nicht) gibt, wo sind noch mehr vergraben...
 
[...]würden mich mal deine Motive für eine solche Qualifizierung interessieren. Was versprichst du dir davon?
Hm, eine zusätzliche Qualifizierung mit der ich aus der Masse heraussteche. Zudem zeigt sie auch, dass ich mich mit neuerem beschäftige und mich weiterbilde anstatt mich immer nur mit dem alten zu beschäftigen.

Im Internet liest man ja sehr unterschiedliche Meinungen, meist jedoch negative.
Da stimme ich dir zu. Die Meinungen zu dieser Zertifizierung sind unterschiedlich. Die Zertifizierung wird derzeit von unserem Unternehmen verlangt und auch von derselben bezahlt. Die Prüfung selbst kostet 200,00 €. Es gibt auch noch einen Vorbereitungskurs der allerdings 1.000,00 € kostet...