Page 1 of 2
Printing floating point numbers with exponents.
Posted: Sun Jan 29, 2012 6:28 am
by Demivec
Code updated for 5.20+
Here are two functions for converting a floating point number into a string representation using exponent notation. The functions allow specifying the number of significant digits to show.
I looked around for quite a while to find functions like this for saving floating point numbers to text files without using a binary representation of them. There are a few in the forum but the ones I am posting here take a slightly different approach and seem to be smaller and more straight forward.
It would be great if something similar could be implemented natively in PureBasic. They could also be added with additional formatting options to a sPrintF() implementation (hint

).
Code: Select all
Procedure.s strFE(x.f, dec.b = 6)
If x = 0: ProcedureReturn "0": EndIf
Protected e.l = (((PeekL(@x) >> 23) & %11111111) - 127) * Log10(2)
If dec < 0: dec = 0: EndIf
ProcedureReturn StrF(x / Pow(10, e), dec) + "e" +Str(e)
EndProcedure
Procedure.s strDE(x.d, dec.b = 6)
If x = 0: ProcedureReturn "0": EndIf
Protected e.q = (((PeekQ(@x) >> 52) & %11111111111) - 1023) * Log10(2)
If dec < 0: dec = 0: EndIf
ProcedureReturn StrD(x / Pow(10, e), dec) + "e" +Str(e)
EndProcedure
Debug strDE(-3.5e20)
Debug strDE(-3.5e-20)
Debug strFE(3.5e20)
Debug strFE(3.5e-20)
@Edit: Made a correction to strDE() to use StrD() instead of StrF().
Re: Printing floating point numbers with exponents.
Posted: Sun Jan 29, 2012 7:47 am
by Little John
Very useful. Thank you, Demivec!
It would be great if something similar could be implemented natively in PureBasic. They could also be added with additional formatting options to a sPrintF() implementation (hint

).
I agree.
In Procedure str
DE(), it reads "ProcedureReturn Str
F(...) ...".
Is this intended? (just curious

)
Best regards, Little John
Re: Printing floating point numbers with exponents.
Posted: Sun Jan 29, 2012 8:24 am
by Demivec
Little John wrote:In Procedure str
DE(), it reads "ProcedureReturn Str
F(...) ...".
Is this intended? (just curious

)
It has been corrected now to use StrD(). Thanks for keeping me honest.

Re: Printing floating point numbers with exponents.
Posted: Sun Jan 29, 2012 6:30 pm
by skywalk
Demivec, that is one cool brain teaser of a solution.
This is Trond's code with a small speed up change for negative exponents.
http://www.purebasic.fr/english/viewtop ... 84#p346484
Yours is ~9% faster for most sign cases, but I do prefer to maintain a fixed width output for all cases and I never print scientific notation.
Use Engineering Notation -> ±xx.yye±"multiple of 3" instead...
http://www.purebasic.fr/english/viewtop ... 74#p346474
Code: Select all
Procedure.s StrDsci(x.d, numsd.i=6)
; Scientific notation, Trond
; skywalk, ExpSgn from .s to .i, faster for negative exponents.
; Supports ± exponent sign and fixed width for ± numbers.
Protected.i Exp
Protected.i Sgn = ' '
Protected.i ExpSgn = '+'
If x < 0
sgn = '-'
x * -1
EndIf
If x <> 0
While x >= 10
x / 10
Exp + 1
Wend
While x <= 1
x * 10
Exp + 1
ExpSgn = '-'
Wend
EndIf
ProcedureReturn Chr(sgn) + StrD(x, numsd-1) + "e" + Chr(ExpSgn) + Str(exp)
EndProcedure
Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 6:04 am
by wilbert
Both ways have issues.
Try
Debug StrDsci(Infinity())
or
Debug strDE(999)
Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 7:06 am
by skywalk
wilbert wrote:Both ways have issues.
Try
Debug StrDsci(Infinity())
No issues here?
My computer says it will be done when I finish drinking Lake Superior.

Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 7:19 am
by skywalk
wilbert wrote:Both ways have issues.
Try
Debug strDE(999)
The 999 results are valid numbers in scientific notation.
StrDe(999,4) = 0.9990e3
StrDsci(999,4) = 9.990e+2
And from my older post, StrDe(999,4) = 999.0e+0

Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 8:57 am
by wilbert
Here's my attempt at getting consistent results
Code: Select all
Procedure.s SciStrD(x.d, dec.a = 5, engineering = #False)
Protected.s{1} sgn, esgn
Protected.l e
; check for Infinity and NaN
!mov edx, 0x7ff00000
!mov eax, [p.v_x + 4]
!and edx, eax
!xor edx, 0x7ff00000
!jnz SciStrD_cont1
ProcedureReturn StrD(x)
; extract sign
!SciStrD_cont1:
!btr eax, 31
!mov [p.v_x + 4], eax
!mov al, 32; ' '
!mov cl, 45; '-'
!cmovc eax, ecx
!mov [p.v_sgn], al
!xor edx, 0x7ff00000
!jz SciStrD_cont2
; extract exponent and exponent sign
If engineering
e = Int(Round(Log10(x) / 3.0, #PB_Round_Down)) * 3
x / Pow(10.0, e)
If Val(StrD(x, dec)) >= 1000.0
x / 1000.0 : e + 3
EndIf
Else
e = Int(Round(Log10(x), #PB_Round_Down))
x / Pow(10.0, e)
If Val(StrD(x, dec)) >= 10.0
x / 10.0 : e + 1
EndIf
EndIf
!SciStrD_cont2:
!mov edx, [p.v_e]
!mov eax, edx
!neg eax
!cmovs eax, edx
!mov [p.v_e], eax
!mov al, 43; '+'
!mov cl, 45; '-'
!cmovg eax, ecx
!mov [p.v_esgn], al
; return result
ProcedureReturn sgn + StrD(x, dec) + "e" + esgn + Str(e)
EndProcedure
Results
Code: Select all
MinusZero.d
PokeQ(@MinusZero, 1 << 63)
Debug StrDE(Infinity()) ; +Infinitye308 ***
Debug StrDE(MinusZero) ; 0
Debug StrDE(NaN()) ; 0 ***
Debug StrDE(0.3) ; 3.000000e-1
Debug StrDE(0.1) ; 10.000000e-2
Debug StrDE(300) ; 3.000000e2
Debug StrDE(100) ; 10.000000e1
Debug StrDsci(Infinity()) ; hangs ***
Debug StrDsci(MinusZero) ; 0.00000e0
Debug StrDsci(NaN()) ; -NaNe+0 ***
Debug StrDsci(0.3) ; 3.00000e-1
Debug StrDsci(0.1) ; 10.00000e-2
Debug StrDsci(300) ; 3.00000e+2
Debug StrDsci(100) ; 10.00000e-3 ***
Debug SciStrD(Infinity()) ; +Infinity
Debug SciStrD(MinusZero) ; -0.00000e+0
Debug SciStrD(NaN()) ; NaN
Debug SciStrD(0.3) ; 3.00000e-1
Debug SciStrD(0.1) ; 1.00000e-1
Debug SciStrD(300) ; 3.00000e+2
Debug SciStrD(100) ; 1.00000e+2
Engineering notation results
Code: Select all
Debug SciStrD(0.23, 5, #True) ; 230.00000e-3
Debug SciStrD(2.30, 5, #True) ; 2.30000e+0
Debug SciStrD(23.0, 5, #True) ; 23.00000e+0
Debug SciStrD(2300, 5, #True) ; 2.30000e+3
Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 10:41 am
by Demivec
wilbert wrote:Debug strDE(999)
@wilbert: Nice catch. It is nice to know that the result isn't wrong, just inconsistent. I was still exploring any possible improvements that could be made, and hadn't addressed the Infinity or NaN results yet. I'm not sure if I consider MinusZero worth the bother though.
I like your version of SciStrD(). It's classy.

Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 10:52 am
by wilbert
@Demivec, yes, the results were only inconsistent. It has to do with the way you are getting the exponent.
If you would use something like
Protected e.d = Round(Log10(Abs(x)), #PB_Round_Down)
the inconsistencies should be gone.
Checking for NaN or Infinity could be done like this
Code: Select all
Procedure.s strDE(x.d, dec.a = 6)
If x = x * 2 : ProcedureReturn StrD(x, 0) : EndIf
Protected e = Int(Round(Log10(Abs(x)), #PB_Round_Down)) : x / Pow(10, e)
If Val(StrD(x, dec)) >= 10.0 : x / 10.0 : e + 1 : EndIf
ProcedureReturn StrD(x, dec) + Left("e+", e >> 9 + 2) + Str(e)
EndProcedure
Debug StrDE(Infinity()) ; +Infinity
Debug StrDE(MinusZero) ; 0
Debug StrDE(NaN()) ; NaN
Debug StrDE(0.3) ; 3.000000e-1
Debug StrDE(0.1) ; 1.000000e-1
Debug StrDE(300) ; 3.000000e+2
Debug StrDE(100) ; 1.000000e+2
Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 5:31 pm
by akj
@wilbert:
There are still minor inconsistencies in your solution. Try for example:
Code: Select all
Debug StrDE(999, 0)
Debug StrDE(999, 1)
Debug StrDE(999, 2)
Debug StrDE(999, 3)
Also, I think it would be better to use dec.a rather than dec.b for the number of decimal places as it's value must be positive.
Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 6:37 pm
by wilbert
@akj, I tried to built in a correction.
Is it working now or still inconsistencies ?
If you have a better solution, feel free to share.
Re: Printing floating point numbers with exponents.
Posted: Mon Jan 30, 2012 7:26 pm
by skywalk
Hi wilbert,
It's rare I beat your asm code(~5-10% faster), but I am not checking for Infinity() or deviating from Engineering Notation.
If the number exceeds a Double, I get a 'NaN' result anyway.
Also, I need the string result to maintain fixed width, given a number of significant digits, not based on number of decimal places. SciStrD() gives variable widths depending on Engineering notation.
Code: Select all
Debug SciStrD(3.5e20,6,1) ; [ 350.000000e+18]
Debug SciStrD(-3.5e-20,6,1) ; [-35.000000e-21]
Debug StrDe(3.5e20,6) ; [ 350.000e+18]
Debug StrDe(-3.5e-20,6) ; [-35.0000e-21]
Code: Select all
Procedure.s StrDe(x.d, numsd.i=6)
; skywalk
;Debug StrDE(1.23456e7, 4) ; " 12.35e+6" <-- fixed widths
;Debug StrDE(-1.23456e7, 4) ; "-12.35e+6" <-- and Exponent in multiples of 3
;Debug StrDE(0, 4) ; " 0" <-- 0 is simplified, not 0.000e+0
; GIVEN: my$ = StrDE(x, numsd)
; IN:
; x = real number to be formatted.
; numsd = integer specifying total number of significant digits.
; "aa.bbbe+3" -> 2 + 3 = 5
; RETURN:
; string of x with numsd significant digits using engineering notation.
; NOTE:
; Does not check for x = Infinity(), but does print NaN instead.
; Double -> ±2.2250738585072013e-308 to ±1.7976931348623157e+308
Protected.i Exp, Sgn
If x > 0
Sgn = #SPC ; or ASC(" ") or ' '
ElseIf x < 0
Sgn = #DASH ; '-'
x * -1
Else ; Should fill " 000.000e+0", +1 for 'sign', +1 for '.', +3 for 'e+0'
ProcedureReturn RSet("0", numsd + 5)
EndIf
exp = Round(Log10(X),#PB_Round_Down)
If exp > 0
exp / 3 * 3
Else
exp = (-exp + 3) / 3 * (-3)
EndIf
x = x * Pow(10,-exp)
If x >= 1000
x / 1000
exp + 3
EndIf
If x >= 100
numsd - 3
ElseIf x >= 10
numsd - 2
Else
numsd - 1
EndIf
If numsd < 0
numsd = 0
EndIf
If exp < 0
ProcedureReturn Chr(sgn) + StrD(x, numsd) + "e" + Str(exp)
Else
ProcedureReturn Chr(sgn) + StrD(x, numsd) + "e+" + Str(exp)
EndIf
EndProcedure
Re: Printing floating point numbers with exponents.
Posted: Tue Jan 31, 2012 1:02 pm
by wilbert
@skywalk, your routine isn't always fixed width.
The width is different for
Debug StrDE(999999, 2)
Debug StrDE(1000000, 2)
skywalk wrote:It's rare I beat your asm code(~5-10% faster)
I didn't optimize for speed.
This probably will be faster
Code: Select all
Procedure.s SciStrD(x.d, dec = 5, engineering = #False)
Protected.s{1} sgn, esgn
Protected.l e
Static Dim pow10table.d(618)
Static setTable = #True
; ** pow10 table **
If setTable
For e = -309 To 309
pow10table(e + 309) = Pow(10, e)
Next
setTable = #False
EndIf
; ** check for Infinity and NaN **
!mov edx, 0x7ff00000
!mov eax, dword [p.v_x + 4]
!and edx, eax
!xor edx, 0x7ff00000
!jnz SciStrD_cont1
ProcedureReturn StrD(x)
; ** extract sign **
!SciStrD_cont1:
!btr eax, 31
!mov dword [p.v_x + 4], eax
!mov al, 32; ' '
!mov cl, 45; '-'
!cmovc eax, ecx
!mov byte [p.v_sgn], al
!xor edx, 0x7ff00000
!jz SciStrD_cont4
; ** extract exponent and exponent sign **
!rol edx, 12
!sub edx, 1022
!cvtsi2ss xmm0, edx
; set things based on engineering flag
!test byte [p.v_engineering], -1
!jz SciStrD_noEng
!mulss xmm0, [SciStrD_lg2div3]
!cvtss2si edx, xmm0
!lea edx, [edx + edx * 2]
!mov eax, 1000
!mov ecx, 3
!jmp SciStrD_Eng
!SciStrD_noEng:
!mulss xmm0, [SciStrD_lg2]
!cvtss2si edx, xmm0
!mov eax, 10
!mov ecx, 1
!SciStrD_Eng:
!cvtsi2sd xmm3, eax
; x / pow(10,e)
!movd xmm0, edx
!add edx, 309
CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
!mov eax, dword [s_SciStrD.a_pow10table]
!movsd xmm1, [eax + edx * 8]
!mov edx, dword [p.v_dec]
!movsd xmm2, [eax + (edx + 309) * 8]
CompilerElse
!mov rax, qword [s_SciStrD.a_pow10table]
!movsd xmm1, [rax + rdx * 8]
!mov rdx, qword [p.v_dec]
!movsd xmm2, [rax + (rdx + 309) * 8]
CompilerEndIf
!movd edx, xmm0; edx = e
!movsd xmm0, [p.v_x]; xmm0 = x
!divsd xmm0, xmm1
!comisd xmm0, [SciStrD_1]
!jnc SciStrD_cont2
!mulsd xmm0, xmm3
!sub edx, ecx
; correct for rounding issues
!SciStrD_cont2:
!movsd xmm1, xmm0
!mulsd xmm1, xmm2
!addsd xmm1, [SciStrD_1div2]
!divsd xmm1, xmm2
!comisd xmm1, xmm3
!jc SciStrD_cont3
!divsd xmm0, xmm3
!add edx, ecx
; extract exponent sign
!SciStrD_cont3:
!movsd [p.v_x], xmm0
!SciStrD_cont4:
!mov eax, edx
!neg eax
!cmovs eax, edx
!mov dword [p.v_e], eax
!mov al, 43; '+'
!mov cl, 45; '-'
!cmovg eax, ecx
!mov byte [p.v_esgn], al
; ** return result **
ProcedureReturn sgn + StrD(x, dec) + "e" + esgn + Str(e)
!SciStrD_1div2 : dd 0, 0x3fe00000
!SciStrD_1 : dd 0, 0x3ff00000
!SciStrD_lg2 : dd 0x3e9a209b
!SciStrD_lg2div3 : dd 0x3dcd80ce
EndProcedure
and hopefully still works

Re: Printing floating point numbers with exponents.
Posted: Wed Feb 01, 2012 8:44 am
by skywalk
Hi wilbert,
I couldn't get your last asm version SciStrD() to run - Operand size error?
Good catch on the StrDsci(1000,2)

I really only use the engineering format of StrDe(x.d, 6), as any fewer significant digits are not acceptable to retain accuracy in the numbers I store. So, StrDe(x.d, 2) is not intended to retain a fixed width or be used.
I have no need for straight scientific notation, but this is my best guess at a fix for StrDsci().
Code: Select all
Procedure.s StrDsciT(x.d, ndec.i=6)
; Scientific notation, Trond
; skywalk, changed ExpSgn.s to ExpSgn.i. Faster for negative exponents.
; fixed redundant exp summing for cases like x = 10,100,etc.
Protected.i Exp
Protected.i Sgn = ' '
Protected.i ExpSgn = '+'
;If ndec < 3: ndec = 3: EndIf
If x < 0
sgn = '-'
x * -1
EndIf
If x <> 0
While x >= 10 ; 'While x >= 10' caused error when x = 10,100,1000,etc.
x / 10
Exp + 1
Wend
If exp = 0 ; Don't add to Exp if already set.
While x <= 1
x * 10
Exp + 1
ExpSgn = '-'
Wend
EndIf
EndIf
ProcedureReturn Chr(sgn) + StrD(x, ndec) + "e" + Chr(ExpSgn) + Str(exp)
EndProcedure