Basic/source/BasFloatString2.spin

538 lines
17 KiB
Plaintext

''*****************************************
''* Floating-Point <-> Strings v 1.2 *
''* Single-precision IEEE-754 *
''* Authors: Chip Gracey and Cam Thompson *
''* (C) 2006 Parallax, Inc. *
''* See end of file for terms of use. *
''*****************************************
'' v1.0 - 01 May 2006 - original version
'' v1.1 - 12 Jul 2006 - added FloatToFormat routine
'' v1.2 - 06 Mar 2009 - added StringToFloat [mpark]
VAR
long p, digits, exponent, integer, tens, zeros, precision
long positive_chr, decimal_chr, thousands_chr, thousandths_chr
byte float_string[20]
OBJ
' The F object can be FloatMath, Float32 or Float32Full depending on the application
F : "FME"'"BasF32"
PUB FloatToString(Single) : StringPtr
''Convert floating-point number to string
''
'' entry:
'' Single = floating-point number
''
'' exit:
'' StringPtr = pointer to resultant z-string
''
'' Magnitudes below 1e+12 and within 1e-12 will be expressed directly;
'' otherwise, scientific notation will be used.
''
'' examples results
'' -----------------------------------------
'' FloatToString(0.0) "0"
'' FloatToString(1.0) "1"
'' FloatToString(-1.0) "-1"
'' FloatToString(^^2.0) "1.414214"
'' FloatToString(2.34e-3) "0.00234"
'' FloatToString(-1.5e-5) "-0.000015"
'' FloatToString(2.7e+6) "2700000"
'' FloatToString(1e11) "100000000000"
'' FloatToString(1e12) "1.000000e+12"
'' FloatToString(1e-12) "0.000000000001"
'' FloatToString(1e-13) "1.000000e-13"
'perform initial setup
StringPtr := Setup(Single)
'eliminate trailing zeros
if integer
repeat until integer // 10
integer /= 10
tens /= 10
digits--
else
digits~
'express number according to exponent
case exponent
'in range left of decimal
11..0:
AddDigits(exponent + 1)
'in range right of decimal
-1..digits - 13:
zeros := -exponent
AddDigits(1)
'out of range, do scientific notation
other:
DoScientific
'terminate z-string
byte[p]~
PUB FloatToScientific(Single) : StringPtr
''Convert floating-point number to scientific-notation string
''
'' entry:
'' Single = floating-point number
''
'' exit:
'' StringPtr = pointer to resultant z-string
''
'' examples results
'' -------------------------------------------------
'' FloatToScientific(1e-9) "1.000000e-9"
'' FloatToScientific(^^2.0) "1.414214e+0"
'' FloatToScientific(0.00251) "2.510000e-3"
'' FloatToScientific(-0.0000150043) "-1.500430e-5"
'perform initial setup
StringPtr := Setup(Single)
'do scientific notation
DoScientific
'terminate z-string
byte[p]~
PUB FloatToMetric(Single, SuffixChr1,SuffixChr2) : StringPtr | x, y
''Convert floating-point number to metric string
''
'' entry:
'' Single = floating-point number
'' SuffixChr = optional ending character (0=none)
''
'' exit:
'' StringPtr = pointer to resultant z-string
''
'' Magnitudes within the metric ranges will be expressed in metric
'' terms; otherwise, scientific notation will be used.
''
'' range name symbol
'' -----------------------
'' 1e24 yotta Y
'' 1e21 zetta Z
'' 1e18 exa E
'' 1e15 peta P
'' 1e12 tera T
'' 1e9 giga G
'' 1e6 mega M
'' 1e3 kilo k
'' 1e0 - -
'' 1e-3 milli m
'' 1e-6 micro u
'' 1e-9 nano n
'' 1e-12 pico p
'' 1e-15 femto f
'' 1e-18 atto a
'' 1e-21 zepto z
'' 1e-24 yocto y
''
'' examples results
'' ------------------------------------
'' metric(2000.0, "m") "2.000000km"
'' metric(-4.5e-5, "A") "-45.00000uA"
'' metric(2.7e6, 0) "2.700000M"
'' metric(39e31, "W") "3.9000e+32W"
'perform initial setup
StringPtr := Setup(Single)
'determine thousands exponent and relative tens exponent
x := (exponent + 45) / 3 - 15
y := (exponent + 45) // 3
'if in metric range, do metric
if ||x =< 8
'add digits with possible decimal
AddDigits(y + 1)
'if thousands exponent not 0, add metric indicator
byte[p++] := " "
if x
byte[p++] := metric[x]
'if out of metric range, do scientific notation
else
DoScientific
'if SuffixChr not 0, add SuffixChr
if SuffixChr1
byte[p++] := SuffixChr1
if suffixChr2
byte[p++] := SuffixChr2
'terminate z-string
byte[p]~
PUB FloatToFormat(single, width, dp) : stringptr | n, w2
''Convert floating-point number to formatted string
''
'' entry:
'' Single = floating-point number
'' width = width of field
'' dp = number of decimal points
''
'' exit:
'' StringPtr = pointer to resultant z-string
''
'' asterisks are displayed for format errors
'' leading blank fill is used
' get string pointer
stringptr := p := @float_string
' width must be 1 to 9, dp must be 0 to width-1
w2 := width := width #> 1 <# 9
dp := dp #> 0 <# (width - 2)
if dp > 0
w2--
if single & $8000_0000 or positive_chr
w2--
' get positive scaled integer value
n := F.FRound(F.FMul(single & $7FFF_FFFF , F.FFloat(teni[dp])))
if n => teni[w2]
' if format error, display asterisks
repeat while width
if --width == dp
if decimal_chr
byte[p++] := decimal_chr
else
byte[p++] := "."
else
byte[p++] := "*"
byte[p]~
else
' store formatted number
p += width
byte[p]~
repeat width
byte[--p] := n // 10 + "0"
n /= 10
if --dp == 0
if decimal_chr
byte[--p] := decimal_chr
else
byte[--p] := "."
if n == 0 and dp < 0
quit
' store sign
if single & $80000000
byte[--p] := "-"
elseif positive_chr
byte[--p] := positive_chr
' leading blank fill
repeat while p <> stringptr
byte[--p] := " "
PUB SetPrecision(NumberOfDigits)
''Set precision to express floating-point numbers in
''
'' NumberOfDigits = Number of digits to round to, limited to 1..7 (7=default)
''
'' examples results
'' -------------------------------
'' SetPrecision(1) "1e+0"
'' SetPrecision(4) "1.000e+0"
'' SetPrecision(7) "1.000000e+0"
precision := NumberOfDigits
PUB SetPositiveChr(PositiveChr)
''Set lead character for positive numbers
''
'' PositiveChr = 0: no character will lead positive numbers (default)
'' non-0: PositiveChr will lead positive numbers (ie " " or "+")
''
'' examples results
'' ----------------------------------------
'' SetPositiveChr(0) "20.07" "-20.07"
'' SetPositiveChr(" ") " 20.07" "-20.07"
'' SetPositiveChr("+") "+20.07" "-20.07"
positive_chr := PositiveChr
PUB SetDecimalChr(DecimalChr)
''Set decimal point character
''
'' DecimalChr = 0: "." will be used (default)
'' non-0: DecimalChr will be used (ie "," for Europe)
''
'' examples results
'' ----------------------------
'' SetDecimalChr(0) "20.49"
'' SetDecimalChr(",") "20,49"
decimal_chr := DecimalChr
PUB SetSeparatorChrs(ThousandsChr, ThousandthsChr)
''Set thousands and thousandths separator characters
''
'' ThousandsChr =
'' 0: no character will separate thousands (default)
'' non-0: ThousandsChr will separate thousands
''
'' ThousandthsChr =
'' 0: no character will separate thousandths (default)
'' non-0: ThousandthsChr will separate thousandths
''
'' examples results
'' -----------------------------------------------------------
'' SetSeparatorChrs(0, 0) "200000000" "0.000729345"
'' SetSeparatorChrs(0, "_") "200000000" "0.000_729_345"
'' SetSeparatorChrs(",", 0) "200,000,000" "0.000729345"
'' SetSeparatorChrs(",", "_") "200,000,000" "0.000_729_345"
thousands_chr := ThousandsChr
thousandths_chr := ThousandthsChr
PUB StringToFloat(strptr) : flt | significand, ssign, places, exp, esign
{{
Converts string to floating-point number
entry:
strptr = pointer to z-string
exit:
flt = floating-point number
Assumes the following floating-point syntax: [-] [0-9]* [ . [0-9]* ] [ e|E [-|+] [0-9]* ]
┌── ┌───── ┌─────────── ┌───────────────────
│ │ │ │ ┌──── ┌─────
Optional negative sign ────────────────────┘ │ │ │ │ │
Digits ────────────────────────────────────────┘ │ │ │ │
Optional decimal point followed by digits ────────────┘ │ │ │
Optional exponent ─────────────────────────────────────────────────┘ │ │
optional exponent sign ────────────────────────────────────────────────┘ │
exponent digits ─────────────────────────────────────────────────────────────┘
Examples of recognized floating-point numbers:
"123", "-123", "123.456", "123.456e+09"
Conversion stops as soon as an invalid character is encountered. No error-checking.
Based on Ariba's StrToFloat in http://forums.parallax.com/forums/default.aspx?f=25&m=280607
Expanded by Michael Park
}}
significand~
ssign~
exp~
esign~
places~
repeat
case byte[strptr]
"-":
ssign~~
".":
places := 1
"0".."9":
significand := significand * 10 + byte[strptr] - "0"
if places
++places 'count decimal places nach dem Dezimalpunkt
"e", "E":
++strptr ' skip over the e or E
repeat
case byte[strptr]
"+":
' ignore
"-":
esign~~
"0".."9":
exp := exp * 10 + byte[strptr] - "0"
other:
quit
++strptr
quit
other:
quit
++strptr
if ssign
-significand
flt := f.FFloat(significand)
ifnot esign ' tenf table is in decreasing order, so the sign of exp is reversed
-exp
if places
exp += places - 1
flt := f.FMul(flt, tenf[exp]) 'adjust flt's decimal point
PRI Setup(single) : stringptr
'limit digits to 1..7
if precision
digits := precision #> 1 <# 7
else
digits := 7
'initialize string pointer
p := @float_string
'add "-" if negative
if single & $80000000
byte[p++] := "-"
'otherwise, add any positive lead character
elseif positive_chr
byte[p++] := positive_chr
'clear sign and check for 0
if single &= $7FFFFFFF
'not 0, estimate exponent
exponent := ((single << 1 >> 24 - 127) * 77) ~> 8
'if very small, bias up
if exponent < -32
single := F.FMul(single, 1e13)
exponent += result := 13
'determine exact exponent and integer
repeat
integer := F.FTRUNC(F.FMul(single, tenf[exponent - digits + 1]))
if integer < teni[digits - 1]
exponent--
elseif integer => teni[digits]
exponent++
else
exponent -= result
quit
'if 0, reset exponent and integer
else
exponent~
integer~
'set initial tens and clear zeros
tens := teni[digits - 1]
zeros~
'return pointer to string
stringptr := @float_string
PRI DoScientific
'add digits with possible decimal
AddDigits(1)
'add exponent indicator
byte[p++] := "e"
'add exponent sign
if exponent => 0
byte[p++] := "+"
else
byte[p++] := "-"
||exponent
'add exponent digits
if exponent => 10
byte[p++] := exponent / 10 + "0"
exponent //= 10
byte[p++] := exponent + "0"
PRI AddDigits(leading) | i
'add leading digits
repeat i := leading
AddDigit
'add any thousands separator between thousands
if thousands_chr
i--
if i and not i // 3
byte[p++] := thousands_chr
'if trailing digits, add decimal character
if digits
AddDecimal
'then add trailing digits
repeat while digits
'add any thousandths separator between thousandths
if thousandths_chr
if i and not i // 3
byte[p++] := thousandths_chr
i++
AddDigit
PRI AddDigit
'if leading zeros, add "0"
if zeros
byte[p++] := "0"
zeros--
'if more digits, add current digit and prepare next
elseif digits
byte[p++] := integer / tens + "0"
integer //= tens
tens /= 10
digits--
'if no more digits, add "0"
else
byte[p++] := "0"
PRI AddDecimal
if decimal_chr
byte[p++] := decimal_chr
else
byte[p++] := "."
DAT
long 1e+38, 1e+37, 1e+36, 1e+35, 1e+34, 1e+33, 1e+32, 1e+31
long 1e+30, 1e+29, 1e+28, 1e+27, 1e+26, 1e+25, 1e+24, 1e+23, 1e+22, 1e+21
long 1e+20, 1e+19, 1e+18, 1e+17, 1e+16, 1e+15, 1e+14, 1e+13, 1e+12, 1e+11
long 1e+10, 1e+09, 1e+08, 1e+07, 1e+06, 1e+05, 1e+04, 1e+03, 1e+02, 1e+01
tenf long 1e+00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09
long 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19
long 1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29
long 1e-30, 1e-31, 1e-32, 1e-33, 1e-34, 1e-35, 1e-36, 1e-37, 1e-38
teni long 1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000
byte "yzafpnum"
metric byte 0
byte "kMGTPEZY"
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ TERMS OF USE: MIT License │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}