Request: Double data types

Blitz3D Forums/Blitz3D Bug Reports/Request: Double data types

I'm guessing the reason doubles were never added to b3d or b+ was because it is a non-trivial task. But just in case it was for some other reason I am requesting native double data types for Blitz3D. There is a dll made by a user here which allows double conversion but this won't work with other dll's that have functions with doubles as parameters, rendering them useless.

I suggest the following syntax for doubles:
mydouble~=0.5

Thanks for your time.

I also want doubles, because I need them for converting fractionals with more precision.

I have to add a correction to this post. I have recently discovered it is possible to pass a double as 2 integers to a function in a dll with a double parameter. This was figured out ages ago actually for OpenGL functions.

I have to add a correction to this post. I have recently discovered it is possible to pass a double as 2 integers to a function in a dll with a double parameter. This was figured out ages ago actually for OpenGL functions.


That could come in handy. What's the syntax for doing that, markcw? Do you just add the singles together in the dll function call (eg. ExampleDllFunctionCall( singlevar1 + singlevar2 ))?

This should explain it.
;Passing Doubles to a DLL in Blitz Example
;Thanks to Michael Reitzenstein

;gluPerspective has 4 doubles for parameters:
;gluPerspective(fovy as Double,aspect as Double,zNear as Double,zFar as Double)

;Blitz does not have an 8-byte data type that you can pass by value
;but this syntax can be used instead:
;blitz_gluPerspective(fovy_hi%,fovy_low%,aspect_hi%,aspect_low%,zNear_hi%,zNear_low%,zFar_hi%,zFar_low%):"gluPerspective"

;The decls file should look like this:
;;Glu32.decls
;.lib "Glu32.dll"
;blitz_gluPerspective(fovy_l%,fovy_r%,aspect_l%,aspect_r%,zNear_l%,zNear_r%,zFar_l%,zFar_r%):"gluPerspective"

