PHP rocks! wünscht allen Mitgliedern einen guten Rutsch ins neue Jahr 2017 !!!
Hinweis: Das Forum zieht um! Um keine Datenverluste zu haben, schalten wir zwecks Übernahme der Daten das Forum am Sonntag, den 24.04.2016 um ca. 21:00 Uhr offline und passen anschliessend die DNS-Einträge an.
www.php-rocks.de wird euch dann nach den Aktualisierungen der DNS-Server wieder wie gewohnt uneingeschränkt zur Verfügung stehen.
Danke für euer Verständnis!

Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
round(), setlocale(), mysql - Float rounding errors
#1
Hallo @all,
falls es jemand gebrauchen kann und auch das Problem hat möchte ich es hier mal berichten.
Ich habe nämlich fast ne Stunde gebaucht um drauf zu kommen, grrr.
PHP-Code:
<?php
$value 
0.1;
$v2 0.1;
$v3 $value $v2;
//... mysql/pdo, whatever...
sqlQuery("INSERT INTO table value='".escapeFunc($v3)."'"); 
Das Datenbankfeld hat value hat den Wert 0.00.

Meine Lösung:
PHP-Code:
//setlocale(LC_ALL, 'de_DE');
//setlocale(LC_MONETARY, 'de_DE');
setlocale(LC_ALL'en_EN');
setlocale(LC_MONETARY'en_EN'); 

Es ist zwar sehr praktisch bei currencies usw. direkt "," (komma) statt "."(punkt) automatisch in der string Konvertierung zu haben, bei mir führt es allerdings zu Folgeproblemen s.o.

(Möglich das es auch hilft mysql locale entsprechend anzupassen, habe ich aber nicht probiert und halte ich für, naja...?)
Antworten
#2
Hm… Kannst du zufällig unkompliziert ein nachvollziehbares Beispiel machen, bei dem das Verhalten so auftritt? Das fände ich doch sehr schräg.
Antworten
#3
PHP-Code:
$stueckpreis $produktDB['preis'] + $produktDB['preis_custom'] + $produktDB['preis_einrichtungsgebuehr'];
$preis_summe =  $menge $stueckpreis;
$mwst_summe =  ( ($produktDB['mwst_satz'] / 100) *  $preis_summe ); 
php 5.3 und ja, ein update ist derzeit in der Mache.
Antworten
#4
Ich meinte mehr so in Richtung von testbarem Code. Das ist bei deinem Snippet nicht der Fall, weil zum Beispiel alle Variablen keinen Wert gesetzt bekommen. Zudem ist gar keine Datenbank im Spiel. Aber wenn es nicht so leicht ist, da was zu posten, ist es auch gut. Ich kann es gut nachvollziehen, dass da der Nutzen vielleicht nicht in Relation zum Aufwand steht. Smile

Ich habe ein wenig mit CAST in MySQL rumgespielt, aber da passierte das, was erwartbar war. Ich habe allerdings nicht versucht, Locales umzustellen. War mir auch zu umständlich für den Moment.

