PHP Math bug?

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Lasse

Lasse

30/07/2008 23:04:00
Quote Anchor link
Hallo,
Ik was bezig met een script tot ik steeds allerlei rare excepties terug kreeg die mijn script zelf had gegooid, omdat ik wilde voorkomen dat mijn script een getal groter dan 100 doorkreeg (om wat voor reden dan ook, niet interessant).

Het getal dat ik in het script gooide was echter niet groter dan 100, maar exact 100. Het getal is zo berekend:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
var = 100 / 9
getal = 0
getal += var
getal += var
getal += var
getal += var
getal += var
getal += var
getal += var
getal += var
getal += var
Oftewel: (100 / 9) * 9. Reken zelf maar uit, dit geeft echt honderd (misschien geeft het 99.9999... op slechte rekenmachines). Ook in php zou deze berekening exact 100 geven, als je het met (100 / 9) * 9 doet. Als je 9 keer plus doet lijkt hij die in eerste instantie ook te geven (als je echo $getal doet komt er 100 op het scherm te staan). Als je nu echter deze vergelijking maakt: getal > 100, krijg je een 1 terug. Zeer tegenstrijdig dus.

Nu wil ik vragen: Zie ik iets over het hoofd, doe ik iets fout? En zo niet, kunnen jullie het probleem ook eens testen op jullie php-bak, om te kijken of het op iedere versie voorkomt? Hier is een snippet om het probleem te testen:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$number
= 9;
echo 'Het getal = ' . $number . '. Het getal 100 wordt nu gedeeld door $number<br />';
$numberDivided = 100 / 9;
echo 'Het getal is gedeeld, en daar is deze uitkomst uitgekomen: ' . $numberDivided . '.<br />';
$numberProductByStar = $numberDivided * 9;
echo 'Het gedeelde getal is nu weer keer $number gedaan d.m.v. de *-operator. Uitkomst: ' . $numberProductByStar . '.<br />';
$numberProductByKruis = 0;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
echo 'Het gedeelde getal is nu weer keer $number gedaan door er 9 keer $numberDivided bij op te tellen. Uitkomst: ' . $numberProductByKruis . '.<br />';
echo 'Kijk of $numberProductByStar groter is dan 100: ';
echo $numberProductByStar > 100 ? 'Ja' : 'Nee';
echo '.<br />';
echo 'Kijk of $numberProductByKruis groter is dan 100: ';
echo $numberProductByKruis > 100 ? 'Ja' : 'Nee';
?>
Hier zou dit uit moeten komen (als de bug er niet is):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
Het getal = 9. Het getal 100 wordt nu gedeeld door $number
Het getal is gedeeld, en daar is deze uitkomst uitgekomen: 11.111111111111.
Het gedeelde getal is nu weer keer $number gedaan d.m.v. de *-operator. Uitkomst: 100.
Het gedeelde getal is nu weer keer $number gedaan door er 9 keer $numberDivided bij op te tellen. Uitkomst: 100.
Kijk of $numberProductByStar groter is dan 100: Nee.
Kijk of $numberProductByKruis groter is dan 100: Nee

Als de bug er wel is dan moet de laatste nee Ja worden.

Als dit echt een bug blijkt te zijn zal ik hem submitten op de bug-track van php.net. Bedankt voor jullie hulp alvast.
Gewijzigd op 01/01/1970 01:00:00 door Lasse
 
PHP hulp

PHP hulp

19/04/2024 22:15:36
 
Robert Deiman

Robert Deiman

30/07/2008 23:17:00
Quote Anchor link
Het is geen bug!!

100/9 is 11,1111111111111111111111111111 enzovoorts

Dat x negen (ik rond nu op meer decimalen af dan dat PHP dat doet) kom je op:
99,999999999999999999999999999999

Dat is uiteraard niet gelijk aan 100. Een rekenmachine rond vaak af en geeft dus inderdaad terug dat het 100 is (vanwege die afronding, als je de laatste 9 in het rijtje naar boven afrond, dan moet je bij alle negens 1 optellen, dan kom je op 100. Het principe van afronden)
Maar omdat een rekenmachine en PHP zelf ook al tussentijds aan afronden doet, kom je inderdaad niet op 100 uit, maar op die 99,999....