;Then a blitz wrapper function is made:
;Function gluPerspective(fovy#,aspect#,zNear#,zFar#)
;The only difference is that you call it with Blitz floats
;instead of doubles.

Function gluPerspective(fovy#,aspect#,zNear#,zFar#)

 Local dblBank,fovy_l,fovy_r,aspect_l,aspect_r
 Local zNear_l,zNear_r,zFar_l,zFar_r

 dblBank=CreateBank(8)

 SngToDbl fovy#,dblBank
 fovy_l=PeekInt(dblBank,0)
 fovy_r=PeekInt(dblBank,4)
 
 SngToDbl aspect#,dblBank
 aspect_l=PeekInt(dblBank,0)
 aspect_r=PeekInt(dblBank,4)

 SngToDbl zNear#,dblBank
 zNear_l=PeekInt(dblBank,0)
 zNear_r=PeekInt(dblBank,4)

 SngToDbl zFar#,dblBank
 zFar_l=PeekInt(dblBank,0)
 zFar_r=PeekInt(dblBank,4)

 FreeBank dblBank

 blitz_gluPerspective fovy_l,fovy_r,aspect_l,aspect_r,zNear_l,zNear_r,zFar_l,zFar_r

End Function

Function SngToDbl( x#, bank )
 ;Thanks to Floyd for this one. His comments:
 ;This should convert all ordinary floats correctly.
 ;The extreme cases +Infinity, -Infinity, NaN
 ;would require special handling.

 Local s, e, m, Lo, Hi, n
 PokeFloat bank, 0, x#
 n = PeekInt( bank, 0 )  ; raw bits of x
 s = n And %10000000000000000000000000000000  ; sign bit
 e = n And %01111111100000000000000000000000  ; 8-bit exponent
 e = (e Shr 3) + %00111000000000000000000000000000  ; 11-bit exponent
 m = n And %00000000011111111111111111111111  ; 23-bit mantissa
 Lo = m Shl 29  ; final three bits of mantissa
 Hi = s Or e Or (m Shr 3 ) ; sign, exponent, first twenty bits of m
 PokeInt bank, 0, Lo
 PokeInt bank, 4, Hi

End Function


Thanks for the info.

I couldn't find a DblToSng function anywhere. This might work but is untested.

Edit: Well it looks like it does work going by this simple test, although it's not incredibly accurate it is sufficient.
;DoubleToSingle Test

dblBank=CreateBank(8)

value#=359.1234
SngToDbl(value#,dblBank)
value_l=PeekInt(dblBank,0)
value_r=PeekInt(dblBank,4)

DblToSng(dblBank)
test#=PeekFloat(dblBank,0)

Print value#
Print test#
WaitKey()
End

Function SngToDbl( x#, bank )
 ;Thanks to Floyd for this one. His comments:
 ;This should convert all ordinary floats correctly.
 ;The extreme cases +Infinity, -Infinity, NaN
 ;would require special handling.

 Local s, e, m, Lo, Hi, n
 PokeFloat bank, 0, x#
 n = PeekInt( bank, 0 )  ; raw bits of x
 s = n And %10000000000000000000000000000000  ; sign bit
 e = n And %01111111100000000000000000000000  ; 8-bit exponent
 e = (e Shr 3) + %00111000000000000000000000000000  ; 11-bit exponent
 m = n And %00000000011111111111111111111111  ; 23-bit mantissa
 Lo = m Shl 29  ; final three bits of mantissa
 Hi = s Or e Or (m Shr 3 ) ; sign, exponent, first twenty bits of m
 PokeInt bank, 0, Lo
 PokeInt bank, 4, Hi

End Function

Function DblToSng( bank )
 ;DoubleToSingle
 ;http://techsupt.winbatch.com/TS/T000001034F21.html
 ;bank -> input as double floating point value
 ;Returns value in bank as single floating point value

 Local DL, DH, Dsign, Ssign, Dexp, Sexp, Sfract
 DL = PeekInt(bank,0)
 DH = PeekInt(bank,4)
 Dsign = Abs(DH Shr 31)
 Ssign = Dsign Shl 31
 Dexp = Abs(DH Shr 20 - (Dsign Shl 11))
 Sexp = Dexp + 127 - 1023
 Sexp = Sexp Shl 23
 Sfract = ((DH And 1048575) Shl 3) + (DL Shr 28)
 PokeInt bank,0,Ssign Or Sexp Or Sfract

End Function


I fixed the accuracy problem and have made a module:
http://www.blitzbasic.com/codearcs/codearcs.php?code=2241

The problem was this line:
Sfract = ((DH And 1048575) Shl 3) + (DL Shr 28)
Should have been this:
Sfract = ((DH And 1048575) Shl 3) + (DL Shr 29)

markcw, incidentally, just wondering if this thread would draw your attention to a problem that I found converting floats to a string: http://www.blitzbasic.com/Community/posts.php?topic=71385#798197

I still haven't got the perfect routine to do so, and would really be grateful for help on this one. I still don't have the perfect routine for this. The goal in the end is to have the ascii codes stored in a bank, and of course the issue is to have a speedy way of doing this, not use string conversion operations.

I don't see what the problem is, all the numbers in the test were as expected.

markcw, I know that's because the for loop was generating a number that the function liked. Try this for loop instead:
For i# = -125.0 To 125.0 Step 15.7


Yeah, I thought that after posting. Where'd you get this code from? Might help.

Oh I tried many attempts at it. It is my code. I have many other routines to convert floats to ascii byte values, but they all give innacurate values due to some problem on how floats are handled in blitz. Also the reason why I use my routine instead of string manipulation and so on is because I work with banks (for a script language parser I made).

Well I had a go but nothing yet. I did however, after some reading, discover how to calculate a float from an integer, and then I found some code to do the reverse. So I added these to my Float2Double module as Float2Int and Int2Float.

See here, I can't properly isolate the values after the decimal point. Blitz returns unusual, results, and there is no function I know in blitz to get only the fraction part of a number:
For i# = -125.0 To 125.0 Step 14.554
ln = write_FLOAT(i)
Next
WaitKey()


Function write_FLOAT%(num_in#)
Local num_upper% = Floor(num_in)
Local num_lower% = (num_in - Float num_upper) * 100000.0
Write num_upper + "." + Abs(num_lower)
End Function


In this case, it starts to break the value after 4 decimals...

See the problem in blitz is there is no reliable function that return the fraction portion of a number. If I had that, it would probably make my life easier for what I need to accomplish.

Here is how I try to study the float values from a binary standpoint:
Graphics 1024,768,32,2
Print "       1 = " + Bin$(Get_Float_Content(1))
Print "      -1 = " + Bin$(Get_Float_Content(-1))
Print "       2 = " + Bin$(Get_Float_Content(2))
Print "      -2 = " + Bin$(Get_Float_Content(-2))
Print "       3 = " + Bin$(Get_Float_Content(3))
Print "      -3 = " + Bin$(Get_Float_Content(-3))
Print "     1.1 = " + Bin$(Get_Float_Content(1.1))
Print "     1.2 = " + Bin$(Get_Float_Content(1.2))
Print "    -1.2 = " + Bin$(Get_Float_Content(-1.2))
Print "    1000 = " + Bin$(Get_Float_Content(1000))
Print "   -1000 = " + Bin$(Get_Float_Content(-1000))
Print "   65535 = " + Bin$(Get_Float_Content(65535))
Print "   65536 = " + Bin$(Get_Float_Content(65536))
Print " 65536.1 = " + Bin$(Get_Float_Content(65536.1))
Print "131072.1 = " + Bin$(Get_Float_Content(131072.1))
Print "262144.1 = " + Bin$(Get_Float_Content(262144.1))
Print "  524288 = " + Bin$(Get_Float_Content(524288))
Print " 1048576 = " + Bin$(Get_Float_Content(1048576))
Print "1048576.1= " + Bin$(Get_Float_Content(1048576.1))
Print "1048576.2= " + Bin$(Get_Float_Content(1048576.2))
Print "1048576.3= " + Bin$(Get_Float_Content(1048576.3))
Print "1048576.4= " + Bin$(Get_Float_Content(1048576.4))
Print "2097152.4= " + Bin$(Get_Float_Content(2097152.4))
Print "2097152.6= " + Bin$(Get_Float_Content(2097152.6))

WaitKey()


Function Get_Float_Content%(value#)
   Local fb% = CreateBank(4)
   PokeFloat(fb,0,value)
   ret% = PeekInt(fb,0)
   FreeBank(fb)
   Return ret
End Function


Detail on the composition of a float: http://steve.hollasch.net/cgindex/coding/ieeefloat.html

Would this help you?

Edit: see below.

So, with this, it is possible to get the whole, and the fraction :P Very nice coding markcw, surprisingly simple and efficient!

EDIT: The only drawback I found is the usage of the value#^value% which is rather very slow when interpreted in bliltz. So, I managed a small efficient Exp10 routine to go with your function :P Just toss in the multiplier, and it will return the multiply factor.
Function Exp10#(value%) ; about 8 times faster than 10^value
   Select value
      Case 0 : Return 1.0          ; 10^0
      Case 1 : Return 10.0         ; 10^1
      Case 2 : Return 100.0        ; 10^2
      Case 3 : Return 1000.0       ; 10^3
      Case 4 : Return 10000.0      ; 10^4
      Case 5 : Return 100000.0     ; 10^5
      Case 6 : Return 1000000.0    ; 10^6
      Case 7 : Return 10000000.0   ; 10^7
      Case 8 : Return 100000000.0  ; 10^8
      Case 9 : Return 1000000000.0 ; 10^9
   End Select
End Function
Basically you'd say:
Return Fraction / Exp10(FractionPart)


Er, thanks but I forgot about very big and very small values and a few other things. So while that code looks right there are bugs. The good news is that I've fixed it.

just found and fixed another bug. I think that's it now but I'm not sure.
another change, removed a bit that wasn't needed.
last edit, fixes a rounding error and adds a cheat if less than 1.

Edit: see below.

markcw, is it me or is FractionPart giving out some irregularities?

Nope, it's not you. There are still some bugs with this code and I'm trying to fix them. You can see the problem if you get random values in a loop and string compare with the fraction until you get an error. The problem with FractionPart is that when you subtract the whole - floor(whole) you don't get the exact fraction, you get an approximation which means sometimes FractionPart will be quite inaccurate. I'm not sure what to do about it yet.

markcw, I got good and bad news about single precision. The good news is I found some good web document about the internals. The article has a C code example which does something that I need. I converted it to Blitz code. The bad news is, I have an imprecision in the fractions, yet again!!!! And it sounds like the same problems that we have generally speaking. The example code follows, and notice my debug prints ;) HINT: The first frac should of been 16384
pf (-772.1)
WaitKey()
End


Function pf(x#)

   Print "float = " + x

   Local fb% = CreateBank(4)
   PokeFloat(fb,0,x)
   xx% = PeekInt(fb,0)
   sign% = xx Shr 31
   expo% = ((xx Shr 23) And $ff) - $7F
   man% = (xx And ((1 Shl 23) - 1)) And $7FFFFFFF ; I removed the sign bit here (maybe pointless)
   man% = man Or (1 Shl 23)

   s$ = ""
   If sign = 1 Then s$ = "-"
   Print s$ + (man Shr (23 - expo)) + "."

   frac% = man And ((1 Shl (23 - expo)) -1)
   base% = 1 Shl (23 - expo) : Print "base:" + base
   c% = 0
   While (frac > 0 And c < 6)
      c = c + 1
      frac = frac * 10 : Print "frac:" + frac
      Print (frac / base)
      frac = frac Mod base
   Wend
   FreeBank(fb)
End Function

Article: http://www.ragestorm.net/blogs/?p=57

Well, I have another version of FloatFraction here. This one may be slightly better but it still isn't 100% accurate. The thing about floats is that they aren't exact, they're a close approximation, so I don't think you're ever going to get exactly what you want. I'm finished with this effort, I don't want to spend any more time on it.

Edit: see below.

Well I somehow have managed to prove myself wrong, again, because I appear to have a version that is 100% accurate. It's a little slower but worth it. I found a way to isolate the wrong values and then round up in these rare cases.

Edit: see below.

Another update to FloatFraction, hopefully the last one. I fixed a bug in the wholepart loop that I created. I also am not returning stuff before the end any more as that doesn't really help.

Edit: see below.

still not 100%, but thumbs up for the effort.

Could you elaborate perhaps?

When I run this last one, I get roughly 1500 errors on a 100 000 run. I have no clue why! Tell me it's not because I have an AMD. I'm using Blitz3D v1.99. By the way, I want to know if you have run the small sample I posted earlier, see if you get errors in the conversion.

The errors in the test loop should actually be correct, it's just that the string compare method can't handle the exponent form.

Yeah, you're right Jacques, there are still errors. It's with numbers less than 1. I had a cheat in where if WholePart was zero then just return Abs(Value#) but then I realized this wouldn't really help if you want to get the exact value so I took it out. I'll try to fix it.

Hey there Mark, here is a little something I did. I got my own version of the FloatFraction running in there which is a very optimized version of one of your early tries. I got a performance bench and a trial error test also. Enjoy!
Local rounding_scale# = 5000.0
scale% = 1000000
;Goto precision_test

.performance_test
printsync "*************** Performance test ***************"
printsync ""
SeedRnd 1
printsync "Calculating leadoff time..."
Local m% = MilliSecs()
For count = 1 To scale
   value# = Rnd(-rounding_scale, rounding_scale)
Next
Local leadoff% = (MilliSecs() - m)
Print ""

SeedRnd 1
printsync "Running first iteration..."
m = MilliSecs()
For count = 1 To scale
   value# = Rnd(-rounding_scale, rounding_scale)
   test# = FloatFraction_33(value)
Next
final% = (MilliSecs() - m) - leadoff
Print "Time used (minus " + leadoff + "ms leadoff): " + final + "ms"
Print ""

SeedRnd 1
printsync "Running second iteration..."
m = MilliSecs()
For count = 1 To scale
   value# = Rnd(-rounding_scale, rounding_scale)
   test# = FloatFraction_markcw(value)
Next
final% = (MilliSecs() - m) - leadoff
Print "Time used (minus " + leadoff + "ms leadoff): " + final + "ms"
Print ""

.precision_test
printsync "**************** Precision test ****************"
printsync ""

Local errors%

printsync "Running first iteration..."
errors = 0

SeedRnd 1
For count = 1 To scale
   value# = Rnd(-rounding_scale, rounding_scale)
   s$ = Str(value#)
   pos = Instr(s$, ".", 1)
   compare$ = "0" + Right(s$, Len(s$) - pos + 1)
   fraction# = FloatFraction_33(value#)
   If compare$ <> Str(fraction#)
;      Print "value# = " + value# + ", fraction# = " + fraction#
      errors = errors + 1
   EndIf
Next
Print "" + errors + " irregularities on " + (count - 1) + " tries"
Print ""


printsync "Running second iteration..."
errors = 0

SeedRnd 1
For count = 1 To scale
   value# = Rnd(-rounding_scale, rounding_scale)
   s$ = Str(value#)
   pos = Instr(s$, ".", 1)
   compare$ = "0" + Right(s$, Len(s$) - pos + 1)
   fraction# = FloatFraction_markcw(value#)
   If compare$ <> Str(fraction#)
;      Print "value# = " + value# + ", fraction# = " + fraction#
      errors = errors + 1
   EndIf
Next
Print "" + errors + " irregularities on " + (count - 1) + " tries"
Print ""

Print "press any key"
WaitKey()
End


Function printsync(txt$)
   Print txt$
   Delay 100
End Function

; **************** The contenders ****************

Function FloatFraction_33#(Value#)
   ;Returns the fractional part of a float as a float
   ;The whole part and sign are removed
   Local WholePart%, e#, fraction#, test#, Whole#
   If Value < 0 Then Value = -Value
   Whole# = Floor(Value)
   test# = 1 ;Work out the number of whole places
   For WholePart = 0 To 6
      If test > Whole Then Exit
      test = test * 10
   Next
   If WholePart > 5 Then
      Return 0 ;Out of range, so no fraction
   Else
      e# = Exp10(6 - WholePart) ;Floats only have 6 meaningful places
      fraction# = Int(Value * e - Whole * e) / e
      If fraction >= 1.0 Then
         Return fraction - 1.0
      Else
         Return fraction
      EndIf
   EndIf
End Function

Function Exp10#(value%) ; about 8 times faster than 10^value
   Select value
      Case 0 : Return 1.0          ; 10^0
      Case 1 : Return 10.0         ; 10^1
      Case 2 : Return 100.0        ; 10^2
      Case 3 : Return 1000.0       ; 10^3
      Case 4 : Return 10000.0      ; 10^4
      Case 5 : Return 100000.0     ; 10^5
      Case 6 : Return 1000000.0    ; 10^6
      Case 7 : Return 10000000.0   ; 10^7
      Case 8 : Return 100000000.0  ; 10^8
      Case 9 : Return 1000000000.0 ; 10^9
   End Select
End Function



Function FloatFraction_markcw#(Value#)
;Returns the fractional part of a float as a float
;The whole part and sign are removed

   Local Temp#, Loop, WholePart, Round#, FractionPart
   Local RoundPart, Pow#, Real, Fraction, Check#, CheckPart

   Temp# = 0.1 ;Work out the number of whole places
   For Loop = 1 To 45
      If Temp# * 10 => Abs(Value#) Then Exit
      Temp# = Temp# * 10
      WholePart = WholePart + 1
   Next

   Temp# = Abs(Value#) ;Work out the number of fractional places
   For Loop = 1 To 45
      If Temp# - Floor(Temp#) <= 0 Then Exit
      Round# = Temp#
      Temp# = Temp# * 10
      FractionPart = FractionPart + 1
   Next

   RoundPart = FractionPart
   If FractionPart + WholePart > 6 And WholePart > 0
      FractionPart = 6 - WholePart ;Floats only have 6 meaningful places
   EndIf

   Temp# = Abs(Value#) ;Calculate fraction
   Pow# = (10 ^ FractionPart)
   Real = (Temp# * Pow#)
   Fraction = Real - (Floor(Temp#) * Pow#)

   Check# = Temp# - Floor(Temp#) ;Work out number of fractional places
   For Loop = 1 To 45
      If Check# - Floor(Check#) <= 0 Then Exit
      Check# = Check# * 10
      CheckPart = CheckPart + 1
   Next

   If Temp# <> Real / Pow# ;Special error checks, don't ask
      If Temp# = Round# / Pow#
         If Int(Round# + 0.001) > Int(Round#)
            If CheckPart = RoundPart
               Fraction = Fraction + 1 ;Round up
            EndIf
         EndIf
      EndIf
   EndIf

   If WholePart > 5 Then Fraction = 0 ;Out of range
   If Fraction / Pow# >= 1.0 Then Fraction = 0 ;Severe rounding error
   Return Fraction / Pow#
End Function


Well, I have a new function, this once's my favorite because it's not so ugly. It won't work for values smaller than 1.0e-004 so I cheat after that and just return Abs(value#). I couldn't fix the inaccuracies in these very small values, but none of the other versions could do this anyway. If you get any errors let me know what range they're in.

;FloatFraction

Graphics 640, 480, 0, 2

CheckSomeValues = 0

If CheckSomeValues
 For loop = 1 To 5
  Select loop
   Case 1 : value# = -0.000000000000000000000000000000000000000000001
   Case 2 : value# = -100000000000000000000000000000000000000.0
   Case 3 : value# = -1.00126
   Case 4 : value# = -0.00000000126
   Case 5 : value# = -370001.6
  End Select
  fraction# = FloatFraction(value#)
  Print "value# = " + value# + ", fraction# = " + fraction#
  Next
 WaitKey()
 End
EndIf

SeedRnd MilliSecs()

For pow = -15 To 15
 For loop = 0 To 10000
  value# = Rnd(-9 * (10 ^ pow), 9 * (10 ^ pow))
  valuestr$ = Float2Str(value#, 1)
  fraction# = FloatFraction(value#)
  fractionstr$ = Float2Str(fraction#, 0)
  If valuestr$ <> fractionstr$
   Print "value# = " + value# + ", fraction# = " + fraction#
   Print " valuestr$ = " + valuestr$ + ", fractionstr$ = " + fractionstr$
   errors = errors + 1
  EndIf
 Next
 Print errors + " errors" + " when pow = " + pow
 errors = 0
Next

Print "done"
WaitKey()
End

Function FloatFraction#(Value#)
 ;Returns the fractional part of a float as a float
 ;The whole part and sign are removed

 Local Number#, Result#, Loop, StartExp
 Local EndExp, Pow#, Fraction#, Round#

 Number# = Abs(Value#) ;Remove the sign
 Result# = 0.1
 For Loop = 1 To 38 ;Get whole places
  If Result# * 10 => Number# Then Exit
  Result# = Result# * 10
  StartExp = StartExp + 1
 Next

 If StartExp = 0 ;Value less than 1
  Result# = Number#
  For Loop = 1 To 45 ;Get fractional places
   Result# = Result# * 10
   If Int(Floor(Result#)) > 0 Then Exit
   StartExp = StartExp - 1
  Next
 EndIf

 EndExp = StartExp - 6
 Pow# = 1
 For Loop = EndExp To -1 ;Get exponent
  Pow# = Pow# * 10 ;Instead of 10 ^ EndExp
 Next

 Fraction# = (Number# * Pow#) - (Floor(Number#) * Pow#)
 Round# = (Number# * Pow#) - (Floor(Number# * Pow#))

 If Int(Round# * 10) = 5 ;Value after last meaningful place
  If Round# >= 0.5
   Fraction# = Fraction# + 0.1 ;Round up
  Else
   Fraction# = Fraction# - 0.1 ;Round down
  EndIf
 EndIf

 Result# = Int(Fraction#) / Pow#
 If Result# >= 1.0 Then Result# = 0.0 ;Special case rounding error
 If StartExp > 37 Then Result# = 0.0 ;Special case for Infinity
 If StartExp < -4 Then Result# = Number# ;Cheat if value very small
 Return Result#

End Function

Function Float2Str$(Value#, RemoveWhole = 0)
 ;Converts a float into a string
 ;RemoveWhole -> Remove whole number flag, 0 = No, 1 = Yes
 ;Removes the exponent form

 Local Number$, Position, Result$, Exponent, Loop, Char$

 Number$ = Str(Abs(Value#))
 Result$ = Number$
 If RemoveWhole
  Position = Instr(Number$, ".", 1)
  If Position
   Result$ = "0." + Right(Number$, Len(Number$) - Position)
  EndIf
 EndIf

 Position = Instr(Number$, "e-", 1)
 If Position
  Result$ = "0."
  Exponent = Right(Number$, Len(Number$) - Position - 1)
  For Loop = 1 To Exponent - 1
   Result$ = Result$ + "0"
  Next
  For Loop = 1 To Len(Number$)
   Char$ = Mid(Number$, Loop, 1)
   If Char$ = "e" Then Exit
   If Char$ <> "." Then Result$ = Result$ + Char$
  Next
 EndIf

 Position = Instr(Number$, "e+", 1)
 If Position
  Result$ = ""
  Exponent = Right(Number$, Len(Number$) - Position - 1)
  For Loop = 1 To Len(Number$)
   Char$ = Mid(Number$, Loop, 1)
   If Char$ = "e" Then Exit
   If Char$ <> "." Then Result$ = Result$ + Char$
  Next
  For Loop = 1 To Exponent - 1
   Result$ = Result$ + "0"
  Next
  Result$ = Result$ + ".0"
  If RemoveWhole Then Result$ = "0.0"
 EndIf

 Return Result$

End Function