Perl Rundungsfehler erneut

7

Das Beispiel, um das Problem zu zeigen:

  • mit einer Nummer 105;
  • dividiere mit 1000 (Ergebnis 0.105)
  • auf 2 Dezimalstellen aufgerundet sollte: 0,11
  • sein

Nun, mehrere Skripte - basierend auf Antworten auf andere Fragen:

Dies wird meistens empfohlen und die meisten Uploads verwenden printf .

%Vor%

gibt jedoch ein falsches Ergebnis aus. Im Kommentar zu Ссылка @Sinan Unur sagt (6-mal höher bewerteter Kommentar):

  

Verwenden Sie sprintf ("%. 3f", $ value) auch für mathematische Zwecke.

aber, es funktionierte nicht "manchmal" ... wie oben.

Die andere empfohlene Lösung Math::BigFloat :

%Vor%

Falsches Ergebnis auch. Ein weiteres empfohlenes bignum :

%Vor%

ist wieder falsch. (

Jetzt die arbeitenden:

%Vor%

gutes Ergebnis 0.11 , jedoch ein Kommentar hier Ссылка beschwert sich darüber.

und schließlich noch eine Empfehlung "von mir selbst":

%Vor%

druckt 0.11 ebenfalls, kann aber nicht beweisen, dass es korrekt ist.

Für die Referenz wurde ich gelesen:

Ich verstehe, als es allgemeines Problem in allen Sprachen ist, aber bitte schließlich über dem Lesen - ich habe immer noch diese Frage:

Was ist der fehlersichere Weg in Perl, um eine Gleitkommazahl auf N zu runden? Nachkommastellen - mit mathematisch korrekter Weise, z. was wird runde Ergebnisse wie 105/1000 korrekt auf N Nachkommastellen ohne "Überraschungen" ...

    
kobame 30.06.2014, 15:06
quelle

5 Antworten

6

In den alten Integer-Mathetagen der Programmierung verwenden wir vorgeben , um Dezimalstellen zu verwenden:

%Vor%

Wir haben einen wertvollen Trick gelernt, als wir versuchten, die Umsatzsteuer richtig zu runden:

%Vor%

Durch Hinzufügen von 1/2 der Genauigkeit zeige ich die Rundung korrekt an. Wenn die Zahl 1,344 ist, wurde durch Hinzufügen von .005 1,349 und durch Abschneiden der letzten Ziffer Dip-Layern 1,344 angezeigt. Wenn ich dasselbe mit 1.345 mache, wird durch Hinzufügen von .005 1.350 und durch Entfernen der letzten Ziffer wird es als 1.35 angezeigt.

Sie könnten dies mit einer Subroutine tun, die den gerundeten Betrag zurückgibt.

Interessant ...

Es gibt eine PerlFAQ zu diesem Thema. Es empfiehlt sich, einfach printf zu verwenden, um die richtigen Ergebnisse zu erhalten:

%Vor%

Für Pi funktioniert das, aber nicht für .105 . Jedoch:

%Vor%

Das sieht nach einem Problem bei der Art aus, wie Perl .105 intern speichert. Wahrscheinlich etwas wie .10499999999 , das korrekt nach unten abgerundet würde. Mir ist auch aufgefallen, dass Perl mich warnt, round und rounding als mögliche zukünftige reservierte Wörter zu verwenden.

    
David W. 30.06.2014, 15:26
quelle
11

Sie erwarten ein bestimmtes Verhalten, wenn die Zahl genau 0.105 ist, aber Gleitkommafehler bedeuten, dass Sie nicht erwarten können, dass eine Zahl genau das ist, was Sie denken.

105/1000 ist eine periodische Zahl in binärer Form, genau wie 1/3 in dezimaler Periodizität.

%Vor%

0.1049999 ... ist kleiner als 0.105, also rundet es auf 0.10.

Aber selbst wenn Sie genau 0,105 hätten, wäre das immer noch auf 0,10 gerundet, da sprintf die Hälfte auf gerade abrundet. Ein besserer Test ist 155/1000

%Vor%

0,155 sollte auf 0,16 runden, aber aufgrund eines Gleitkommafehlers auf 0,15 runden.

%Vor%

Der zweite funktioniert, weil 5/10 nicht periodisch ist, und darin liegt die Lösung. Wie Sinan Unur sagte, können Sie den Fehler korrigieren, indem Sie sprintf verwenden. Aber Sie müssen auf eine Ganzzahl runden, wenn Sie Ihre Arbeit nicht verlieren wollen.

%Vor%

Damit wird der Rundungsfehler behoben, der es sprintf erlaubt, die Hälfte gleichmäßig zu runden.

%Vor%

Wenn du stattdessen die Hälfte abrunden willst, musst du etwas anderes als sprintf verwenden, um das letzte Runden zu machen. Oder Sie könnten s/5\z/6/; vor der Division durch 10 hinzufügen.

Aber das ist kompliziert.

Der erste Satz der Antwort ist der Schlüssel. Sie erwarten ein bestimmtes Verhalten, wenn die Zahl genau 0.105 ist, aber Gleitkommafehler bedeuten, dass Sie nicht erwarten können, dass eine Zahl genau so ist, wie Sie es für richtig halten. Die Lösung besteht darin, eine Toleranz einzuführen. Das ist das Runden mit sprintf , aber es ist ein stumpfes Werkzeug.

%Vor%

%Vor%

Es gibt wahrscheinlich bereits existierende Lösungen, die auf die gleiche Art und Weise funktionieren.

    
ikegami 30.06.2014 15:48
quelle
3

Ihre benutzerdefinierte Funktion sollte größtenteils wie erwartet funktionieren. Hier ist, wie es funktioniert und wie Sie es überprüfen können:

%Vor%

Das folgende wird jedoch einen Fehler für $float = 0 auslösen, also gibt es am Anfang eine zusätzliche if -Anweisung.

Das Schöne an der obigen Funktion ist, dass es möglich ist, auf negative Dezimalstellen zu runden, so dass Sie links von der Dezimalstelle runden können. Zum Beispiel wird myround(123, -2) 100 geben.

    
Mr. Llama 30.06.2014 15:19
quelle
2

Ich würde use bignum zu Ihrem ursprünglichen Codebeispiel hinzufügen.

%Vor%

Dies löst den Fehler, den Sie in Ihrem use Math::BigFloat -Beispiel gemacht haben, indem Sie Ihre Zahlen sofort in Objekte zerlegen und nicht darauf warten, dass Sie die Ergebnisse eines Rundungsfehlers in Math::BigFloat->new

übergeben     
tjd 30.06.2014 17:36
quelle
0

Nach meiner Erfahrung verwendet Perl printf / sprintf einen falschen Algorithmus. Ich habe diese Schlussfolgerung unter Berücksichtigung des folgenden einfachen Beispiels gemacht:

%Vor%

Der ganzzahlige Teil sollte in diesem Beispiel keinen Einfluss haben, aber es hat. Ich habe dieses Ergebnis mit Perl 5.8.8 erhalten.

    
Alexander Samoylov 21.12.2016 12:20
quelle

Tags und Links