Toon posts:

[C] Reeële getallen in twee integers

Pagina: 1
Acties:

Verwijderd

Topicstarter
Ik heb af en toe wat problemen met doubles (of floats? wat is nou het verschil?). Vandaar dat ik er aan dacht om een reeël getal (123,45) in een int te representeren. Nu heb ik twee dingen bedacht:

1. Met bitmasks werken, dus de eerste 16 bits geven het character (hele getal) aan en de laatste 16 bits de mantissa (achter de komma):

C:
1
2
3
#define REAL_CONS(c, m) (c<<16 | m)
#define REAL_CHAR(r) (r>>16)
#define REAL_MANT(r) (r & 0xffff)


2. Met miljoentallen werken. 25,36 wordt dan 25360000

C:
1
2
3
#define REAL_CONS(c, m) (c*1000000+m)
#define REAL_CHAR(r) (r/1000000)
#define REAL_MANT(r) (r%1000000)


Manier 1 is sneller, met manier 2 is het makkelijker rekenen. Het probleem is nu dat als je REAL_CONS(12,34) doet je niet 12340000 krijgt maar 12000034. Dat is natuurlijk niet de bedoeling. Hoe maak ik een functie dit dit oplost en alle getallen met 10, 100, 1000 etc. vermenigvuldigd tot het uit 6 cijfers bestaat?

  • ludo
  • Registratie: Oktober 2000
  • Laatst online: 01-03 18:17
Wat voor problemen heb je met doubles dat je een deel van een taal gaat omzeilen met allerlei vreemde trucs :? Een double is bedoeld om getallen als 123.45 in op te slaan, waarom zou je daar dan iets voor gaan gebruiken (integers) dat daar niet geschikt voor is?

Verwijderd

Topicstarter
Ik had problemen om floats in de database op te slaan. Als je stop_in_database("%f", 2.34) doet komt er 2.3399999 in te staan ofzo. Later kwam ik er achter dat je dit op kunt lossen door %.2f ofzo te gebruiken, maar het probleem uit de beginpost lijkt me nog steeds interessant.

  • Macros
  • Registratie: Februari 2000
  • Laatst online: 30-04 09:28

Macros

I'm watching...

Verschil tussen doubles en floats is dat doubles 2 keer zo precies zijn omdat ze 2 keer zoveel geheugen innemen. 8 tegen 4 bytes.
2.34 wordt 2.3399999 omdat ze intern met een binaire mantissa worden opgeslagen, dus sommige getallen kunnen alleen bij benadering worden weergegeven. Maar dat moet nooit een probleem zijn. Als je het getal later op het scherm print en je wilt van die extra 9'ens af of whatever, dan rond je het getal DAN pas om.
Ga aub niet lopen fucken met stomme coderings enzo.

"Beauty is the ultimate defence against complexity." David Gelernter


Verwijderd

Macros schreef op zondag 20 februari 2005 @ 20:42:
Maar dat moet nooit een probleem zijn.
...
Ga aub niet lopen fucken met stomme coderings enzo.
Alhoewel ik je als developer gelijk moet geven is het in de praktijk soms toch een probleem zijn hoor. Stel je een stuk software voor waar je in een tekstveld (text edit control ofzo) een bepaalde waarde (in je software opgeslagen in een double) weergeeft met laten we zeggen 2 cijfers na de komma. De 'domme' klant (als in 'geen developer', 'niet technisch onderlegd' etc) vult vrolijk 10,005 in, wat wordt opgeslagen als 10,004999... ofzo in je double. Dat hoeft inderdaad geen probleem te vormen natuurlijk want bij berekeningen wordt een 'correcte' waarde gebruikt. Echter, afgerond op twee decimalen wordt dat wel 10,00 en dan krijg je uiteraard gemekker van de 'domme' klant dat z'n ingevulde waarde niet correct wordt weergegeven in je UI. Want die klant verwacht namelijk 10,01 te zien...
En dan wens ik je uit ervaring veel succes om je klant uit te leggen dat je software toch correct werkt :/ Op zo'n moment moet je soms wel eens aan de kunstgrepen ;)

Verwijderd