Hij rondt die echter zelf ook weer af en geeft 100 terug. -> Maar de eigenlijke waarde van die berekening (die 99,999...) houdt die wel vast. En 99,999.... is ongelijk aan 100. Vandaar dus, voor de weergave rondt die af.
 
- SanThe -

- SanThe -

30/07/2008 23:19:00
Quote Anchor link
Nee, dat komt waarschijnlijk door het steeds afronden na de berekening. En 9 minuscule afrondingen kunnen er net voor zorgen dat ie net boven de 100 komt.
 
Lasse

Lasse

30/07/2008 23:32:00
Quote Anchor link
@Robert Deiman: Dat klopt niet, want jij zegt nu dus dat het waarschijnlijk net even onder de 100 ligt. Zou kunnen, maar lijkt mij onwaarschijnlijk (de uitkomst is nu eenmaal 100 (als je hoofdrekent), en een goede rekenmachine geeft dat ook, en ik neem aan dat php een goede rekenmachine is :P).

Maar zelfs al is het zo, het gaat mij om een vergelijking of het getal boven de honderd komt, en niet onder...

@Santhe: Voorekenen (steeds afronden):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
0 + 11.111111 = 11.111111 (afronden naar beneden)
11.111111 + 11.111111 = 22.222222 (afronden naar beneden)
22.222222 + 11.111111 = 33.333333 (afronden naar beneden)
33.333333 + 11.111111 = 44.444444 (afronden naar beneden)
44.444444 + 11.111111 = 55.555556 (afronden naar boven)
55.555556 + 11.111111 = 66.666667 (afronden naar boven niet nodig, want de laatst mogelijke decimaal is al 7)
66.666667 + 11.111111 = 77.777778 (afronden naar boven niet nodig, want de laatst mogelijke decimaal is al 8)
77.777778 + 11.111111 = 88.888889 (afronden naar boven niet nodig, want de laatst mogelijke decimaal is al 9)
88.888889 + 11.111111 = 100 (de laatste negen wordt nul, waardoor de een na laatste 8, 9 wordt + nog 1 die er bij op moet maakt 0, en zo wordt het honderd.

Dit zou dus ook exact honderd moeten maken.
 
Orhan T

Orhan T

30/07/2008 23:34:00
Quote Anchor link
Ik snap jou niet, 100 is niet groter dan 100. Je zou de vergelijking >= moeten maken.
 
Lasse

Lasse

30/07/2008 23:37:00
Quote Anchor link
@Orhan: Klopt, 100 is niet groter dan 100. En daarom vindt ik het raar dat PHP bij de door mij in de beginpost genoemde manier aangeeft dat 100 wel groter is dan 100 (oftewel, de ene 100 is net iets groter dan de andere 100).
 
Orhan T

Orhan T

30/07/2008 23:39:00
Quote Anchor link
@Lasse,

Waar zie jij dat staan?
Kijk of $numberProductByStar groter is dan 100: Nee.
Kijk of $numberProductByKruis groter is dan 100: Nee
 
Lasse

Lasse

30/07/2008 23:43:00
Quote Anchor link
@Orhan: Dat zie ik nergens staan. Als ik het script uitvoer krijg ik dit te zien:

Kijk of $numberProductByStar groter is dan 100: Nee.
Kijk of $numberProductByKruis groter is dan 100: Ja

De tekst die jij geeft zou je moeten te zien krijgen als de bug er niet is...
 

30/07/2008 23:45:00
Quote Anchor link
Hmm... Dit lijkt mij als 0,999..., wat gewoon 1 is, alleen met wat nulletjes meer. Bewijsstuk 1, Bewijsstuk 2.

Orhan daar gaat het nu niet om. Php beweerd dat het getal groter is dan honderd, dit is dus niet zo.

Mjin test:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
Het getal = 9. Het getal 100 wordt nu gedeeld door $number
Het getal is gedeeld, en daar is deze uitkomst uitgekomen: 11.1111111111.
Het gedeelde getal is nu weer keer $number gedaan d.m.v. de *-operator. Uitkomst: 100.
Het gedeelde getal is nu weer keer $number gedaan door er 9 keer $numberDivided bij op te tellen. Uitkomst: 100.
Kijk of $numberProductByStar groter is dan 100: Nee.
Kijk of $numberProductByKruis groter is dan 100: Ja
Gewijzigd op 01/01/1970 01:00:00 door
 
Orhan T

Orhan T

30/07/2008 23:47:00
Quote Anchor link
Ik begreep het verkeerd , de tekst die ik gaf zag ik onderaan je post vandaar dat ik dacht dat dat goed ging. Speed reading is niet altijd goed zo te zien. ;)
 
