Formating floating point/double for display
BlitzMax Forums/BlitzMax Programming/Formating floating point/double for display
Sorry "Formatting", anyway...
Hi, has anyone seen any decent floating point number formating functions kicking around in the code archives? I'd like to pass in a floating point number and say have it rounded (without bankers rounding) to one decimal place for example and return a printable string. As you know if you try to store something like 1.4 in float you get 1.39999 and I would like the display to say 1.4.
Last time I checked BMax and BPlus did Bankers Rounding
http://en.wikipedia.org/wiki/Rounding which is why I (well actually Beaker did after I request it) wrote ccRound:
Function ccRound%(flot#)
Return Floor(flot+0.5)
End Function
Thanks very much for any help.
Its actually quite simple to convert to fixed point math, round there and convert it back, assuming you use numbers in a reasonable size (ie number * 10^decimals < MaxFloat, if this does not hold, just make float to double and int to long)
function fRound:float(number:float,decimals:int)
local t:int = 10^decimals
local result:float = int(number * t + 0.5)
return result / t
end function
There are most likely simpler and especially less breaking ways, but so far I didn't have reason to use them.
Thanks Tony (keeper of the archives ;-), looks good but doesn't round. May still use it though, I'll ask Dmaz if I can put it in the framework.
Dreamora: Yeah I've already used that method before in my current game but I didn't stick it in a function. So I was looking for a combination function that can properly round and format floats. Mind if I put that function as is in my framework with a credit to you?
Anyway it doesn't solve the issue where I want the float to be outputted as the correct number and not a float approximation. For example:
test#= 1.4
Print test
Print fround(test,1)
Function fRound:Float(number:Float,decimals:Int)
Local t:Int = 10^decimals
Local result:Float = Int(number * t + 0.5)
Return result / t
End Function
gives
1.39999998
1.39999998
because of course it just gets rounded back to a float. I want the user to see 1.4. I did it before with some messy code but wondered if anyone had made anything neater...
See my problem now?
Here was the messy and non-flexible code I wrote:
Method GetMultiplierStrings(M: Double,part1$ Var, part2$ Var)
'Convert the multiplier number into a string.
'First get the part before the decimal place.
Local temp$ = String(M)
Part1$ = ccFirstStringToSubChop(temp,".")
'Now make sure the number after the decimal is a single digit.
'We need to do this because 1.4 in Double is stored as 1.399999 etc !
'We do this by multiplying by 10 and turning into an int.
Local TempPart2 = Floor(0.05+(Float("0."+temp)*10)) 'round down! (add on a little bit of 0.05 to make sure that .999 numbers go over to the next whole number.)
'Has it rounded up to the next number?
If String(TempPart2) = "10" Then
TempPart2 = 0
Part1 = String(Part1.ToInt()+1)
EndIf
Part2 = String(TempPart2)
End Method
Function ccFirstStringToSubChop$(s$ Var, sub$)
'Pass in a String, this will only return the first part up to,
'but not including, the substring (or end).
'The source string will have the first part and the substring removed.
Local pos% = Instr(s$, sub$)
'If pos = 0 then the end of the string was reached, so return the whole thing.
If pos = 0 Then
Local ret$ = s$
'now clear s$
s$ = ""
Return ret$
Else
Local ret$ = Mid(s$, 1, pos-1)
s$ = Mid$(s$, pos+1, Len(s$)) 'leave remainer in s$
Return ret$
EndIf
End Function
There are 2 ways:
1. Use Double
2. Use a new mingw version (was mentioned to happen with the next version of BM anyway)
The default mingw sadly is that old that it has some problems with float (as you know from 2^intVariable which is caused by the outdated mingw as well)
A rounding function won't be able to fix a pure core limitation I fear.
And yes feel free to include it in your framework if you find use for it. Credit is ok :)
EDIT: your "messy" code at least has the pro right now that it is not cut from working due to float inaccuracies in the math module :)
converting everything to double merely gives this:
1.3999999761581421
1.3999999999999999
What the new mingw fixes this? Hope it doesn't screw up my code that now expects this problem! :-O
Thanks re: including the function in my framework.
You mean my messy code is good because it fixes (for display) the float inaccuracies? Yeah that's why I made it :-)
The new MingW fixes several float inaccuracies and above that raises the math speed by 30-200% depending on the "core support" BRL is targetting (by raising the MinSpecs to P3 you get over 100% in different situations, right now it does not use any extended math commandset from CPU).
Haven't seen any "screw up" so far.
And yes, thats what I meant with my EDIT :)
sounds cool. So I wonder what happens if someone runs it NOT on a P3?
If you want the use to see '1.4' then can't you use the string without converting it back to a float.
what string? I have a float (which I've calculated and could be 1.4 or whatever) and I need it to be converted to a nice rounded string (not just truncated). Am I mssing something there's no string in Dreamora's function? Perhaps I could intercept result before it's divided by t, convert to a string and work out where to truncate and place the decimal point.
Yes if you wanted to get a "nice String" you would use the result upfront the division, make it a string and split it at decimals from right by entering a . there.
Anyone see anything wrong with this? It seems fine to me but I've not extensively tested it:
Strict
Local test#=1.356
Print test
Print ccRoundFloatToString(test,1)
Print ccRoundFloatToString(test,2)
Print ccRoundFloatToString(test,3)
Function ccRoundFloatToString$(number:Float,decimals:Int)
'This gets rid of any floating point inaccuracies such as 1.4 stored in a float
'displaying as 1.39999 etc.
Local t:Int = 10^decimals
Local result% = Int(number * t + 0.5)
'Convert result into a string
Local display$ = String(result)
'Now put in a decimal point at the correct place.
display = Mid(display,1,Len(display)-decimals)+"."+Mid(display,Len(display)-decimals+1,decimals)
Return display
End Function
Gives these results:
1.35599995
1.4
1.36
1.356
Heres what i use to pretty print floats.
Theres probably a better sollution, but it works =)
Function PrettyFloat:String( value:Double)
Extern "C"
Function modf:Double( value:Double, intpart:Double Var)
Function snprintf_i:Int( buf:Byte Ptr, szbuf:Int, fmtstr$z, val:Int) = "snprintf"
Function snprintf_f:Int( buf:Byte Ptr, szbuf:Int, fmtstr$z, val:Double) = "snprintf"
EndExtern
Local i:Double
Local buf:Byte[32]
Local l:Int
' check for whole number
If modf( value, i) = 0.0 Then
l = snprintf_i( buf, SizeOf(buf), "%d", i)
Return String.FromBytes( buf, l)
Else
' eliminate ending zeros except the one after "."
l = snprintf_f( buf, SizeOf(buf), "%f", value)
While (l > 0) And (buf[l-1] = Asc("0")) And (buf[l-2] <> Asc("."))
l :- 1
Wend
' terminate it, just in case
buf[l+1] = 0
Return String.FromBytes( buf, l)
EndIf
EndFunction
EDIT: there was a typo, fixed now!
Thx. I wondered when someone was going to resort to using printf from C.
GA: Looks correct to me, don't see any more or less obvious problem.
0.05 gives odd results in your code GA.<edit> and 50.00
<edit> Knew it had been done before
here <edit2> except that still struggles with 50.0.
Just for interest :
comment from Mark
Here is a corrected code. Note: replaced the int conversion with floor.
Strict
Local test#=50
Print test
Print ccRoundFloatToString(test,1)
Print ccRoundFloatToString(test,2)
Print ccRoundFloatToString(test,3)
Function ccRoundFloatToString$(number:Float,decimals:Int)
'This gets rid of any floating point inaccuracies such as 1.4 stored in a float
'displaying as 1.39999 etc.
Local t:Int = 10^decimals
Local result% = Floor(number * t + 0.5)
'Convert result into a string
Local display$ = String(result)
For Local i:Int = Len(display) To decimals
display = "0" + display
Next
'Now put in a decimal point at the correct place.
display = Mid(display,1,Len(display)-decimals)+"."+Mid(display,Len(display)-decimals+1,decimals)
Return display
End Function
There are surely simpler solutions.
TonyG, good research thanks! That other thread doesn't round though. [edit]Tony I'm confused, I plugged 0.5 and 50 into my latest code and it seemed fine. well 0.5 was .5 (missing leading 0) and 50 was fine. What exactly did you discover? thanks.
Dreamora: aha, I wondered about your use of Int as my ccRound uses Floor. What's the differece though between Int and Floor (apart from with negative numbers), they both round down right? Does it affect my routine for TonyG's test? Thanks.
OK i've fixed it for when you specify 0 as the Decimals. But any number under 1 is messed up big time. Will fix.
Also noticed more weirdness, 1.05001 rounds up to 1.1 (if one decimal place) but 1.05 stays at 1.0 :-(
wow, found out something weird about mid(), see the other thread. Anyway here's a fixed version for 0 (or negative) as the decimals param and also works for numbers < 1 now. Also that 1.05 problem is fixed.
Function ccRoundFloatToString$(number:Float,decimals:Int)
'This gets rid of any floating point inaccuracies such as 1.4 stored in a float
'displaying as 1.39999 etc.
Local t:Int = 10^decimals
Local result% = Floor(number * t + 0.5)
'Convert result into a string
Local display$ = String(result)
'Do we need to append leading zeros for numbers less than 1?
While Len(display)-decimals <= 0
display = "0"+display
Wend
'Now put in a decimal point at the correct place.
If decimals > 0 Then display = Mid(display,1,Len(display)-decimals)+"."+Mid(display,Len(display)-decimals+1,decimals)
Return display
End Function
OK.
GA original code for 0.05 gave :
0.0500000007
.1
5.5
50.50
and for 50.00 or 50 gave :
50.0000000
50.0
50.00
49.950
Dreamora's corrected code for 0.05 gave :
0.0500000007
0.1
0.05
0.050
which seems ok but for 50.00 or 50 gave :
50.0000000
50.0
50.00
49.950
GA latest test for 0.05 gave :
0.0500000007
0.1
0.05
0.050
which, again, seems OK but 50.00 gave :
50.0000000
50.0
50.00
49.950
I could have it wrong but 49.950 seems incorrect.
! your 50 result is WEIRD. This code on my PC:
Local test#=50
Print test
Print ccRoundFloatToString(test,1)
Print ccRoundFloatToString(test,2)
Print ccRoundFloatToString(test,3)
Function ccRoundFloatToString$(number:Float,decimals:Int)
'This gets rid of any floating point inaccuracies such as 1.4 stored in a float
'displaying as 1.39999 etc.
Local t:Int = 10^decimals
Local result% = Floor(number * t + 0.5)
'Convert result into a string
Local display$ = String(result)
'do we need to append a leading zero for numbers less than 1?
'Now put in a decimal point at the correct place.
While Len(display)-decimals <= 0
display = "0"+display
Wend
If decimals > 0 Then display = Mid(display,1,Len(display)-decimals)+"."+Mid(display,Len(display)-decimals+1,decimals)
Return display
End Function
gives:
50.0000000
50.0
50.00
50.000
Thanks for testing btw.
Dreamora: oops, just noticed you added in leading zeros which I did with a while loop. Your for loop should be faster as Len only needs to be evaluated once by the for loop. Changing mine now.
I get 49.950 on my PC and laptop.
Wonder if it's a difference in our Blitz or MinGW or CPUs, how odd. I'll try it on my Macbook Pro.
MacBook pro gives 50 all the way. Are you pulling my leg?
Nope. Using 1.24 fully synced plus MinGW 3.1.0
I'm using 1.24 fully synced. Don't know what MinGW mine is but I haven't upgraded it. My CPU is an Intel btw...
I got this on my PC using GA's test:
50.0000000
50.0
50.00
50.000
Here's the code with some extra print statements :
Local test#=50
Print "Original number : " + test
'Print ccRoundFloatToString(test,1)
'Print ccRoundFloatToString(test,2)
Print "Returned : " + ccRoundFloatToString(test,3)
Function ccRoundFloatToString$(number:Float,decimals:Int)
'This gets rid of any floating point inaccuracies such as 1.4 stored in a float
'displaying as 1.39999 etc.
Print "Number passed to function : " + number
Local t:Int = 10^decimals
Local result% = Floor(number * t + 0.5)
Print "result% : " + result
'Convert result into a string
Local display$ = String(result)
Print "Display : " + display
'do we need to append a leading zero for numbers less than 1?
'Now put in a decimal point at the correct place.
While Len(display)-decimals <= 0
display = "0"+display
Wend
If decimals > 0 Then display = Mid(display,1,Len(display)-decimals)+"."+Mid(display,Len(display)-decimals+1,decimals)
Return display
End Function
and just to confirm that this :
Local number:Float = 50.0
Local decimals:Int=3
Local t:Int = 10^decimals
Local result% = Floor(number * t + 0.5)
Print "t : " + t
Print "number : " + number
Print "result% : " + result
gives :
t : 999
number : 50.0000000
result% : 49950
<edit> Finally,
t1:Int = 10 ^ 3
Print t1
Local decimals:Int = 3
t2:Int = 10 ^ decimals
Print t2
produces
1000
999
I am sure I remember this being raised before.
...
t : 1000
number : 50.0000000
result% : 50000
1000
1000
Pretty strange...
You have an AMD. Don't know for how far back this holds, but if your CPU has one, install the cool'n'quiet driver.
otherwise calculation and timing errors are assumed to be common.
Amon installed those drivers and it got rid of some problems he was having, can't remember what though. To think that floating point maths would be affected just seems crazy! (if true)
Hmmm. Don't think I can use Cool'n'Quiet with 2700+.
OK, CPU drivers in general.
Intel drivers come with Windows, AMDs don't, thats why Intel CPUs don't suffer this problems.
And its not crazy that this happens. Especially as long as programmers behave like morrons and use high resolution timer routines clearly marked by MS, Intel and AMD to be avoided for exactly this reason (high res are high broken with a dynamically stepped cpu)