Topicstarter
Op deze site staan nog wat voor- en nadelen van floats. Er staat ook een alternatief, een decimal datatype.

Ze lossen daar mijn probleem op door het aantal cijfers te tellen bij het converteren van een string naar een decimal. Dat is niet echt snel, maar waarschijnlijk de enige optie.

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 13-05 06:47
Het gebruik van fixed point numbers is niet heel vreemd, hoor. Voor bepaalde toepassingen is het best zinnig. Het voordeel van floating point getallen is dat ze een enorm bereik hebben: van 0,0000000000000000000001 tot 1.000.000.000.000.000.000, bij wijze van spreken (want in de praktijk nog veel verder). Voor veel toepassingen is dat gewoon niet nodig: in het voordeel van de bank bijvoorbeeld wil je misschien altijd in tienden van centen rekenen en minder is dan echt niet nodig. De enige meerwaarde van floating point numbers die dan overblijft is dat ze in de meeste talen standaard aanwezig zijn (maar in een taal als bijvoorbeeld C# is er ook een fixed point number type beschikbaar).

Het is het makkelijkst om te rekenen met een kleinere eenheid: als je een lengte in meters tot drie cijfers achter de komma nauwkeurig wil opslaan, kun je ook gewoon direct millimeters opslaan (in een integer dus) en je berekeningen daar op aanpassen. Dat is effectief manier 2.

Het enige voordeel van manier 1 is dat je heel snel de cijfers voor of na de komma kunt opvragen. De macro's lijken efficiënter, maar feitelijk is dat niet waar. Je berekeningen zijn namelijk veel ingewikkelder. Als je voor methode 2 twee getallen wil optellen, kun je gewoon (a+b) schrijven. In methode 1 moet je daarna echter compenseren voor overflow in de laagste 16 bits; dat is tenminste een extra regel code en ook een extra operatie.

Hoe vermenigvuldigen gaat hangt er een beetje vanaf of je een groter datatype beschikbaar hebt om in te overflowen. Is het antwoord ja, dan kun je voor methode 2 gewoon (a*b)/1000000000 (ofzoiets) doen. Anders moet je de vermenigvuldiging opdelen net zoals je voor methode 1 zou moeten doen.

Methode 2 is wat mij betreft dus minstens zo efficiënt als methode 1 en meestal overzichtelijker en eenvoudiger te programmeren. Ik zou dus zeker voor methode 2 gaan. Het 'probleem' dat je beschrijft bij methode 2 snap ik niet, want volgens mij heb je dat net zo goed bij methode 1; kun je dat nog toelichten?

(In het voordeel van methode 1 valt trouwens nog wel te vermelden dat het goed generaliseert naar willekeurige precisie, waarbij je een willekeurig (niet van te voren bepaald) aantal words gebruikt om je getallen in op te slaan.)

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Wat is er mis met een Currency? :?
Als je je decimalen (helaas wel maar tot maximaal 4) wil opslaan in je DB kun je ook een currency/money gebruiken, afhankelijk van je DBMS. Je hebt dan geen last meer van de "beperkingen" van floats/doubles maar dus wel maar tot maximaal 4 decimalen (onder MSSQL that is, andere DBMS-en misschien wel meer/minder)

[ Voor 34% gewijzigd door RobIII op 21-02-2005 01:02 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
"Ik heb wat problemen met doubles" is wel erg ruim omschreven. Ik neem aan dat je zit met het feit dat een getal als 2.34 niet exact in een double opgeslagen wordt?

Zo ja, dit is inherent aan het gebruik van floating point waarden. Het wordt veroorzaakt door het feit dat een double wordt opgeslagen als een macht van 2. Zo wordt 0.125 bijv. gecodeerd als 2-3. Nu is 0.34 niet uit te drukken in een gehele macht van 2 (0.34 is ong. 21.55639335), en dus gaan er een aantal decimalen verloren tijdens het opslaan. Als je het getal terug zou lezen krijg je dus 0.33999999...(etc.)

Als je weet hoeveel decimalen je achter de komma nodig hebt, kun je gebruik maken van fixed point math - het Currency datatype dat wordt gebruikt in databases doet dit volgens mij ook. Je kan getallen idd ook als een veelvoud van 10 opslaan. Stel dat je max. 4 decimalen achter de komma nodig hebt, dan sla je elk getal dus op in een int als x * 10000.

Optellen en aftrekken werkt dan gewoon normaal, alleen moet je oppassen met vermenigvuldigen en delen. Als je 2 getallen van dit datatype vermenigvuldigd reken je eigenlijk (x * 10000) * (y * 10000) uit, terwijl je (x * y * 10000) als uitkomst wil hebben. Na het vermenigvuldigen moet je dus nog delen door 10000. Voor delen geldt het omgekeerde, je uitkomst na het delen is dan nog een factor 10000 te klein.

Verwijderd

Zoek op 't net eens op BCD (binary coded decimals). Daar zijn een hoop C-libraries voor te vinden.
Maar de meeste compilers en databases ondersteunen BCD al 'uit de doos', dus misschien heb je geen extra library nodig... :)

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Verwijderd schreef op maandag 21 februari 2005 @ 11:17:
Zoek op 't net eens op BCD (binary coded decimals). Daar zijn een hoop C-libraries voor te vinden.
Maar de meeste compilers en databases ondersteunen BCD al 'uit de doos', dus misschien heb je geen extra library nodig... :)
Da's weer wat anders... 8)7

