Printing IEEE Floating Point numbers

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Slasher

Printing IEEE Floating Point numbers

Post by Slasher »

I'm trying to add Printing of Floats and Doubles to my lib. I fully understand the IEEE format
bit 31 = sign bit
bits 23 - 30 =biased exponent(single precision)
bits 0 - 22 = mantissa with implied leading 1.
I know how to convert from decimal to binary float.
you convert the whole part as usual by repetitive division of the number by 2 and keeping record of the remainders. then writing the remainders out from right to left starting from the last one to the first one. there is also a subtraction method.
The fraction part is converted by multiplying the fraction part by 2 and writing the result of the whole part only,after each multipication until the fraction part becomes zero or you reach your precision target.
eg
70.25
[whole part] 70 = 01000110
[fraction part] 0.25 =>
0.25 * 2 = 0.5 so write 0 then use 0.5 for next loop/pass
0.50 * 2 = 1.0 so write 1 then stop as fract part is now zero
write binary fraction part starting from first to last
so 70.25 = 01000110.01
normalized 70.25 = 1.00011001 (we moved the point 6 places to the left so e = 6)
so in IEEE format
sign bit = 0
exponent = 10000101 (133 (6 +127(bias number))
mantisss = 00011001 (add trailing zero to complete 23 bits)
which gives
01000010100011001000000000000000 = 0X428C8000
Here is my dilemma.
How does one print Floats?
I was thinking of using tables of fractions [0.5,0.25,0.125,0.0625,0.03125] or [50000,25000,12500,6250,3125...]
but can see how.
Thanks
tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Hi,

Here is the early version of some code I wrote for 386 real mode. It may have a few errors but hopefully you can rewrite it (probably in a different language :P).

Code: Select all



; -----------------------------------------------------------------
;
; ATOF (Ascii to float)
;
; Converts an ascii string into a 32 bit float.
;
; Input:
;  [DS:SI] = String, format: (+/-)xxxxxx.xxxxxxx([e/E](+/-)yyyyy)
; Output:
;  EAX = 32 bit float
;  Other registers and flags preserved
;  
ATOF:

; save values
pushfd
push edx
push esi
push edi
push ebx
push ecx

;
; Converting an ascii to a float consists of 2 main steps:
; 1) convert to a decimal float
; 2) convert decimal float to binary float
;

;
; Converting DS:SI to a decimal float....
;

; zero out values
xor eax, eax
xor ebx, ebx
xor ecx, ecx

; First test for leading mantissa sign
cmp byte [si], '+'
je .finishMantissaSign
cmp byte [si], '-'
jne .doneMantissaSign
or cl, 1               ; Set low bit
.finishMantissaSign:
inc si                 ; Move to next char
.doneMantissaSign:

; Process the digits
.processChar:
xor edx, edx
mov dl, [si]
inc si

cmp dl, '0'
jb .notDigit
cmp dl, '9'
ja .notDigit

; If we are behind the decimal point we have to adjust the exponent
test cx, 1<<15            ; flags stored in ecx
jz .noDecimalPointAdjust
sub ebx, 1                ; Subtract 1 from exponent
.noDecimalPointAdjust:

; If we have already rounded off the mantissa,
; no point trying to add to it
test ecx, 1<<31
jnz .roundedMantissa

; Check for overflow
cmp eax, 0FFFFFFFh
ja .mantissaOverflow

; Multiply eax by 10
push edx
mov edx, 10
mul edx
pop edx

; Add new digit
sub dl, 30h
add eax, edx              ; Add 2 values together EAX contains cumulative total
jmp .processChar          ; and go on to next digit

; ----------------------------------
; We have filled up the mantissa, do a bit of rounding
.mantissaOverflow:
or ecx, 1<<31             ; Set the rounded flag
cmp dl, '5'
jb .roundedMantissa
add eax, 1                ; Round up

.roundedMantissa:
add ebx, 1                ; Multiply by 10 by increasing the exponent
jmp .processChar

; ----------------------------------
; If we have found the . set flag appropriately
.processDecimalPoint:
or cx, 1<<15
jmp .processChar

; ----------------------------------
; Process non integers. If the character is invalid go to exit
.notDigit:
cmp dl, '.'
je .processDecimalPoint
cmp dl, 'e'
je .processExponent
cmp dl, 'E'
je .processExponent
jmp .finishedDecExponent   ; Part 1 is done

; ---------------------------------
; Modify the exponent (ebx) if we arrive at an E character
.processExponent:
push eax                  ; Screw around in EAX
xor eax, eax              ; so set it to a known value

; First check for exponent's sign
cmp byte [si], '+'
je .finishExponentSign
cmp byte [si], '-'
jne .doneExponentSign
or eax, 1<<30             ; Set high bit, AX is used for conversion
.finishExponentSign:
inc si                    ; Move to next char
.doneExponentSign:

.processExpChar:
xor edx, edx
mov dl, [si]
inc si

cmp dl, '0'
jb .finishExp
cmp dl, '9'
ja .finishExp

continued....
tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Code: Select all


; Check for exponent overflow
cmp ax, 0FFFh
ja .processExpChar        ; Ignore digits that can't be converted

; Multiply by 10 and add this digit
push dx
mov dx, 10
mul dx
pop dx
sub dl, 30h
add ax, dx
jmp .processExpChar

.finishExp:
test eax, 1<<30           ; If this is set, exponent is negative
jz .fixedEax
and eax, 0FFFFFFFh        ; Unset the bit
neg eax                   ; Negate eax
.fixedEax:
add ebx, eax              ; Simply add it to ebx.
pop eax                   ; Finished processing the exponent
jmp .notDigit             ; so exit routine

; Done converting to decimal exponent
.finishedDecExponent:

; DEBUG DEBUG
;call PrintEax             ; Mantissa
;push ax
;mov al, '-'
;call PrintChar
;pop ax
;xchg eax, ebx             ; Display exponent
;call PrintEax
;xchg ebx, eax
;push ax
;mov al, '-'
;call PrintChar
;pop ax
;xchg eax, ecx             ; Display sign
;call PrintEax
;xchg ecx, eax
;call PrintNewLine

;
; Part 2 - Convert the decimal float into a binary float
;  EAX = mantissa (an integer, decimal point is at right of number)
;  ECX = sign of mantissa in LSBit
;  EBX = signed unbiased decimal exponent
;
;  Temp values: EDI = new binary exponent
;               EDX gets trashed
;
; Return binary float in EAX
;

; First test for special case, 0
cmp eax, 0
jne .setupValues                    ; Reverse logic
jmp .finishedBinaryFloat

.setupValues:
; The binary exponent begins with 23 (the number of bits in final mantissa)
mov edi, 17h                        ; Set up new binary exponent

; Convert the decimal exponent to 0 using divides or multiplies
.checkDecExponent:
cmp ebx, 0
jl .negativeDecExponent
jg .positiveDecExponent
jmp .fixedDecExponent

; Positive exponent, so multiply mantissa by 10
.positiveDecExponent:
test eax, 0F0000000h        ; Check if we can multiply by 10
jz .noMultiplyOverflow
clc
rcr eax, 1                  ; Divide the value by 2
adc eax, 0                  ; If there is a remainder round up the result
add edi, 1                  ; Update the Binary exponent
jmp .positiveDecExponent

.noMultiplyOverflow:
mov edx, 10                 ; Multiply eax by 10 and subtract 1 from dec exp
mul edx
sub ebx, 1
jmp .checkDecExponent

; Negative exponent, so divide the mantissa by 10
.negativeDecExponent:
test eax, 1 << 31           ; Move the mantissa as far left as possible
jnz .doDivision
shl eax, 1                  ; Move mantissa left
sub edi, 1                  ; and subtract one from binary exponent
jmp .negativeDecExponent

.doDivision:
push ecx                    ; Divide EDX:EAX by 10
mov ecx, 10
xor edx, edx
div ecx
pop ecx                     ; Restore the saved value of ecx
add ebx, 1                  ; Increment the decimal exponent

jmp .checkDecExponent

; --------------------------------
.fixedDecExponent:          ; Now have to ensure binary mantissa is 24 bits
test eax, 0FF000000h
jz .fixedOverPrecision
rcr eax, 1                 ; Move mantissa to the right, saving LSBit
setc bl                    ; Dec exponent = 0, so recycle register
add edi, 1                 ; Add 1 to binary exponent to compensate
jmp .fixedDecExponent

.fixedOverPrecision:       ; Possibly mantissa is LESS than 24 bits
cmp bl, 0                  ; If the last bit shifted out of eax was 1,
je .noRoundingError        ; round up
add eax, 1
.noRoundingError:
test eax, 00800000h        ; 24th bit MUST be set
jnz .fixedUnderPrecision
shl eax, 1                 ; Shift left and adjust binary exponent
sub edi, 1
jmp .fixedOverPrecision

.fixedUnderPrecision:      ; Now make sure binary exponent is ok
cmp edi, -126
jl .notANumber
cmp edi, 127
jg .notANumber
mov ebx, edi               ; ebx used to be decimal exponent, but is recycled now
and ebx, 000000FFh         ; clear out the extra bits
add bl, 127                ; bias the exponent

; Finally join all of them together
and eax, 007FFFFFh         ; Mantissa in eax, strip implied 24th bit
shl ebx, 23                ; Move exponent to its proper position
or eax, ebx                ; Combine them
test ecx, 1                ; Check for sign
jz .finishedBinaryFloat
or eax, 80000000h          ; Set the sign bit
jmp .finishedBinaryFloat

.notANumber:
mov eax, 7FC00000h         ; NAN = biased exp of FFh, mantissa not 0

; --------------------------------
.finishedBinaryFloat:

pop ecx
pop ebx
pop edi
pop esi
pop edx
popfd
retn
;
; End of ATOF
; -----------------------------------

tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Code: Select all




; -----------------------------------------------------------------
;
; FTOA (Float to Ascii)
;
; Converts a 32 bit float into an ascii string.
;
; Input:
;  Stack is valid, SP > 128
;  EAX = floating point value
;  [ES:DI] = Destination string pointer. String requires 15 bytes memory
; Output:
;  String modified
;  Other registers and flags preserved
;  
FTOA:
pushf
pushad


; Part 1 - Converting to a decimal float
;
push edi                  ; Save value of EDI last YYYY

; Extract components from the float
mov ecx, eax              ; Sign bit is easy
shr ecx, 31
and ecx, 1
mov ebx, eax              ; Set up the unbiased binary exponent
shr ebx, 23
sub bl, 127               ; BL contains unbiased exponent
movsx edi, bl             ; Move to EDI
and eax, 007FFFFFh        ; Clean out the mantissa
or eax, 800000h           ; Set the implied 24th bit
xor ebx, ebx              ; New decimal exponent must be 0!

; Need to get binary exponent back to initial value of 17h (23)
.testBinExp:
cmp edi, 17h
jl .negBinExp
jg .posBinExp
jmp .finishedBinExponent

; Have to divide by 2 to increase the exponent
.negBinExp:
cmp eax, 0FFFFFFFh       ; Increase the value as much as possible before dividing
ja .gotMaxVal
mov edx, 10              ; Multiply by 10
mul edx
sub ebx, 1               ; Adjust decimal exponent to compensate
jmp .negBinExp

.gotMaxVal:
clc                      ; Divide by 2 rounding up if there is a remainder
rcr eax, 1
adc eax, 0
add edi, 1               ; Adjust the binary exponent
jmp .testBinExp

; Have to multiply by 2 to decrease the exponent
.posBinExp:
test eax, 80000000h      ; Only divide by 10 when absolutely necessary
jz .noDivide
push ecx                 ; Do the division
mov ecx, 10
xor edx, edx
div ecx
pop ecx
add ebx, 1               ; Add 1 to decimal exponent to compensate
cmp edx, 5               ; After division round up if necessary
jb .noDivide              ; noDivide label doubles as noRoundUp label
add eax, 1
.noDivide:
shl eax, 1               ; Multiply by 2 and adjust bin exponent
sub edi, 1
jmp .testBinExp

.finishedBinExponent:
;
;  Part 1 complete
;
;  EAX = mantissa (an integer, decimal point is at right of number)
;  ECX = sign of mantissa in LSBit
;  EBX = signed unbiased decimal exponent
;  EDI = binary exponent, 17h

; DEBUG DEBUG
;call PrintEax             ; Mantissa
;push ax
;mov al, '-'
;call PrintChar
;pop ax
;xchg eax, ebx             ; Display decimal exponent
;call PrintEax
;xchg ebx, eax
;push ax
;mov al, '-'
;call PrintChar
;pop ax
;xchg eax, edi             ; Display binary exponent
;call PrintEax
;xchg edi, eax
;call PrintNewLine
; END DEBUG

tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Code: Select all


;
; Part 2 - Converting decimal float to ascii
;
mov edi, [esp]           ; Finished with binary exponent, need string address
add edi, 13              ; Point at last character in string

; Converting the mantissa to an ascii string is a real pain, as string is created
; in reverse
xor bp, bp               ; bp = number of characters in string
.convertMantissa:         ; Convert the mantissa to an ascii string
cmp eax, 0
je .doneConvertMantissa

push ecx                 ; Divide  by 10
mov ecx, 10
xor edx, edx
div ecx
pop ecx

add dl, 30h              ; Remainder is least significant digit
mov [es:di], dl
dec di
inc bp
jmp .convertMantissa

.doneConvertMantissa:

; DEBUG DEBUG
;mov edi, [esp]
;push esi
;mov esi, edi             ; Print out converted string
;call PrintString
;call PrintSpace
;push ax
;mov ax, bp
;call PrintAx
;pop ax
;call PrintNewLine
;pop esi
; END DEBUG

; Put the sign character
mov edi, [esp]
push esi                 ; Save esi  XXXX
mov esi, edi
test cl, 1
jz .positiveFloat
mov [di], byte '-'
jmp .doneSign
.positiveFloat:
mov [di], byte ' '
.doneSign:
inc di                   ; Move past first char

; Move the first character
push bp                  ; Save permamently the number of chars   AAAA
mov cx, bp               ; Save the number of chars in CX
mov bp, 14               ; Point to first char using bp
sub bp, cx
mov dl, [es:si+bp]       ; Move character
mov [es:di], dl
inc di
dec cx                   ; Converted 1 char so reduce count

; Insert a decimal point
mov [es:di], byte '.'
inc di

; Copy rest of remaining characters
.moveChars:
cmp cx, 0
je .finishedMove
mov bp, 14
sub bp, cx
mov dl, [es:si+bp]       ; Move character
mov [es:di], dl
inc di
dec cx                   ; Converted 1 char so reduce count
jmp .moveChars
.finishedMove:

; DEBUG DEBUG
;call PrintString
;call PrintNewLine
; END DEBUG

; Do the exponent now
mov di, si
add di, 11
mov [es:di], byte 'E'
inc di

; Have to update exponent value because of inserted decimal point
pop ax                   ; Number of digits in string     AAAA
cwde
add ebx, eax
sub ebx, 1               ; Subtract 1 because 1 digit is left of dec point

; Check sign of exponent
cmp ebx, 0
jl .NegAsciiExp
mov [es:di], byte '+'
jmp .doneAsciiExpSign
.NegAsciiExp:
mov [es:di], byte '-'
neg ebx                  ; Make it positive for division
.doneAsciiExpSign:
inc di

; Convert exponent to ascii. We know it is a 2 digit string
mov eax, ebx
xor edx, edx
mov ebx, 10              ; Divide by 10
div ebx

; High digit is in al, low digit is in dl
add al, 30h
mov [es:di], al
inc di
add dl, 30h
mov [es:di], dl
inc di
mov [es:di], byte 0      ; Add EOS

pop esi                  ; Restore esi XXXX
pop edi                  ; Restore saved value YYYY

popad
popf
retn
;
; End of FTOA
; -----------------------------------

Hope that helps............
Tim

Re:Printing IEEE Floating Point numbers

Post by Tim »

That's very useful. Maybe you should summarise the algorithm you used in pseudocode.
Slasher

Re:Printing IEEE Floating Point numbers

Post by Slasher »

Hey Tom1000000, thanks for the code. ;D
I'm looking at it, there somethings you did that aren't clear,studying the converting to string part.
I agree with Tim, if you could, please explain the algorithm.
Its more educational to the rest of us! (though I still appreciate the code)
PS
you've been posting for a while now, why aren't you a member?

Below is the function i had written to extract the parts of a 32bit single precission IEEE floating point number. It is for 32bit mode and not optimized ;)

[attachment deleted by admin]
Slasher

Re:Printing IEEE Floating Point numbers

Post by Slasher »

MEN! This has been a long night! ::)
But I've converted the code to C and it works. ;D
Tom, I have a lot of questions.
1. why do we loop until the binary exponent is 23? ie why is our target 23?
2. why is binary exponent that is < 23 negative?
3. why is binary exponent that is > 23 positive?
4. why do we increase mantissa until it is > 0x0fffffff?
5. why do we subtract 1 from the decimal exponent if we increase mantissa?
6. for positive binary exponent, why do we divide by 10 only if the highest bit is set?

Tom, where did you get the info to write this function. We'd be grateful if you shared the knowledge with us. :)
Well, here is the C code. I wrote some important explainations at the end of the function before the final closing curly bracket } of the function. if you remove the notes i wrote there be careful not to delete the closing curly bracket.



[attachment deleted by admin]
User avatar
Solar
Member
Member
Posts: 7615
Joined: Thu Nov 16, 2006 12:01 pm
Location: Germany
Contact:

Re:Printing IEEE Floating Point numbers

Post by Solar »

Either code does not have any information on license status, which makes it "default copyrighted", i.e. we are not allowed to use it without written consensus of the author.

It would probably be most helpful if you could add a "public domain" disclaimer, which is most likely what you intended.

(Sorry for being the license lawyer again.)
Every good solution is obvious once you've found it.
thooot

Re:Printing IEEE Floating Point numbers

Post by thooot »

Well, I'm not sure if this is the same algorithm as tom is using, since I didn't examine his code too closely. To print out a floating point straight up (not in a.bbbbb x 10^x form) you can use this pseudocode:

Split into the portion before and after the decimal point.
Print out the number before the decimal point
Print out the decimal point
Print out the number after the decimal point

To print out a floating point in a.bbbbb x 10^x form you can use this pseudocode:

Get the base 10 exponent (rounded down)
Divide the number by 10 to the power of the exponent found
Output the result using the above pseudocode
Output the exponent

In C:

Code: Select all

void printDouble(double n)
{
   double ipart, frac;
   char buf[1000];
   int i;

   if (n < 0)
   {
      printf("-");
      n = -n;
   }
   
   frac = modf(n, &ipart);
   
   i = 0;
   do
   {
      buf[i] = (char) (((int) fmod(ipart, 10)) + '0');
      ipart = floor(ipart / 10);
      i++;
   } while (ipart != 0);
   i--;

   for (; i >= 0; i--)
      printf("%c", buf[i]);
   
   if (frac != 0)
      printf(".");
      
   while (frac != 0)
   {
      printf("%c", (char) (((int) floor(frac * 10)) + '0'));
      frac = frac * 10 - floor(frac * 10);
   }
}

void printExponent(double n)
{
   double e;

   if (n < 0)
   {
      printf("-");
      n = -n;
   }
   
   e = floor(log10(n));
   n = n / pow(10, e);
   printDouble(n);
   printf("e%d", (int) e);
}
This code requires math.h to be written, and I don't know how efficient it is but it should work just fine.

Edit: And to Solar: this code is placed into the public domain.
tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Hi,

I will license the code under the BSD license so u should have no problems with lawyers.

This is the algorithm I got off programmersheaven.com, and I'm quoting from memory.

If you want to convert from floating point to ascii it goes something like:

1) Move the mantissa so that it is an integer eg nothing after the decimal point.

You have to ensure the binary exponent is 23 - the number of digits stored in the float).

You can multiply / divide by 2 (adjusting the binary exponent compensate).

You may ask what happens when there is overflow / underflow eg multiply (signed integer) 0x7FFFFFFF by 2?

To fix that you can multiply / divide by 10 (adjusting the decimal exponent to compensate). The decimal exponent begins at 0 and obviously was not stored with the original floating point number. If you multiply the mantissa by 10, subtract 1 from the decimal exponent (equivalent to dividing by 10).

2) Now you have to adjust the mantissa again to ensure 1 decimal digit is left of the decimal point (eg convert 1288 to 1.288). Divide by 10 etc.

3) Now you will have the mantissa and decimal exponent ready to display. Convert to ascii.

There maybe another way that is more efficient, if there is please let me know!
tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Oh also re your question why do you increase the mantissa to a really large value?

Because when you divide by 10 you want to reduce the rounding error as much as possible.


Tom
tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

And for you legal gurus I believe there was no need for me to license it under the BSD license.

If you take the algorithm and recreate it in another programming language / recreate it independently then IMHO you have created a new work completely independent of mine.

I am assuming thats correct otherwise Samba would have been taken out by Microsoft long ago.

Another example is there's nothing to stop someone writing a Linux clone (that has the same syscalls etc) as long as they don't directly copy the code. The api / algorithms can be used by anyone.

I only have copyright on what I did, I do NOT own the algorithm (unless I get a patent on it :P).
tom1000000

Re:Printing IEEE Floating Point numbers

Post by tom1000000 »

Well I just noticed thoot's post, his method seems more elegant but as he stated you need to write those math.h functions first.
Slasher

Re:Printing IEEE Floating Point numbers

Post by Slasher »

Thanks Tom! ;D I'll look into the algorithm.
Why aren't you a member?
As for the licensing of the code I posted, it is for Public Domain, Use as You like but Don't blame me for anything :)
Post Reply