Robert Deiman

Robert Deiman

30/07/2008 23:48:00
Quote Anchor link
edit:
Foutje, ik las niet goed.


Ik heb het getest en inderdaad, hij geeft dat die 100 wordt, maar zegt wel dat die groter is dan 100, dat zou niet mogen. Er zijn 2 opties, of 99.9999 (kleiner dan 100) of precies gelijk aan 100.

Wat ik wel zei en wat blijkt te kloppen is dat die alleen de output afrond, en niet tussendoor.
Gewijzigd op 01/01/1970 01:00:00 door Robert Deiman
 

30/07/2008 23:51:00
Quote Anchor link
Robert, php zou dus niet moeten aangeven dat 100 groter is dan 100, maar eerder kleiner dan 100?

Verder klopt de berekening wel, want als je 10 deelt door 5 krijg je 2, als je dat weer keer 5 doet heb je weer 10.
Gewijzigd op 01/01/1970 01:00:00 door
 
Lasse

Lasse

30/07/2008 23:55:00
Quote Anchor link
Edit:
Jullie waren mij allebei voor.


@Karl: Correct:D En bedankt voor je test.
@Robert: Gedeeltelijk mee eens:
Als je uitgaat van een oneindig aantal 1-en zul je ook een oneindig aantal 9ens krijgen, en kijkend naar de bewijstukken van Karl is dat 100. Dat php niet afrond zou je gelijk in kunnen hebben, maar aan de andere kant: Waarom geeft hij dan niet 99.999... met zijn aantal decimalen terug in plaats van 100?

Maar dat maakt alsnog niet uit, want zoals ik al zij: Mijn vergelijking was dat het getal groter was dan 100 en niet kleiner. Het maakt dus niet uit of het 99.9999... is of 100. In dat geval zou ik altijd een boolean 0 terug krijgen.
Gewijzigd op 01/01/1970 01:00:00 door Lasse
 
Robert Deiman

Robert Deiman

31/07/2008 00:00:00
Quote Anchor link
@Lasse

Het heeft waarschijnlijk wel iets met de decimalen te maken. Als je round gebruikt (maakt niet uit hoeveel decimalen) gaat het wel goed.
 
Lasse

Lasse

31/07/2008 00:06:00
Quote Anchor link
@Robert: Klopt ja, ik had het al geprobeerd met de BC Math extensie van PHP, en die doet het wel goed. En round zal neem ik aan ook wel werken. Punt is echter dat het wel een bug is, en dat het beter wel kan worden gerapporteerd, zodat de volgende die zoiets als ik doe probeert niet eerst uren naar het probleem hoeft te zoeken...

Edit:
Bedankt voor jullie reacties. Ik zal het morgen rapporteren. Mocht er nog iemand kunnen bewijzen dat het geen bug is, voel je geroepen...
Gewijzigd op 01/01/1970 01:00:00 door Lasse
 

31/07/2008 00:07:00
Quote Anchor link
Het klopt iig niet. Ik weet niet waar php over struikeld, maar als ik bccomp probeer met wat voor scale dan ook, ik krijg 0 terug, wat betekend:
Quote:
Returns 0 if the two operands are equal