BCDs slaan een getal zodanig op, dat zijn hex-representatie het decimale getal afdrukt (!!!). M.a.w., als ik het getal 1234 op wil slaan, moet ik eigenlijk 0x1234 = 4660 opslaan. Het voordeel hiervan is dat elke nibble (16 bits) nu een enkele decimaal opslaat. En dat is ook het enige voordeel.

Ow, en er zijn ook assembly-instructies voor het werken met BCDs. Vandaar dat er dus ook compilers zijn die ermee kunnen werken.

Maar het lost je probleem met decimalen dus nog steeds niet op.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:41

.oisyn

Moderator Devschuur®

Demotivational Speaker

Macros schreef op zondag 20 februari 2005 @ 20:42:
Verschil tussen doubles en floats is dat doubles 2 keer zo precies zijn omdat ze 2 keer zoveel geheugen innemen. 8 tegen 4 bytes.
Niet helemaal, de preciesie wordt aangegeven door de grootte van de mantissa, niet de overige bits. De mantissa van een float is (effectief) 24 bits, terwijl die van een double 53 bits is. Een double is dus meer dan 2x zo precies als een float.
Verwijderd schreef op zondag 20 februari 2005 @ 23:34:
[...]

Echter, afgerond op twee decimalen wordt dat wel 10,00 en dan krijg je uiteraard gemekker van de 'domme' klant dat z'n ingevulde waarde niet correct wordt weergegeven in je UI. Want die klant verwacht namelijk 10,01 te zien...
Dat is een kwestie van goed afronden ipv truncaten :)
Soultaker schreef op maandag 21 februari 2005 @ 00:39:
Het enige voordeel van manier 1 is dat je heel snel de cijfers voor of na de komma kunt opvragen. De macro's lijken efficiënter, maar feitelijk is dat niet waar. Je berekeningen zijn namelijk veel ingewikkelder. Als je voor methode 2 twee getallen wil optellen, kun je gewoon (a+b) schrijven. In methode 1 moet je daarna echter compenseren voor overflow in de laagste 16 bits; dat is tenminste een extra regel code en ook een extra operatie.
:?
Waarom is manier 1 anders dan manier 2? Met manier 2 vermenigvuldig je met 1000000, met manier 1 met 65536. Het controleren voor overflow is dan ook onnodig, de bits zullen vanzelf overflowen in het gedeelte voor de komma.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Verwijderd

MrBucket schreef op maandag 21 februari 2005 @ 11:49:
Da's weer wat anders... 8)7

BCDs slaan een getal zodanig op, dat zijn hex-representatie het decimale getal afdrukt (!!!). M.a.w., als ik het getal 1234 op wil slaan, moet ik eigenlijk 0x1234 = 4660 opslaan. Het voordeel hiervan is dat elke nibble (16 bits) nu een enkele decimaal opslaat. En dat is ook het enige voordeel.
TS gaf aan dat 'ie problemen had met afrondingen (binary <-> 10-tallig), en met BCD ben je dat probleem kwijt. En dat is inderdaad het enige voordeel, maar wel verdraaid handig als je bv. een boekhoudpakket aan het ontwikkelen bent... :)

  • Onno
  • Registratie: Juni 1999
  • Niet online
