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. |
| ||
Number Format ? |
| ||
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 : 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) |