Float en DWord beiden 32bits??


Op 27 maart 2019 22:41:15 schreef Hensz:
Je kunt van alles opslaan in 32 bits, tekst bijv., of cijfers per 4 bits één.
Als je maar goed in de gaten houdt wát je hóe opslaat.

Zeg eens eerlijk... Jij hebt alleen de titel gelezen he..? :P

Bij een float sla je 3 dingen op: het sign bit (positief of negatief getal), een integer (de mantissa heet dat in een float), en een exponent. Samen kun je daarmee hele grote en hele kleine getallen schrijven, maar met beperkte nauwkeurigheid, want je heb nog steeds maar 32 bits (in dit voorbeeld, er bestaan ook floats met andere formaten, 32 en 64 bit zijn het meest gangbaar.

Laten we even een voorbeeld doen in het decimale stelsel. Als je 6 cijfers mag gebruiken, kun je gewoonlijk een getal tot 999999 weergeven. Echter, als je 2 van die cijfers mag gebruiken als exponent, kan dat 9999*10^99 worden (9999E99 in wetenschappelijke notatie). Deze methode heeft wel een prijs. Als je 100000 wilt schrijven, wordt dat 1000*10^2, maar daarmee is de stapgrootte dus 10^2 ofwel 100 geworden. Dat betekend dat het eerstvolgende grotere getal dat je kunt schrijven 100100 oftewel 1001*10^2 is. Naarmate de exponent groter wordt, neemt de nauwkeurigheid dus af.

Vaak is dit geen probleem; de verschuiving van continenten wordt gemeten in centimeters per jaar, en dan is een paar millimeter meer of minder dus wel belangrijk, maar je gaat je snelheid op de snelweg niet weergeven in millimeters per uur.

Echter, dit geeft wel problemen als je een getal herhaaldelijk bij een groot getal probeert op te tellen. In dat voorbeeld zou 100 keer het getal waarmee je begon, 100000, ophogen met 25 een resultaat geven van... 100000, in plaats van de verwachtte 102500, omdat elke keer die 25 die je erbij optelt, weg valt in de afronding, terwijl het getal 1025*10^2 prima opgeslagen kan worden.

Overigens heeft "Frits" het m.i. fout, je kunt 2147483646,999 helemaal niet opslaan in een 32 bit float, dat wordt afgerond naar 2147483648, en het eerstvolgende grotere getal is 2147483904. De stapgrootte is dus 256, en je kunt nog veel grotere getallen opslaan.

Edit: JoWi was sneller, verhaal komt op hetzelfde neer.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken
240diesel

Golden Member

Wat een goede info!
Daar ben ik erg blij mee, het werd al steeds duidelijker naarmate er meer antwoorden kwamen maar met Sparky's uitleg viel het kwartje helemaal op z'n plaats.

Iedereen heel hartelijk bedankt voor de genomen moeite :-)

Groet, Jan

Hensz

Golden Member

Op 27 maart 2019 22:55:03 schreef BVZ:
[...]
Zeg eens eerlijk... Jij hebt alleen de titel gelezen he..? :P

Leuk geprobeerd, maar ik lees praktisch altijd het hele draadje. Nu dus ook. Ik dacht dat ik er maar eens een andere draai aan moest geven, want wat Diesel nou precies niet snapte was ook niet echt helder en dan helpt een een flinke duw wel eens om tóch die wissel om te krijgen en zo de hersenen van TS uit de spagaat te halen.

Don't Panic!

Natuurlijk, maar de uitleg op Wikipedia is niet bijzonder begrijpelijk en duikt gelijk in de technische details en wiskunde, zonder eerst *uit te leggen* hoe het werkt.

Een manager is iemand die denkt dat negen vrouwen in één maand een kind kunnen maken

Op 27 maart 2019 21:53:11 schreef 240diesel:
...
float = drijvend, drijvende komma), die neemt ook 32 bits in (-2147483647,999 t/m 2147483646,999)."
...