.oisyn schreef op maandag 21 februari 2005 @ 11:56:
Dat is een kwestie van goed afronden ipv truncaten :)
Oh, jij vindt 10,004999 naar 10,01 afronden dus goed? Dan zou ik wel eens een implementatie van zo'n 'goed' afrondalgoritme willen zien. :)

  • MrBucket
  • Registratie: Juli 2003
  • Laatst online: 29-10-2022
Verwijderd schreef op maandag 21 februari 2005 @ 12:19:
[...]

TS gaf aan dat 'ie problemen had met afrondingen (binary <-> 10-tallig), en met BCD ben je dat probleem kwijt. En dat is inderdaad het enige voordeel, maar wel verdraaid handig als je bv. een boekhoudpakket aan het ontwikkelen bent... :)
Ja, maar een BCD op zich levert geen fixed point math op, daarvoor zul je imaginair een decimale punt moeten plaatsen in je BCD-getal (dwz: ik beschouw de laatste x decimalen als "achter de komma").

En dus zit je met dezelfde problemen met vermenigvuldigen en delen als bij het opslaan van x als x * 10000 (zie mijn eerdere post).

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:41

.oisyn

Moderator Devschuur®

Demotivational Speaker

Onno schreef op maandag 21 februari 2005 @ 12:27:
[...]

Oh, jij vindt 10,004999 naar 10,01 afronden dus goed? Dan zou ik wel eens een implementatie van zo'n 'goed' afrondalgoritme willen zien. :)
Ah, ik las dat 10.004999 afgerond werd naar 10.004 ipv 10.005, maar er stond een decimaal minder. Mijn fout :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 13-05 06:47
.oisyn schreef op maandag 21 februari 2005 @ 11:56:
:?
Waarom is manier 1 anders dan manier 2? Met manier 2 vermenigvuldig je met 1000000, met manier 1 met 65536. Het controleren voor overflow is dan ook onnodig, de bits zullen vanzelf overflowen in het gedeelte voor de komma.
Ik moet toegeven dat ik bij gebrek aan gedetailleerde informatie zelf wat aannames heb gedaan; ik ging er van uit dat het de bedoeling was om decimale getallen op te slaan en dat de TS dus bijvoorbeeld uitging van 4 getallen na de komma die in de helft van een integer opgeslagen worden. Het geldige bereik van die 16-bits gaat dus maar tot en met 9999 en niet 65535, dus werkt overflow ook niet zoals je zou willen (je moet de getallen tussendoor normaliseren).

Als je gewoon een fixed-point binair getal (met 16 bits voor en 16 bits na de komma) hebt dan werkt het inderdaad wel hetzelfde als methode 2.

Verwijderd

Topicstarter
Ik had op een gegeven moment een probleem in een kassa-systeem. Hierin hebben de klanten "potjes" die ze vooraf kunnen vullen en waarvan ze dan hun drankjes kunnen betalen. Op een gegeven moment werd er bij iemand de foutmelding gegeven: Deze persoon heeft onvoldoende saldo en moet EUR 0.00 betalen. Dit komt waarschijnlijk doordat hij minder dan 1 cent negatief staat en het had waarschijnlijk opgelost kunnen worden door goed te letten op afronding enzo.

Mijn database maakt echter al gebruik van decimal, numeric, money of currency (meer namen voor hetzelfde datatype). Zo'n datatype zit echter niet standaard in C, voor zover ik weet, vandaar dat ik zelf aan de slag ben gegaan.

Het probleem bij methode 1 is inderdaad de overflow: 12,60+12,60 wordt dan 12,120. Bij methode 2 sla je "micro-euros" op, ofwel miljoensten van getallen. Dat maakt rekenen makkelijk en mijn programma is toch niet echt performance-gebonden, dus ik denk dat ik daarvoor ga.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 02:41

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ah ik zie het idd, ik dacht even dat het standaard fixed point was, maar dat doet ie niet.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1