[PHP] afronden met number_format gaat niet correct.

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik kwam na maanden van gebruik er zojuist achter dat de PHP "number_format" niet helemaal correct afrond.
Problemen met "number_format" zijn al eerder besproken in dit forum, er is echter nog geen oplossing voor dit probleem.

Men neme bijvoorbeeld 5,5 x 1,19 wat de uitkomst 6,545 heeft.
Ronden we dit bedrag af met twee cijfers achter de comma, komen we op 6,55 .

Als ik onderstaande script gebruik, krijg ik echter de uitkomst 6,54 .
De gebruikte "number_format" rond in dit geval niet goed af.

Afbeeldingslocatie: http://sepers.org/voorbeeld_nf.gif

Als je bijvoorbeeld het getal 1.235 laat afronden door deze functie krijg je wel de juiste uitkomst, namelijk 1,24 .

Hoe krijg ik het voor elkaar dat de bedragen in php correct worden afgerond?

_/-\o_ Alvast bedankt voor jullie hulp! _/-\o_

[ Voor 6% gewijzigd door Verwijderd op 24-08-2003 00:11 ]


Acties:
  • 0 Henk 'm!

  • jan-marten
  • Registratie: September 2000
  • Laatst online: 16-09 14:45
Zelf even afronden op een slimme manier?
ceil / floor / round whatever

Acties:
  • 0 Henk 'm!

Verwijderd

Je helpen met het probleem in PHP kan ik niet, maar
zover ik weet is dit de zgn. Banking afronding (of zoiets), is
iets wat in meer talen gebeurd. Een gewone afronding
gebruiken, en zelf het formaat zetten zou misschien
kunnen helpen.

[ Voor 5% gewijzigd door Verwijderd op 24-08-2003 00:22 ]


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Nu online
Het probleem zit 'm niet in number_format, maar in het feit dat het getal 10,45 niet exact gerepresenteert kan worden in een (IEEE-compatible) floating point getal. De reden dat het getal niet te representeren is, is dat een floating point getal intern gerepresenteerd wordt als een som van 2-machten. Python laat op de command prompt netjes zien dat het getal slechts benadert wordt:
Python:
1
2
3
4
>>> 5.5 * 1.9
10.449999999999999
>>> 10.45
10.449999999999999

10.45 wordt dus gerepresenteert door een getal dat dicht bij 10.449999999999999 ligt (maar het ook niet exact is).

Je kunt het probleem omzeilen als een van de twee getallen een constante is. In plaats van met 1,9, bijvoorbeeld, vermenigvuldig je dan met 190 (als je twee decimalen wilt hebben: 1,9 * 100 = 190). Vervolgens kun je het getal converteren naar een integer (een geheel getal) en de delen weergeven met (1045/100).','.(1045%100).

In PHP code:
PHP:
1
2
$x = 5.5 * 190;
echo (int)($x/100).','.(int)($x%100);

Acties:
  • 0 Henk 'm!

Verwijderd

Soultaker schreef op 24 August 2003 @ 00:22:
...
aannemelijk verhaal
...
Klink zeer aannemelijk, maar zou wel een zeer slechte zaak zijn.

[ Voor 11% gewijzigd door Verwijderd op 24-08-2003 00:25 ]


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Nu online
Verwijderd schreef op 24 augustus 2003 @ 00:25:
Klink zeer aannemelijk, maar zou wel een zeer slechte zaak zijn.
Het is de alledaagse praktijk. ;)

Een andere (veel simpelere) work-around, als je niet te al te veel geeft om de precisie, is het toevoegen van een kleine constante aan je resultaat (0.00000001 ofzo) zodat je op zo'n grensgeval toch naar boven af kunt ronden.

Dit staat trouwens ook gewoon in de PHP handleiding, zie ik net:
When rounding on exact halves round() rounds down on evens and up on odds. If you want to always force it in one direction on a .5 (or .05 in your case) add or substract a tiny fuzz factor. The reason behind rounding half the values down and the other half up is to avoid the classical banking problem where if you always rounded down you would be stealing money from your customers, or if you always rounded up you would end up over time losing money. By averaging it out through evens and odds you statistically break even.

[ Voor 45% gewijzigd door Soultaker op 24-08-2003 00:30 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Soultaker schreef op 24 August 2003 @ 00:28:
[...]
The reason behind rounding half the values down and the other half up is to avoid the classical banking problem where ....
[...]
Dus toch het "Banking Rounding" probleem ...

[ Voor 31% gewijzigd door Verwijderd op 24-08-2003 00:35 ]


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Nu online
Verwijderd schreef op 24 August 2003 @ 00:35:
Dus toch het "Banking Rounding" probleem ...
Hmm, ik heb misschien verwarring gezaaid door dat stukje te quoten, want het is een heel ander probleem. Banking rounding is het naar boven of beneden afronden van een getal X,5 afhankelijk van of X even of oneven is (bijvoorbeeld: 1,5 en 2,5 worden allebei afgerond naar 2; 3,5 en 4,5 worden afgerond naar 4, enzovoorts). Dat werkt natuurlijk ook met een getal als X,Y5, maar dan kijk je natuurlijk naar Y, enzovoorts.

Die manier van afronden gaat er echter vanuit dat je weet dat je een getal X,5 hebt en round() (en misschien ook number_format, maar dat zou ik niet weten) rondt dan af volgens de bovengenoemde methode.

Het probleem is hier echter dat niet het getal X,5 gegeven is, maar het getal X,4999999; het is in dit geval duidelijk dat dat een afrondingsfout bij het vermenigvuldigen is (een gevolg van de inherente onnauwkeurigheid van IEEE floats), maar in principe is X,4999999 ook de exacte representatie van een getal. Round() rondt zo'n getal dan ook gewoon naar beneden af (behalve als je dus zelf iets forceert met de genoemde "fuzz factor").

[ Voor 7% gewijzigd door Soultaker op 24-08-2003 00:47 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik heb het probleem inmiddels maar opgelost door de cijferreeks 0.000000000001 op te tellen.

Afbeeldingslocatie: http://sepers.org/voorbeeld_nf2.gif

Deze minimale afwijking in de bedragen zal niemand merken aangezien het bij deze kwestie voornamelijk gaat om een correcte weergave op het scherm.
De 0,01 cent verschil viel echter wel op.

De afronding is zo wel goed, al denk ik dat mijn collega's het minder netjes vinden. Dit noemen ze dan "houtje touwtje programmeren".

Mochten jullie toch nog een nettere manier uitvinden, zou ik die graag willen leren.
Hierbij moet wel rekening worden gehouden met het feit dat ik liever de functie zoals afgebeeld wijzig. Het kost teveel tijd om zo'n 200 bestanden aan te passen.
Pagina: 1