Dat is wel grandioos verwarrend ja! Bij float (floating point numbers) zou ik nooit blijven vasthouden aan het concept van getallen zoals bij integers (byte, word, dword) het geval is.
De door IEEE gestandaardiseerde floating point getallen (waarvan ik redelijkerwijs aanneem dat dat ook de floating point getallen zijn die in jouw taal gebruikt worden) zijn in dit wikipedia artikel beschreven zijn als volgt beschreven
±n*10m ±n*2m
waar n is in (0..16777215) en m is in (-126..127).
Let erop dat hoe verder je getal van 0 af is, hoe minder precisie je hebt. Waar bijvoorbeeld 1 en 2 beschreven kunnen worden, is er "geen" verschil tussen 1-triljard en 1-triljard-en-1. Hierdoor kunnen er vreemde situaties ontstaan, bijvoorbeeld dat (√2)2≠2.

Edit: @rew foutje! het is inderdaad een tweemacht, geen tienmacht.

Meep! Meep!

De range van een 32 bit floating point (in het IEEE-754 formaat!) is

-3.40282 x 10^38 tot +3.40282 x 10^38

240diesel

Golden Member

Ik ben blij te lezen dat het dus niet een vraag naar de bekende weg was, als jullie het al verwarrend vinden hoef ik mij niet te schamen.

Soms denk ik wel eens "had ik maar beter opgelet met wiskunde" , maar ja sommige leraren leken er wel op getraind te zijn de stof zo saai en onaantrekkelijk mogelijk te brengen.

Gelukkig zijn de mogelijkheden om je kennis te vergroten met de jaren enorm toegenomen, mede door dit soort fora. Een mens is nooit te oud om te leren.

Hensz

Golden Member

Op 28 maart 2019 09:58:03 schreef roadrunner84: Let erop dat hoe verder je getal van 0 af is, hoe minder precisie je hebt. Waar bijvoorbeeld 1 en 2 beschreven kunnen worden, is er "geen" verschil tussen 1-triljard en 1-triljard-en-1. Hierdoor kunnen er vreemde situaties ontstaan, bijvoorbeeld dat (√2)2≠2.

Als precisie gedefinieerd is als een absolute waarde, dan klopt het. Belangrijker is echter vaak de nauwkeurigheid, die is met zon 32-bit Fp-representatie wel over het hele bereik even groot.
Je voorbeeld (√2)2≠2 komt daaruit voort. De nauwkeurigheid is eindig, ook bij kleine getallen.

Meestal maakt het geen barst uit of je iets tot 10 cijfers achter de komma nauwkeurig kunt weergeven en is 5-7 cijfers ruim voldoende.
Uiteraard bevestigt hier ook de uitzondering de regel, in de ruimtevaart en de natuurkunde bijv. wil nog wel een met veel grotere nauwkeurigheden gerekend (moeten) worden.

Don't Panic!

Op 28 maart 2019 09:58:03 schreef roadrunner84:
De door IEEE gestandaardiseerde floating point getallen (waarvan ik redelijkerwijs aanneem dat dat ook de floating point getallen zijn die in jouw taal gebruikt worden) zijn in dit wikipedia artikel beschreven zijn als volgt beschreven
±n*10m
waar n is in (0..16777215) en m is in (-126..127).

Als dat inderdaad in hetwikipedia artikel staat dan is dat hartstikke fout. Het is namelijk iets als: w = +/- X * 2^Y. Hierbij is er 1 bit die het +/- gedoe bepaalt, 1<= X < 2 in stapjes van 2^-24 en Y is -127...126 (of iets van die orde).

@blurp hieronder: Jep. Had het zelf ook even gechecked. :-) Het "hidden bit" heb ik niet benoemd, maar zit wel in mijn formule doordat ik 1<=X<2 noem.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 28 maart 2019 19:20:24 schreef rew:
[...]Als dat inderdaad in hetwikipedia artikel staat dan is dat hartstikke fout. Het is namelijk iets als: w = +/- X * 2^Y. Hierbij is er 1 bit die het +/- gedoe bepaalt, 1<= X < 2 in stapjes van 2^-24 en Y is -127...126 (of iets van die orde).

Wees gerust Rew, het wikipedia artikel heeft het wel goed. Zelfs gedetailleerder dan jouw omschrijving (wiki noemt het hidden bit, en de details rond subnormal numbers wel)

Toch wel knap uitgelegd door SparkyGSX.

Vorig jaar had ik een routine nodig die zeer snel 32 bit floating point waarden omzette naar ASCII tekst op een Windows PC.
Het moest minimaal 10 X sneller zijn dan de "sprintf functie" in een single thread.
Moest dus het IEEE-754 protocol bestuderen.
Het resultaat was +/- 25 maal sneller dan sprintf.

Voor de berekeningen maak ik gebruik van de SIMD SSE2 instructie set en voer de berekeningen uit in parallel en natuurlijk in assembly. ( Masm )
Alle code is voorzien van commentaar. ( per instructie, vooral om zelf de kluts niet kwijt te raken. :))
Helaas wel in het engels omdat ik het eerder gepost heb in het Masm forum.
In kader van dit onderwerp, misschien interessant om te zien hoe ik het heb geprogrammeerd.

Als er interesse voor is kan ik vanmiddag de assembly source code uploaden. ( ben nu niet thuis )

Gaaf! :-)

Nu ben ik geen fan van X86 assembly (te veel toeters en bellen die er aan geklunst zijn) maar het is toch leerzaam om te kijken naar hoe je een factor 25 kan winnen op een f-to-a.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Hier is de routine, hoop dat het te volgen is, anders vraag me maar om uitleg te geven.

code:

.code

align 4
Real4_2_ASCII proc Real4string:DWORD,floatnumber:REAL4

    mov         ecx,Real4string         ; The string to fill with the floating point value in ASCII format
    mov         eax,floatnumber         ; Load the floating point value
    test        eax,eax
    je          message_PosZero         ; check if it is 0.0
    cmp         eax,080000000h
    je          message_NegZero         ; Check if it is -0.0
    
    ; check floating-point exceptions
    cmp         eax,07F7FFFFFh
    ja          TestExceptions          ; jump to check floating point exceptions

ProcessReal4:    
    mov         byte ptr [ecx],020h     ; Write " " to the string
    test        eax,80000000h           ; Check the sign bit
    jz          No_SignBit
    mov         byte ptr [ecx],02dh     ; write "-" character to the string
    and         floatnumber,7FFFFFFFh   ; Make it an absolute value
    and         eax,7FFFFFFFh           ; Remove the sign bit
No_SignBit:

    ; Fast Log10(x)-1 routine to calculate the number of digits
    shr         eax,23                  ; Get the 8bit exponent
    sub         eax,127                 ; Adjust for the exponent bias
    cvtsi2ss    xmm0,eax                ; Convert int32 to real4
    mulss       xmm0,Log10_2            ; Approximate Log10(x) == Log10(2) * exponent bits ==  0.30102999566398119 * exponent bits
    addss       xmm0,PowersOfTen[37*4]  ; Add one to get the approximated number of digits from the floating point value
    cvtss2si    eax,xmm0                ; Convert real4 to int32
    mov         ecx,eax                 ; Save approximated number of digits
    mov         edx,38+1                ; Highest possible number of digits + 1
    add         eax,edx                 ; Get the Power Of Ten offset for the digits rounding check

    ; Now do the check to get the exact rounded number of digits from the floating point value
    ; We can do this by comparing it to the closest Power Of Ten below the floating point value  
    movss       xmm0,floatnumber
    comiss      xmm0,PowersOfTen[eax*4-4]
    jc          ExactLog10xMin1         ; Is it below the closest Power Of Ten?
    cmp         ecx,edx                 ; It is above, also check the approximated number of digits
    je          ExactLog10xMin1         ; Is it not above the highest possible number of digits skip adjustment
    dec         edx                     ; Adjust the number of digits by subtracting one
ExactLog10xMin1:                        ; Now we are allmost done to get the exact number of digits
                                        ; There is one exception, the lowest Power Of Ten check value is out of range ( 1.0E+39 )
                                        ; See the last added value in the PowersOfTen table, it's used for the out of range check
    sub         edx,ecx                 ; edx holds the offsets for the PowersOfTen table and the scientific notation string table
    mulss       xmm0,PowersOfTen[edx*4] ; Get the calculated Power Of Ten value and multiply it with the floating point value
    comiss      xmm0,PowersOfTen[38*4]  ; Compare to 10.0
    jnc         ExactNumDigits          ; Is it below 10.0?
    inc         edx                     ; It is below, adjust the offset for the scientific notation string
    mulss       xmm0,PowersOfTen[38*4]  ; Adjust decimal position ( it also solves the out of range issue )
ExactNumDigits:                         ; At this point we have the exact number of digits from the floating point value
    mulss       xmm0,PowersOfTen[42*4]  ; Get the 7 significant digits from the range -1.175494E-38 to 3.402823E+38
    cvtss2si    eax,xmm0                ; We want a Natural Number
    cvtsi2ss    xmm0,eax                ; So, remove the digits after the decimal point

    shufps      xmm0,xmm0,0             ; Splat...., make 4 copies from the real4 number
    movaps      xmm1,xmm0               ; Copy to a total of 8 copies
    mulps       xmm0,dividers           ; Produce base10 numbers
    mulps       xmm1,dividers+16        ; Produce base10 numbers
    movaps      xmm2,xmm0               ; Copy them
    movaps      xmm3,xmm1               ; Copy them
    mulps       xmm2,div10              ; Nullify least significant base10 numbers
    mulps       xmm3,div10              ; Nullify least significant base10 numbers
    cvttps2dq   xmm0,xmm0               ; Truncate remaining fractions
    cvttps2dq   xmm1,xmm1               ; Truncate remaining fractions
    cvtdq2ps    xmm0,xmm0               ; Convert back to real4
    cvtdq2ps    xmm1,xmm1               ; Convert back to real4
    cvttps2dq   xmm2,xmm2               ; Truncate remaining fractions
    cvttps2dq   xmm3,xmm3               ; Truncate remaining fractions
    cvtdq2ps    xmm2,xmm2               ; Convert back to real4
    cvtdq2ps    xmm3,xmm3               ; Convert back to real4
    mulps       xmm2,mul10              ; Move them back in the correct base10 position 
    mulps       xmm3,mul10              ; Move them back in the correct base10 position 
    subps       xmm0,xmm2               ; Subtract to get the extracted digits
    subps       xmm1,xmm3               ; Subtract to get the extracted digits
    cvttps2dq   xmm0,xmm0               ; Convert back to int32
    cvttps2dq   xmm1,xmm1               ; Convert back to int32

    shufps      xmm0,xmm0,11100001b     ; Swap the 2 first digits, the first digit is always zero
                                        ; Now we can write the decimal point for free ( no more memory swaps )
                                        ; Using a prepared ASCIIconverter constant

    packssdw    xmm0,xmm1               ; Pack 8 x 32bit to 8 x 16bit ( signed but, we are within the limit )
    packuswb    xmm0,xmm0               ; Pack 8 x 16bit to 16 x 8bit unsigned
    movq        xmm1,ASCIIconverterE    ; Prepared to insert a decimal point, convert to ASCII and terminate string in one go
    paddb       xmm1,xmm0               ; Convert the number to ASCII

    mov         edx,Scientific_sz[edx*4]; Get the 4 byte scientific notation string
    mov         ecx,Real4string

    movq        qword ptr [ecx+1],xmm1  ; Write the 7 significant digits
    mov         [ecx+9],edx             ; Write the scientific notation string
    ret                                 ; Done....

TestExceptions:
    cmp         eax,07F800000h
    je          message_Inf
    cmp         eax,07F800001h
    je          message_SNaN
    cmp         eax,07FBFFFFFh
    je          message_SNaN
    cmp         eax,07FC00000h
    je          message_QNaN
    cmp         eax,07FFFFFFFh
    je          message_QNaN
    cmp         eax,0FFC00001h
    je          message_QnegNaN
    cmp         eax,0FFBFFFFFh
    je          message_SnegNaN
    cmp         eax,0FF800001h
    je          message_SnegNaN
    cmp         eax,0FFC00000h
    je          message_Indeterm
    cmp         eax,0FF800000h
    je          message_NegInf
    cmp         eax,0FFFFFFFFh
    je          message_QnegNaN
    jmp         ProcessReal4            ; No exceptions found, proceed...  
message_QnegNaN:
    movaps  xmm0,oword ptr szQnegNaN
    movaps  oword ptr [ecx],xmm0
    ret
message_SnegNaN:
    movaps  xmm0,oword ptr szSnegNaN
    movaps  oword ptr [ecx],xmm0
    ret
message_Indeterm:
    movaps  xmm0,oword ptr szIndeterm
    movaps  oword ptr [ecx],xmm0
    ret
message_NegInf:
    movaps  xmm0,oword ptr szNegInf
    movaps  oword ptr [ecx],xmm0
    ret
message_Inf:
    movaps  xmm0,oword ptr szInf
    movaps  oword ptr [ecx],xmm0
    ret
message_SNaN:
    movaps  xmm0,oword ptr szSNaN
    movaps  oword ptr [ecx],xmm0
    ret
message_QNaN:
    movaps  xmm0,oword ptr szQNaN
    movaps  oword ptr [ecx],xmm0
    ret
message_PosZero:
    movaps  xmm0,oword ptr szPosZero
    movaps  oword ptr [ecx],xmm0
    ret
message_NegZero:
    movaps  xmm0,oword ptr szNegZero
    movaps  oword ptr [ecx],xmm0
    ret

Real4_2_ASCII endp

Resultaat van mijn computer:

code:


SIMD Real4 to ASCII conversion by Siekmanski 2018.

1000000 calls per Run for the Cycle counter and the Routine timer.

Intel(R) Core(TM) i7-4930K CPU @ 3.40GHz

 Routine timers running now....

Real4_2_ASCII Clock Cycles: 68 RoutineTime: 0.021774835 seconds
sprintf       Clock Cycles: 1910 RoutineTime: 0.563480065 seconds

Real4_2_ASCII routine is 25.877582 times faster than the sprintf function

Result Real4_2_ASCII:  3.402823e+38
Result sprintf      : 3.402823e+038

Press any key to continue...

Hier de complete source codes en de executable:

Update!
Heb de Scientific_sz lookup table aangepast. ( per ongeluk de verkeerde test versie ingevoegd )
01+e, 02+e en 03+e in de onderste 3 regels van de tabel moesten zijn: 01-e, 02-e en 03-e
Source code aangepast in de download link hieronder. ( sorry voor het ongemak )

http://members.home.nl/siekmanski/Real4_2_ASCII.zip

Nu ben ik toch wel heel nieuwsgierig waarom je zo snel floats moet omzetten naar strings?

Want niemand kan zo snel al die strings lezen....

Er zijn legio redenen.

Mijn reden was het loggen van hoog frequente FFT data.
Een ander voorbeeld is bijvoorbeeld grafische representatie op het scherm d.m.v. bitmap fonts.
Mijn routine is hier super snel in omdat het gedeelte van de ascii conversie totaal kan worden overgeslagen omdat alle digits als 32 bit integers worden berekend.
Handig voor toepassingen als spellen en muziek programma's.

Dan mijn vraag, waarom niet gebruik maken van de mogelijkheden die er zijn.

Ik moet wel toegeven dat ik een beetje een autistische nerd ben en altijd op zoek om de snelste algoritmes te bedenken.

Dit is niet eens de snelste versie, de AVX variant is nog sneller omdat alle 8 digits ( 7 + de decimale punt ) in 1 keer worden berekend.
Maar deze versie werkt vanaf de pentium4 vandaar.

