StrE(FPnumber, sig_digits)
Posted: Mon Nov 17, 2008 3:35 pm
I sometimes have a need to output numbers in scientific format such as 3.1415926535E-24 which StrD() and StrF() cannot appear to do. The code below fills this gap:
I suspect that the FNINIT instruction should be replaced by something less drastic and that the If..Else..Endif block within the assembler code should be replaced by conditional jumps in pure assembler, but I don't know what would be best. Can anyone advise me?
Code: Select all
Procedure.s StrE(fp.d, length=16)
; Convert FP number to fixed length scientific format
; The length is the number of significant mantissa digits
; The fundamental concept for a given floating point number fp:
; fp = sign*mantissa[radix 2]* 2^exponent[radix 2]
; = sign*mantissa[radix10]*10^exponent[radix10]
; Where the first format is how the FP number is stored internally
; and the second format is how it is shown as a string
; The purpose of StrE(fp) is to convert between these formats
; The hard bit is accurately calculating 10^exponent[radix10]
; This is done via the assembler instruction F2XM1 which
; calculates 2^(x)-1 where x = -1.0 .. +1.0
; and the relationship: 10^x = 2^(x*log2(10))
Protected mnt.d, exp ; mantissa, unbiased exponent [radix 10]
Protected fp$ ; The result
Protected test.d ; Test whether F2XM1 argument is in range
If length<0 Or length>20: length = 20: EndIf ; Sanity check
; Special case
If fp=0.0
ProcedureReturn "+"+StrD(0.0, length-1)+" E+0"
EndIf
; Extract sign
If fp<0.0: fp = -fp: fp$ = "-": Else: fp$ = "+": EndIf
; Calculate radix 10 mantissa and exponent
EnableASM
FNINIT ; Round to nearest, all exceptions masked, 64-bit precision
FLD qword [p.v_fp]
FXTRACT ; Exponent (radix 2) -> ST1, mantissa -> ST0
FXCH ; Exponent (radix 2) -> ST0, mantissa -> ST1
FLD ST0 ; Push ST0 (duplicate exp2)
FLDLG2 ; Push Log10(2) = 0.301
FMULP ; Approx exponent (radix 10) -> ST0
FRNDINT ; Round exponent to integer
; Must round (not truncate) for F2XM1 to work
FIST exp ; Approx exponent (radix 10), may be wrong by 1
; Calculate mantissa correction = 2^(exp2-exp*log2(10))
FLDL2T ; Push Log2(10) = 3.322
FMULP ; exp2*log2(10)
FSUBP ; exp2-exp*log2(10) Hopefully in the range -1.0..+1.0
FST test ; Magnitude upper limit is 0.5*Log2(10)=1.661
If Abs(test)<1 ; If already in range -1.0..+1.0
F2XM1 ; (2^x)-1
FLD1 ; +1
FADDP ; Mantissa correction
Else ; Use 2^x = Square_of(2^(x/2))
FLD1 ; 1
FLD ST0 ; Duplicate
FADDP ; 2
FDIVP ; x/2
F2XM1 ; (2^(x/2))-1
FLD1 ; +1
FADDP ; [Square root of mantissa correction]
FLD ST0 ; Duplicate
FMULP ; Square to give mantissa correction
EndIf
FMULP ; Mantissa
FSTP mnt
DisableASM
; Normalise mantissa to be >=1 and <10 (i.e. 1 digit before DP)
; In theory at most one adjustment will ever be needed
While mnt<1.0: mnt * 10.0: exp - 1: Wend
While mnt>=10.0: mnt / 10.0: exp + 1: Wend
; Build result
fp$ + StrD(mnt, length-1)+" E"
If exp>=0: fp$ + "+": EndIf
ProcedureReturn fp$ + Str(exp)
EndProcedure
; Examples
Debug StrE(1/81, 9) ; +1.2345...E-2
Debug StrE(Pow(2, -52)) ; +2.2204...E-16
Debug StrE(-#PI, 20) ; -3.1415...E+0