:-)
Idd bug dus. Kijk wel effe of d'r al niet iets staat. Graag hoor ik er iig nog meer over :-).

Edit:
Waarschijnlijk toch ergens over een decimaal, want als ik:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php
if(((100/9)*9)>100) {
echo "Jah";
}
else {
echo "Nee";
}

?>

doe, krijg ik nee...
Gewijzigd op 01/01/1970 01:00:00 door
 
Lasse

Lasse

31/07/2008 00:13:00
Quote Anchor link
@Karl: Ja het is heel raar. Ik zal dit topic weer omhoog halen als ik meer weet.
 
Lasse

Lasse

31/07/2008 13:32:00
Quote Anchor link
Update: Dit blijkt geen bug te zijn!!
Na wat in de bug database te hebben gezocht, en wat onderzoek te hebben gedaan blijkt dit geen bug te zijn.

Dit staat in de php manual onder Floating point numbers:
Quote:
Warning
Floating point precision

It is typical that simple decimal fractions like 0.1 or 0.7 cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9.

This is due to the fact that it is impossible to express some fractions in decimal notation with a finite number of digits. For instance, 1/3 in decimal form becomes 0.3.

So never trust floating number results to the last digit, and never compare floating point numbers for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.
De reden hiervan is dus dat floats in php (en alle andere scripting/programeertalen) in het binaire
IEEE 745 worden opgeslagen. Dit wil zeggen dat de afrondingsmanieren anders zijn, en bovendien sommige getallen die decimaal een einde hebben, binair oneindig zijn.
De comments op de php manual link die ik gaf zijn trouwens ook interessant leesvoer.

Bewijs dat dit klopt: Voer dit script uit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
$number
= 9;
echo 'Het getal = ' . $number . '. Het getal 100 wordt nu gedeeld door $number<br />';
$numberDivided = 100 / 9;
echo 'Het getal is gedeeld, en daar is deze uitkomst uitgekomen: ' . $numberDivided . '.<br />';
$numberProductByStar = $numberDivided * 9;
echo 'Het gedeelde getal is nu weer keer $number gedaan d.m.v. de *-operator. Uitkomst: ';
printf("%.40f", $numberProductByStar);
echo '.<br />';
$numberProductByKruis = 0;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
$numberProductByKruis += $numberDivided;
echo 'Het gedeelde getal is nu weer keer $number gedaan door er 9 keer $numberDivided bij op te tellen. Uitkomst: ';
printf("%.40f", $numberProductByKruis);
echo '.<br />';
echo 'Kijk of $numberProductByStar groter is dan 100: ';
echo $numberProductByStar > 100 ? 'Ja' : 'Nee';
echo '.<br />';
echo 'Kijk of $numberProductByKruis groter is dan 100: ';
echo $numberProductByKruis > 100 ? 'Ja' : 'Nee';
?>
De uitkomst zal zijn:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
Het getal = 9. Het getal 100 wordt nu gedeeld door $number
Het getal is gedeeld, en daar is deze uitkomst uitgekomen: 11.111111111111.
Het gedeelde getal is nu weer keer $number gedaan d.m.v. de *-operator. Uitkomst: 100.0000000000000000000000000000000000000000.
Het gedeelde getal is nu weer keer $number gedaan door er 9 keer $numberDivided bij op te tellen. Uitkomst: 100.0000000000000142108547152020037174224854.
Kijk of $numberProductByStar groter is dan 100: Nee.
Kijk of $numberProductByKruis groter is dan 100: Ja
Als je dus wilt vergelijken met floats zul je de boel dus moeten afronden op een voor jou interessant aantal decimalen.

Edit:
En nog deze Decimaal float naar Binair float omzetter, om het zelf te proberen

Niet Bumpen::
Twee of meer keer achter elkaar in een topic posten heet bumpen. Bumpen is pas na 24 uur toegestaan en kan een reden zijn voor de admins en moderators om een topic te sluiten. Gebruik indien nodig de Afbeelding knop om je tekst aan te passen.

SanThe.
Gewijzigd op 01/01/1970 01:00:00 door Lasse
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.