Was ich mir denken könnte: Vielleicht hast du beim Dezimaldatentyp im DBMS M und D (siehe etwa hier: https://dev.mysql.com/doc/refman/5.7/en/precision-math-decimal-characteristics.html) nicht richtig gesetzt. Dadurch könnten Dezimalstellen abgeschnitten werden.
Antworten
#5
Zitat:Ich meinte mehr so in Richtung von testbarem Code. Das ist bei deinem Snippet nicht der Fall, weil zum Beispiel alle Variablen keinen Wert gesetzt bekommen. Zudem ist gar keine Datenbank im Spiel. Aber wenn es nicht so leicht ist, da was zu posten, ist es auch gut. Ich kann es gut nachvollziehen, dass da der Nutzen vielleicht nicht in Relation zum Aufwand steht
Das ist im wesentlichen der test-code,
PHP-Code:
$produktDB=array(
'preis' => 9.90,
'preis_custom'=> 0,   //  oder aus DB=(string) '0.00' ?
'preis_einrichtungsgebuehr'=> 0//  oder aus DB=(string)  '0.00' ?
'mwst_satz'=>19 //  oder aus DB=(string)  '19.00' ?
);

$menge 1;
$stueckpreis $produktDB['preis'] + $produktDB['preis_custom'] + $produktDB['preis_einrichtungsgebuehr'];

//setlocale(LC_ALL, 'de_DE');
//setlocale(LC_MONETARY, 'de_DE');
//echo $stueckpreis; // 9,90          <-- hier noch jeweils richtig

setlocale(LC_ALL'en_EN');
setlocale(LC_MONETARY'en_EN'); 
echo 
$stueckpreis// 9.90         <-- hier noch jeweils richtig


$preis_summe =  $menge $stueckpreis;
$mwst_summe =  ( ($produktDB['mwst_satz'] / 100) *  $preis_summe );

//setlocale(LC_ALL, 'de_DE');
//setlocale(LC_MONETARY, 'de_DE');
//echo $mwst_summe; // 1,71 entspricht stückpreis 9 


setlocale(LC_ALL'en_EN');
setlocale(LC_MONETARY'en_EN'); 
//echo $mwst_summe; // 1.881 ~1.88 
danach wird $mwst_summe in ein mysql-feld eingetragen mwst_summe decimal(20,2) , dort ist der Wert dann je nach locale entweder richtig oder abgerundet.

Zitat: Ich kann es gut nachvollziehen, dass da der Nutzen vielleicht nicht in Relation zum Aufwand steht.
Yo Smile
Antworten
#6
Um es noch mal auf den Punkt zu bringen:

Casts von float nach string sind in PHP locale-abhängig.

Daran wird es letztlich wohl liegen.

Code:
<?php

$f = 1337.42;

setlocale(LC_ALL, 'C');
var_dump(setlocale(LC_ALL, 0));
var_dump($f);
echo $f, "\n\n";

setlocale(LC_ALL, 'de_DE.utf8');
var_dump(setlocale(LC_ALL, 0));
var_dump($f);
echo $f, "\n\n";


# Ausgabe:

# string(1) "C"
# float(1337.42)
# 1337.42
#
# string(10) "de_DE.utf8"
# float(1337,42)
# 1337,42

Also wäre es vermutlich ratsam, Floats nicht einfach so in Queries schreiben, sondern mit number_format() oder dergleichen zu behandeln.
Antworten
#7
Zitat:Casts von float nach string sind in PHP locale-abhängig.

Daran wird es letztlich wohl liegen.
Ja.

Zitat:Also wäre es vermutlich ratsam, Floats nicht einfach so in Queries schreiben, ...
Ich hatte wohl nicht recht bedacht, daß ein query ja ein string ist wenn nicht "prepared" Confused

Ich denke der Fehler kommt hier zustande, nicht durch die float<->string Konvertierung ansich, sondern, da verschieden lokalisierte Werte verwendet werden:
Wenn $preis_summe auf DE loaklisiert, aber $produktDB['mwst_satz']=19.00
$mwst_summe = ( ($produktDB['mwst_satz'] / 100) * $preis_summe );
also (float)19,0 in DE !== (float)19.00 in EN

Zitat:..., sondern mit number_format() oder dergleichen zu behandeln.
number_format z.B. ist ja wieder eine String-Konvertierung.
Ich denke der beste Weg wäre ein float einfach als float zu belassen, also en_EN lokalisiert.
Bei Rechnen verwendet man in php 19.0, die Formatierung mit Komma gehört m.E. aufjedenfall in die String Ausgabe.
Mir erscheint es dadurch, bzw. praktisch kommt es mir dadurch so vor, als wenn durch die de_DE Lokalisierung ein float gar kein float mehr ist, oder anders gesagt, wieso sollte man überhaupt eine float-Notation ja gar die mathematische Syntax von php lokalisieren?

Eigentlich sollten automatische Typen-Konvertierungen generell verboten werden, das schafft nur Durcheinander!!!
Antworten
#8
Dein Beispiel gibt bei mir das aus, was erwartbar ist. (Habe etwas andere Locale-Namen auf meinem System.)

PHP-Code:
<?php

function test($locale)
{
 
   setlocale(LC_ALL$locale);
 
   setlocale(LC_MONETARY$locale);

 
   $produktDB=array(
 
       'preis'                     => 9.90,
 
       'preis_custom'              => 0    //  oder aus DB=(string) '0.00' ?
 
       'preis_einrichtungsgebuehr' => 0    //  oder aus DB=(string)  '0.00' ?
 
       'mwst_satz'                 => 19     //  oder aus DB=(string)  '19.00' ?
 
   );

 
   $menge 1;
 
   $stueckpreis $produktDB['preis']
 
           $produktDB['preis_custom']
 
           $produktDB['preis_einrichtungsgebuehr'];

 
   echo $stueckpreis"\n";

 
   $preis_summe $menge $stueckpreis;
 
   $mwst_summe  = ($produktDB['mwst_satz'] / 100) * $preis_summe;

 
   echo $mwst_summe"\n";
}

test('en_US.utf8'); // 9.9  1.881

test('de_DE.utf8'); // 9,9  1,881 

Zitat:Ich denke der Fehler kommt hier zustande, nicht durch die float<->string Konvertierung ansich, sondern, da verschieden lokalisierte Werte verwendet werden:
Wenn $preis_summe auf DE loaklisiert, aber $produktDB['mwst_satz']=19.00
$mwst_summe = ( ($produktDB['mwst_satz'] / 100) * $preis_summe );
also (float)19,0 in DE !== (float)19.00 in EN

Die Lokalisierung betrifft aber nicht die interne Repräsentation, sondern nur die Ausgabe. Das passiert beim Konvertieren nach string.

Du wolltest wahrscheinlich ursprünglich (so von der Idee her) 9.9 in die DB eintragen, aber durch die Umwandlung von float→string für den Query-String wurde daraus "9,9", was in der DB als 9.0 ankam, weil die DB den Punkt als Separator erwartet.

An der Stelle würde ich – wenn es so war – das größte Problem sehen: Die DB hätte das nicht stillschweigend annehmen sollen.

Zitat:number_format z.B. ist ja wieder eine String-Konvertierung.

Aber mit einem speziellen Algorithmus, bei dem du angeben kannst, welcher Dezimalseparator genutzt werden soll.

Zitat:Mir erscheint es dadurch, bzw. praktisch kommt es mir dadurch so vor, als wenn durch die de_DE Lokalisierung ein float gar kein float mehr ist, oder anders gesagt, wieso sollte man überhaupt eine float-Notation ja gar die mathematische Syntax von php lokalisieren?

Intern ist das unverändert die gleiche Struktur, nur beim Konvertieren nach string wird der andere Dezimalseparator gewählt.

Floats sind ohnehin immer so eine Sache. Auch wegen der üblichen Fließkommazahl-Darstellungsgeschichten kann man die eigentlich nicht einfach so ausgeben, weil es mit etwas Pech nicht 2,7 oder so wird, sondern 2,699999999.
Antworten
#9
Zitat von mir:
Zitat: Smile
...warum sollte man überhaupt die Syntax einer Algebra lokalisieren...?

Man glaubt es kaum, in diesem Zusammenhang läuft mir gerade Excel über den Weg:
Zitat:Mit der Funktion SUMMEWENN können Sie die Werte in einem Bereich addieren, die angegebenen Kriterien entsprechen. Wenn Sie beispielsweise in einer Spalte mit Zahlen nur die Werte addieren möchten, die größer als 5 sind, können Sie die folgende Formel verwenden:
Code:
=SUMMEWENN(B2:B25;">5")

https://support.office.com/de-de/article/SUMMEWENN-Funktion-169b8c99-c05c-4483-a712-1697a653039b
Antworten
#10
Sorry, der Zusammenhang zum Rest des Threads erschließt sich mir jetzt nicht.
Antworten


Gehe zu: