1

Onderwerp: Afrondings foutje... waarom ?

Voor een systeem moet ik bedragen in centen opgeven.
Tijdens controlle kwam ik een nogal cliche bug tegen

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv)
{
   char BEDRAG[]="16.52";
   char BTW[]="3.47";
   printf("%i\n", (int) (100.0 * (atof(BEDRAG) + atof(BTW))));
   printf("%i\n", (int) ((100.0*atof(BEDRAG) + 100.0*atof(BTW))));
   return 1;
}

Resultaat:
1998
1999

Ik weet wel over mantisa's en zo. maar echt begrijpen doe ik ehm toch niet.

(Ik heb al voor gesteld deze ene cent automatisch naar mijn bankrekening over te boeken lol  )

Pascal's Blobfree Homepage
Een dag geen NedLinux is een dag niet geleefd

Re: Afrondings foutje... waarom ?

De implementatiedetails ken ik niet.

Ik kan je wel vertellen dat je twee bedragen berekent die beide floating point zijn, via verschillende berekeningen. Omdat floats slechts bestaan als binaire approximaties van reëele getallen, zijn de twee berekende waarden niet exact gelijk. Met andere woorden, als printf zegt dat het getal 1999.00 is, is dat in het eerste geval iets als 1998.99999999999 en in het tweede getal minimaal 1999.0.

"Free and non-free software can co-exist the same way that free people and slaves can co-exist. But that's not a desirable state of affairs." -- RMS

3

Re: Afrondings foutje... waarom ?

Bastiaan,
Uiteraard is mij dit bekend
Ik begrijp je insteek en weet dat ik het daar ook moet zoeken.
Desondanks is je antwoord geen verklaring.
Ik hoop juist die te vinden zodat ik deze valkuil niet nog eens tegenkom.
(nog nog even iets vinden om dat verschil foutloos te bepalen en het bedrag automatisch over te boeken naar mijn geheime bankrekening in Moncaco)

Pascal's Blobfree Homepage
Een dag geen NedLinux is een dag niet geleefd

Re: Afrondings foutje... waarom ?

pascal schreef:

Ik hoop juist die te vinden zodat ik deze valkuil niet nog eens tegenkom.

In dit concrete geval is het vermijden van de valkuil niet zo moeilijk. Je kunt bijvoorbeeld echt afronden met round() in plaats van casten naar int. Of je kunt de opsomming van BEDRAG en BTW promoveren naar double.

Beter is om een library als libmpfr gebruiken in gevallen waar precisie belangrijk is.

"Free and non-free software can co-exist the same way that free people and slaves can co-exist. But that's not a desirable state of affairs." -- RMS

5

Re: Afrondings foutje... waarom ?

Juist om dit soort problemen te voorkomen registreer ik bedragen altijd als integers en in centen. In de opmaak maak ik er dan correcte bedragen van.

Old programmers never die: they just jump to a new address
Kubuntu 16.02 LTS, Compaq CQ61 met Linux Mint 18, een Galaxy Tab 10.1 met Android Ice Cream en een Raspberry PI met raspian
http://www.volkerskrant.nl

6

Re: Afrondings foutje... waarom ?

Jovo, wijs idee al weet ik dat er programeertalen zijn waar juist met dit probleem rekening gehouden wordt.
Bijvoorbeeld door fixed point variabelen te gebruiken (waarbij meestal wel intern met drie ipv twee decimalen gerekend wordt, geheel volgens de traditie van de meettechniek.
Goed leuk allemaal.. echter
- ik zit nu eenmaal aan dit systeem verbonden.
- Ik heb nog steeds geen eenduidig antwoord op mijn vraag.
Ik ga het zelf iig even uitzoeken... zodra ik er tijd voor heb.... wink

Pascal's Blobfree Homepage
Een dag geen NedLinux is een dag niet geleefd

7

Re: Afrondings foutje... waarom ?

De code beetje uitgebreid:

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv)
{
   char BEDRAG[]="16.52";
   char BTW[]="3.47";
   
   float bedrag = atof(BEDRAG);
   float btw = atof(BTW);
   float sum = bedrag + btw;
   
   printf ("bedrag = %f\n", bedrag);
   printf ("btw = %f\n", btw);
   
   printf ("sum = %f\n", (sum));
   printf ("100.0 * sum = %f\n", (100.0 * sum));
   
   printf("%i\n", (int) (100.0 * (atof(BEDRAG) + atof(BTW))));
   printf("%i\n", (int) ((100.0*atof(BEDRAG) + 100.0*atof(BTW))));
   return 1;
}
bedrag = 16.520000
btw = 3.470000
sum = 19.990000
100.0 * sum = 1998.999977
1998
1999

Precies wat Bastiaan zegt dus (uiteraard), alleen op een andere plek dan waar je het op 't eerste gezicht zou verwachten.

8

Re: Afrondings foutje... waarom ?

't blijft verwarrend. ik had desondanks bij beide berekeningen het zelfde verwacht.

Pascal's Blobfree Homepage
Een dag geen NedLinux is een dag niet geleefd

9

Re: Afrondings foutje... waarom ?

Ah, reals en C. Altijd lachen en kopzorgen.

Eigenlijk zijn er drie opties om goed met reals te werken op een pc.
1/ Gebruik een mathematische programma zoals Matlab, Maple e.d.
2/ Gebruik een moderne managed taal zoals C#, Java e.d. (maar dat zie ik je niet snel doen wink )
3/ Denk als een computer en niet als een mens of wiskundige.

Optie 3
Ook al gaat er een real in en een real uit. Intern kun je beste omzetten naar integers en weer terugzetten. Sterker soms moet het resultaat weggeschreven worden op scherm of file, dan kan omzetten naar string ook.
Werk met zo min mogelijk formules. Kijk wat je in tabellen kan stoppen met voorgekauwde waarden. Stel dat a 1,2,3,4 kan hebben en input b 2,3,4,5 dan zou je de resultaaten in een list of 2d-array stoppen.

Of je moet je heel erg gaan verdiepen in de IEEE standaard van de floating point.

Met embedded, waar het echt heel leuk is met reals, heb ik altijd tables gebruikt en gehele getallen. Liever 10 klokslagen kwijt met 100% zekerheid dan aan omzetten dan onbekende nauwkeurigheid. Je ziet met dit kleine voorbeeld dat je goed moet stresstesten om alle mogelijkheden eruit te vissen.

P.s. wist je dat je op en 64 bits een andere FPU-chip hebt dan een 32-bits. Wederom kans op andere uitkomst?
Zoals ik zei, C en floating points is lachen. wink

IF not THEN toch

10

Re: Afrondings foutje... waarom ?

Ah, reals en C. Altijd lachen en kopzorgen.

Eigenlijk zijn er drie opties om goed met reals te werken op een pc.
1/ Gebruik een mathematische programma zoals Matlab, Maple e.d.
2/ Gebruik een moderne managed taal zoals C#, Java e.d. (maar dat zie ik je niet snel doen wink )
3/ Denk als een computer en niet als een mens of wiskundige.

Optie 3
Ook al gaat er een real in en een real uit. Intern kun je beste omzetten naar integers en weer terugzetten. Sterker soms moet het resultaat weggeschreven worden op scherm of file, dan kan omzetten naar string ook.
Werk met zo min mogelijk formules. Kijk wat je in tabellen kan stoppen met voorgekauwde waarden. Stel dat a 1,2,3,4 kan hebben en input b 2,3,4,5 dan zou je de resultaaten in een list of 2d-array stoppen.

Of je moet je heel erg gaan verdiepen in de IEEE standaard van de floating point.

Met embedded, waar het echt heel leuk is met reals, heb ik altijd tables gebruikt en gehele getallen. Liever 10 klokslagen kwijt met 100% zekerheid dan aan omzetten dan onbekende nauwkeurigheid. Je ziet met dit kleine voorbeeld dat je goed moet stresstesten om alle mogelijkheden eruit te vissen.

P.s. wist je dat je op en 64 bits een andere FPU-chip hebt dan een 32-bits. Wederom kans op andere uitkomst?
Zoals ik zei, C en floating points is lachen. wink

IF not THEN toch

11

Re: Afrondings foutje... waarom ?

Ah, reals en C. Altijd lachen en kopzorgen.

Eigenlijk zijn er drie opties om goed met reals te werken op een pc.
1/ Gebruik een mathematische programma zoals Matlab, Maple e.d.
2/ Gebruik een moderne managed taal zoals C#, Java e.d. (maar dat zie ik je niet snel doen wink )
3/ Denk als een computer en niet als een mens of wiskundige.

Optie 3
Ook al gaat er een real in en een real uit. Intern kun je beste omzetten naar integers en weer terugzetten. Sterker soms moet het resultaat weggeschreven worden op scherm of file, dan kan omzetten naar string ook.
Werk met zo min mogelijk formules. Kijk wat je in tabellen kan stoppen met voorgekauwde waarden. Stel dat a 1,2,3,4 kan hebben en input b 2,3,4,5 dan zou je de resultaaten in een list of 2d-array stoppen.

Of je moet je heel erg gaan verdiepen in de IEEE standaard van de floating point.

Met embedded, waar het echt heel leuk is met reals, heb ik altijd tables gebruikt en gehele getallen. Liever 10 klokslagen kwijt met 100% zekerheid dan aan omzetten dan onbekende nauwkeurigheid. Je ziet met dit kleine voorbeeld dat je goed moet stresstesten om alle mogelijkheden eruit te vissen.

P.s. wist je dat je op en 64 bits een andere FPU-chip hebt dan een 32-bits. Wederom kans op andere uitkomst?
Zoals ik zei, C en floating points is lachen. wink

IF not THEN toch

12

Re: Afrondings foutje... waarom ?

Dat is dan ook weer een beetje overdreven.

Er zijn twee opties:

a) Snelle uitvoering
b) Nauwkeurigheid.

Je moet kiezen.

"Free and non-free software can co-exist the same way that free people and slaves can co-exist. But that's not a desirable state of affairs." -- RMS

13

Re: Afrondings foutje... waarom ?

Joris, en computers... dat is pas lachen.
De software waar ik het over heb, draait op een HP-UX server
Ik heb het eigenlijk niet op mijn eigen systeem geprobeerd.
dwz wel het voorbeeldje dat ik gepost heb maar niet de code waar het uiteindelijk om gaat.

Ik denk overigens dat ik rustig kan beweren dat mijn technische achtergrond voldoende zou moeten zijn om het probleem te herkennen en onderkennen.
Reals is trouwens meer een term uit de PASCAL wereld, de rest noemt het bij mijn weten floating point variabelen (in C floats voor de eventuele noob die dit leest)

Er is nog een duidelijke reden waarom ik het niet met je eens ben Joris.
Uit mijn voorbeeld blijkt maar al te duidelijk dat het om financiele berekeningen gaat.
De beste oplossing is dus een fixed point variabele.
Nu zijn er atof en atoi funties maar geen a_to_fixed en evenmin zijn er fixed point variabelen in C.
Allemaal te implementeren maar ik moet de rommel wel porteerbaar houden.
Aldus is de oplossing van Jovo denk ik het beste en het makkelijkste te implementeren
Immers net als bij DSP technieken kun je ook bij financiele programma's integers gebruiken om fixed point variabelen in op te slaan.

@Bart, ik had het zelf kunnen bedenken, desondanks is je voorbeeld verhelderend.

Pascal's Blobfree Homepage
Een dag geen NedLinux is een dag niet geleefd

14

Re: Afrondings foutje... waarom ?

pascal schreef:

Joris, en computers... dat is pas lachen.
De software waar ik het over heb, draait op een HP-UX server
Ik heb het eigenlijk niet op mijn eigen systeem geprobeerd.
dwz wel het voorbeeldje dat ik gepost heb maar niet de code waar het uiteindelijk om gaat.

Ik denk overigens dat ik rustig kan beweren dat mijn technische achtergrond voldoende zou moeten zijn om het probleem te herkennen en onderkennen.
Reals is trouwens meer een term uit de PASCAL wereld, de rest noemt het bij mijn weten floating point variabelen (in C floats voor de eventuele noob die dit leest)

Real of float is een kwestie van dialect. Floating point is een type in de floats (of reals). Beide termen worden door elkaar gebruikt in verschillende oplossingen.

Ik gebruik liever real omdat je niet verwarring hebt dat floating point uit de groep float komt.
Net zoals int uit de groep integers komt.

Er is nog een duidelijke reden waarom ik het niet met je eens ben Joris.
Uit mijn voorbeeld blijkt maar al te duidelijk dat het om financiele berekeningen gaat.
De beste oplossing is dus een fixed point variabele.
Nu zijn er atof en atoi funties maar geen a_to_fixed en evenmin zijn er fixed point variabelen in C.
Allemaal te implementeren maar ik moet de rommel wel porteerbaar houden.
Aldus is de oplossing van Jovo denk ik het beste en het makkelijkste te implementeren
Immers net als bij DSP technieken kun je ook bij financiele programma's integers gebruiken om fixed point variabelen in op te slaan.

Met als prijs dat je niet voor 100% betrouwbaarheid gaat, maar voor human-readable.
Ik zou het lekker in centen houden waardoor je in gehele waarden blijft. Kost minder cpu, minder kans op fouten en geen geklooi met onnauwkeurigheid van de floating point die echt per CPU-type kan verschillen.

Tenzij je echt naar Java of .NET gaat. Die hebben deze onnauwkeurigheden opgelost door een betere implementatie. Onderwater worden o.a. registers gekoppeld ipv direct leunen op de FPU of de soft-FPU.

IF not THEN toch

15

Re: Afrondings foutje... waarom ?

Tja Joris,
Wat je opmerking over 100% betrouwbaarheid hebben wij ofwel een hickup in  communicatie ofwel een meningsverschil.
Echter je kaart Java en dotNet aan...
Welnu over java wil ik het niet eens hebben...ik werk in een omgeving waar een zeer hoge mate van betrouwbaarheid geeist wordt.
Wat dotNet betreft, geld eigenlijk ook hier die opmerking  de enige goede dotNet implementatie is die van MicroSoft en helaas moet ik vaststellen dat daar nogal eens wat mist gaat in de uitvoering. (dit dus nog even los van wat ik van de kwaliteit en performance van MicroSoft als platform vind)

Er worden ook hier wel eens wat balletjes opgegooid en (vaak kostbare) experimenten gestart.
Voorlopig blijkt echter dat al die hippe ontwikkelingen geen partij zijn voor twintig jaar ervaring en dus even zo oude legacy.
Bovendien draait MicroSoft op slechts een platform, en dat is tot nog toe kwa performance te zwak gebleken.

Pascal's Blobfree Homepage
Een dag geen NedLinux is een dag niet geleefd

16 Laatst bewerkt door Joris (31 Jul 2014 19:30:42)

Re: Afrondings foutje... waarom ?

pascal schreef:

Tja Joris,
Wat je opmerking over 100% betrouwbaarheid hebben wij ofwel een hickup in  communicatie ofwel een meningsverschil.
Echter je kaart Java en dotNet aan...
Welnu over java wil ik het niet eens hebben...ik werk in een omgeving waar een zeer hoge mate van betrouwbaarheid geeist wordt.
Wat dotNet betreft, geld eigenlijk ook hier die opmerking  de enige goede dotNet implementatie is die van MicroSoft en helaas moet ik vaststellen dat daar nogal eens wat mist gaat in de uitvoering. (dit dus nog even los van wat ik van de kwaliteit en performance van MicroSoft als platform vind)

Managed talen hebben het voordeel dat ze nauwkeuriger omgaan met floats dan C.
C heeft als voordeel dat het gemaakt is om dicht bij de hardware te zitten. Nadeel is dat variabelen die niet op de hardware zitten of per hardware anders zijn geïmplementeerd hun beperkingen heeft. Floats zijn daarom wispelteurig in C. Dat zie je in je voorbeeld bevestigd worden.

.NET en Java zitten niet dicht op de hardware en hebben hier een goede oplossing kunnen bedenken. Maar ook hier zitten afwijkingen in. Het beste resultaat krijg je met speciale software. Matlab-library's gebruiken. Of in integers blijven werken. Bouw de formule om voor gehele register-waarden.

Er worden ook hier wel eens wat balletjes opgegooid en (vaak kostbare) experimenten gestart.
Voorlopig blijkt echter dat al die hippe ontwikkelingen geen partij zijn voor twintig jaar ervaring en dus even zo oude legacy.
Bovendien draait MicroSoft op slechts een platform, en dat is tot nog toe kwa performance te zwak gebleken.

.NET draait helaas alleen op Windows. Toch is C# een hele fijne taal. De ontwikkelaar van Delphi heeft meegeholpen. En dat merk je best.

IF not THEN toch