Dat loggen kun je toch veel beter als digitale data doen en evt uit die logging iets omzetten naar leesbare tekst? Een FFT opslaan als ascii tekst klinkt mij als een nogal onelegante oplossing (maar misschien mis ik je punt).

@hieronder: ok, duidelijk. No offense.

"We cannot solve our problems with the same thinking we used when we created them" - Albert Einstein

Je hebt helemaal gelijk hoor, maar aangezien het ontvangende station een hele slome cpu en floating point unit heeft hebben we hier voor gekozen. Anders was het in realtime niet te doen.
Nu hoeven er alleen aaneengesloten blokken van 12 byte per getal te worden ontvangen. Dus geen extra kosten. ( dit was de voornaamste reden )

Eigenlijk was dit voorbeeld alleen maar om te laten zien hoe het 32 bit floating point format kan worden gedecodeerd zonder de FPU te gebruiken, dacht dat dit wel interessant was in kader van dit topic.

Op 30 maart 2019 13:04:51 schreef Siekmanski:
Dan mijn vraag, waarom niet gebruik maken van de mogelijkheden die er zijn.

Uiteraard, "Omdat het kan" is altijd een goed antwoord op de vraag waarom je iets doet (bij dit soort technische nerd dingen althans..)

Maar TS kwam hier met een vraag over floats op PIC's, dus ik denk niet dat ie heel snel ftoa() wil doen :-).

Wel leuk dat het kan, en dat iemand het gedaaan heeft. Zelf ben ik 100 jaar geleden ook ooit assembly begonnen met 16 bits integers in ascii omzetten op een 8088... Gelukkig kan ik nu meestal wegkomen met C :-)

Op 30 maart 2019 17:46:02 schreef blurp
Maar TS kwam hier met een vraag over floats op PIC's, dus ik denk niet dat ie heel snel ftoa() wil doen :-).

OK sorry, dacht dat het vooral om het floating point format ging. :o

Wel leuk dat het kan, en dat iemand het gedaaan heeft. Zelf ben ik 100 jaar geleden ook ooit assembly begonnen met 16 bits integers in ascii omzetten op een 8088... Gelukkig kan ik nu meestal wegkomen met C :-)

Ik ben zelf ook ongeveer 100 jaar geleden begonnen met assembly en er nooit mee gestopt. :-)

240diesel

Golden Member

Hahaha, mooi werk, ik begrijp net zoveel van je code als van het Chinese schrift......dat neemt echter niet weg dat ik het wel interessant en vooral heel knap vind.

Ik ben zelf heel blij met alles wat ik van Frits z'n picbasic cursussen heb geleerd (en nog steeds leer) , wel eens gedacht om andere programmeertaal te leren maar ben bang dat ik dan helemaal alles door elkaar ga halen. Daar komt nog bij dat ik recent ook aan het AUTOCAD-en ben geslagen dus hersentraining heb ik zat.

Los daarvan heb ik allang begrepen dat de ideale programmeertaal tóch niet bestaat net zomin als de beste auto etc.

Dank je.

Op 30 maart 2019 22:10:58 schreef 240diesel:
Los daarvan heb ik allang begrepen dat de ideale programmeertaal tóch niet bestaat net zomin als de beste auto etc.

Hahaha, daar zijn de meningen over verdeeld, uiteindelijk is het maar net waar je je prettig bij voelt en wat je doelen zijn. ;-)
Succes met je projecten.

Update!
Heb de Scientific_sz lookup table aangepast. ( per ongeluk de verkeerde test versie ingevoegd )
01+e, 02+e en 03+e in de onderste 3 regels van de tabel moesten zijn: 01-e, 02-e en 03-e
Source code aangepast in de download link hieronder. ( sorry voor het ongemak )

http://members.home.nl/siekmanski/Real4_2_ASCII.zip

Inderdaad,

De ideale programmeertaal bestaat wel degelijk, maar is wel afhankelijk van veel factoren, onder andere je persoonlijke voorkeur. ;)

Voor mij is het C++, vooral omdat ik daar in thuis ben en omdat het breed ondersteund word op een groot aantal processors.