Get Real !
BlitzMax Forums/BlitzMax Beginners Area/Get Real !
| ||
Precision in numbers is very important when your code calls for Floating Point values. Now every programming language I've used in the past has always calculated and displayed Real numbers without fail and to the exact contents you give them. Apparently this is not the case with BlitzMAX as you can see in this simple example: Strict Local a#,b#,c# a#=.1 Print a# b#=.991 Print b# c#=a#+b# Print c# printreal c# Function printreal(a#) Local b$=String(a#),i,c For i=Len(b$)-3 To 1 Step -1 If b$[i-1]=48 Then c=1 If c=1 And b$[i]<>48 Then Exit Next Print Left$(b$,i+1) EndFunction You can see the trailing digits, and b# rounds to .990, not even .991. I guess one question I have, is this just a fault of PRINT or are these numbers accurate (or inaccurate) as the case might be ? And how would I make a function that would display a real number as it is entered ? |
| ||
Floating Point Determinism 240.0 + 0.01 = 240.009995 ??? Maybe that helps - and answers most of the questions. I use "mapm.mod" (one of Brucey's modules) to work with floating point numbers - when I need the reliability of getting the same results (...okay, for my game this is on "todo" :-)). bye Ron |
| ||
And how would I make a function that would display a real number as it is entered ? Store it as a string :-) |
| ||
1) BlitzMax uses the same representation for reals as every other mainstream language, IEEE-754. You can choose between single and double precision (you're using single precision above, use :Double or ! as the type tag for double precision). 2) There's no such thing as a language that actually supports real numbers accurately. (It might be that you used a language with support for rationals or fixed-point math, which can give perfect accuracy in some cases, but are more limited in most.) The set of reals is infinite between any two points, after all. 3) The Print function does indeed round a couple of digits off the end of real numbers to make them easier on the eye (I think this is a libc thing rather than a BlitzMax thing, can't remember). The number you get out with Print is not necessarily the number being stored in the actual floating value. Floating point numbers are primarily intended for performance. They are accurate, but in their own idiosyncratic way: if you need "intuitive" accuracy, you need to use a non-native number format. This is true for every language - some of them (Scheme, most famously) simply hide this fact behind a layer of "do what I mean". This usually comes at the cost of being able to choose representations for best performance, though. Usually, you also don't need "intuitive" accuracy (e.g. you can often limit the extent to which you ever need to "read back" data after it's entered a system so you always work with the clean input). |
| ||
Looking at Brucey's Library, WOW ! That's quite a bit of coding ! Now Ron, I do have a sort of simple solution in mind. it's not perfect, but it does work. Still rather surprising that PRINT seems to have this problem. Instead of using a MOD library, had someone simply updated BlitzMAX and how real numbers are handled since ? |
| ||
It is unfortunate that BlitzMax, and other Blitzes, don't have any formatting options for displaying floating point numbers. If you want anything other than the default you will have to implement it yourself. Blitz3D used to round the display to six digit accuracy and then delete trailing zeroes. That looked nice enough but was sometimes misleading since it is as not as accurate as possible. It also meant that converting a float to a string and then back to a float might not recover the original value. This would be an issue with saving values to a file in plain text form. BlitzMax defaults to a more accurate, less visually appealing display. It always uses nine digits for single precision or seventeen digits for double. This guarantees that a conversion such as float to string to float recovers the original value. To elaborate on Brucey's post, floating point numbers are (usually) approximations and there is no way to know which approximation you want. Blitz3D was essentially trying to guess the answer by rounding to six digits. But the fact is that 0.1 is not exactly representable in binary. It is approximated as 13421773 / 134217728. The denominator is 2^27. If the denominator could be 134217730 then x would be 0.1 exactly. But 134217730 is not a power of two, so that can't happen. Of course 0.1 works just fine in the human-centric base ten world since it is exactly 1/10. Other values such as 1/3 do not. You have to approximate that as 0.3333333333 using however many threes will fit in the format you are using. |
| ||
Instead of using a MOD library, had someone simply updated BlitzMAX and how real numbers are handled since ? See Yasha's post above... BlitzMax is not broken. |
| ||
I guess one question I have, is this just a fault of PRINT or are these numbers accurate (or inaccurate) as the case might be ? Instead of always jumping to the conclusion that BlitzMax is wrong, why not ask why BlitzMax behaves like this? In binary, 1/10 is a recurring decimal and cannot be stored accurately. If a game stored the value of 1/10 using rounded mode instead of exact decimal representation the value would be different when loaded back into the program. This is critical for serialisation and networking when you need values to be exactly the same before and after conversion. The fact BlitzMax does not provide an alternative rounded conversion for human readable purposes I suppose could be declared a shortcoming. |
| ||
Now every programming language I've used in the past has always calculated and displayed Real numbers without fail and to the exact contents you give them. You must have a very short memory. Typed in the above into QBasic and got nearly the same results as in BlitzMAX. QBasic code a# = .1 PRINT a# b# = .991 PRINT b# c# = a# + b# PRINT c# QBasic results .1000000014901161 .9909999966621399 1.090999998152256 BlitzMAX result from code above 0.100000001 0.990999997 1.09099996 |
| ||
And here is BlitzMax output, after changing to double precision.0.10000000149011612 0.99099999666213989 1.0909999981522560I see the QBasic display is always rounded to 16 digits. That no doubt means it was storing numbers in double precision. It also means double to string to double would sometimes not be exact. |
| ||
There is a single precision mode on QBasic. I guess it prints to 8 digits. With rounding to 8 digits, you get.10000000 .99100000 1.0910000 Which would be the results you expect, but even though the rounding will push the numbers to the expected values, internally, they are probably sill being stored with the extra digits. Doing a little testing, yeup, just as I expected. QBasic code n! = 1! FOR i% = 1 TO 10000 n! = n! + .01 NEXT FOR i% = 1 TO 20000 n! = n! - .01 NEXT FOR i% = 1 TO 10000 n! = n! + .01 NEXT PRINT n! prints .9999994 |
| ||
Just for fun...' Single precision approximation of 0.1 is 13421773 / 134217728 num% = 13421773 denom% = 134217728 display$ = "0." While num num :* 10 digit% = num / denom num = num - digit * denom display :+ digit Wend Print Print "Exact value of single precision approximation of 0.1 = " + displayYes, I have a peculiar notion of fun. |
| ||
Tom, that can't be right ! I remember printing decimal values in Q-Basic years ago and it was fine. In fact, I have one program I have not posted yet which gets a GOOD Hyperspace effect, but it uses real numbers, and I printed them on the screen for debugging. Now I'm wondering if DOS BOX or otherwise is flawed in their interpretation of real numbers. Surely the QBasic I used that many years ago gave .1 for a .1 real value entry. |
| ||
Now I'm wondering if DOS BOX or otherwise is flawed in their interpretation of real numbers. Yes, DOS Box's x86 emulation must be broken... ? |
| ||
Not necessarily. If the number is stored as .1000000000001, but is only printed to 8 decimals, then it will show .1. If .12 is stored as .119999999999999, then printed to 8 decimals, you get .12. That may be why you are seeing correct results even when it is stored wrong. Fact is, a computer can't possibly store real numbers accurately any more than we can write it down. Try writing 1/3 down as decimal. No matter how many 3s you put after the dot, it will still be infinite 3s short from accurate. Eventually we will stop at some point and call it accurate enough. Since computers are base 2, and not base 10, those inaccuracies affect some values that have an accurate base 10 representation. I am sure you are mis-remembering how reals act in programming. I've been programming since the Commodore 64 (actually dabbled in programming on the VIC-20, but didn't fully get into it until I got my C64) and even then, floating point number inaccuracies had to be taken into account. Every system I have had since, C128, Amiga, PC 286, 486, Pentium, etc... have had issues with floating point numbers. If you want more information about how exactly floating point numbers are stored, I found this site. http://steve.hollasch.net/cgindex/coding/ieeefloat.html |
| ||
Yes, DOS Box's x86 emulation must be broken... ? Must've written their floating point algorithms in BlitzMAX as it seems to give the same result. :-D |
| ||
Google to the rescue again. Apparently QBasic has PRINT USING, so displaying 0.1 is possible. Just about any language will be using the floating point hardware for calculations. The differences arise in output formatting. The last time I used a language which did not operate this way was back when floating point hardware was an expensive add-on. |
| ||
Floyd, Phew ! Yeah, that must've been what I used. Dang, any small code to make a print using ability for BlitzMAX ? That would save the day ! http://www.antonis.de/qbebooks/gwbasman/printusing.html |
| ||
There are modules for formatting numbers/text. |
| ||
Hi Brucey. Any that can be posted here not requiring outside LIBS or MODS ? AND fix the visual results for REAL variables like QBasic did ? |
| ||
Looks like you just want snprintf, but to use a higher precision specifier than the default. |
| ||
Yasha, urff ... in C. Could you please post a translate working example for Blitzmax, and does this visually format real variables correctly ? |
| ||
Mixing a bit of my code and one from the Diddy-authors (monkey framework). (added spaces after https:// as blitzmax forum autoconverts to <a>-code else.. really do not understand why they manipulate content of a <code>-box...) Output: price 12.124 My name is John, write it big! JOHN is 12 bye Ron |
| ||
The very first thing I was taught in my undergraduate mathematics degree in 1995 when learning techniques for solving differential equations numerically was that floating point numbers have precision limitations as everyone has said above.... |
| ||
Function DoubleToString:String(d:Double, precision:Int) Extern "C" Function snprintf_d:Int(out:Byte Ptr, size:Int, format$z, precision:Int, d:Double) = "snprintf" End Extern Local out:Byte[32 + precision] Local written:Int = snprintf_d(out, out.Length, "%.*f", precision, d) Return String.FromBytes(out, written) End Function Local a:Double = 0.1, b:Double = 6.5, c:Double = 0.123 Print (a + b + c) For Local i:Int = 10 To 30 Print DoubleToString(a + b + c, i) Next |
| ||
Hi Ron: Tried this. Print numbertostring(4585837.328)Result was 4585837.50 ... Not quite right. Yasha, your code gives this:C:/David/BlitzMax-1.50/lib/libmingwex.a(pformat.o):pformat.c:(.text+0x355): undefined reference to `__umoddi3' C:/David/BlitzMax-1.50/lib/libmingwex.a(pformat.o):pformat.c:(.text+0x377): undefined reference to `__udivdi3' Build Error: Failed to link Can't run it the way it is. |
| ||
@4585837 Think the problem here is the number + the approach. If you replace Function NumberToString:String(value:Double, digitsAfterDecimalPoint:int = 2, truncateZeros:int = False) with Function NumberToString:String(value:Double, digitsAfterDecimalPoint:int = 2, truncateZeros:int = False) print value You will see something odd: "4585837.5000000000" So this means, even the "passing" of the double somehow did not went troublefree. So with this being messed up somehow, the other stuff cannot correct that. With smaller values, it seems to work again. Also try: print (double(4585837.328) - double(4585837.0)) result = 0.50000000000000000 bye Ron |
| ||
So this means, even the "passing" of the double somehow did not went troublefree It went fine. You've declared a Float value. If you want to specify your large decimal number as a Double, you will need to suffix it with ! or :Double. |
| ||
Ok, so my brackets just casted it to a double, but the declaration already was flawed... (should really unwind my brain before trying to help) Thanks Brucey Print numbertostring(4585837.328:Double, 2) result: 4585837.33 I always thought it "magically" uses the right datatype - but then "10" would be assumed to be a byte...hmm Edit: to make that printF work, replace: 'Ronny: replaced code with a rounding one from ' our framework Local df:Float = Float(args[argnum]) with 'Ronny: replaced code with a rounding one from ' our framework Local df:Double = Double(args[argnum]) I also adjusted my above's "complete example" code bye Ron |
| ||
I am not seeing why you have to declare the number 4585837.328 as a double if the argument it goes to is already set for double ? Function NumberToString:String(value:Double, digitsAfterDecimalPoint:Int = 2, truncateZeros:Int = False) |
| ||
Because that's the way the language is defined. I'm not saying I agree with it. When BlitzMax was new it was promoted as a language for game programming although it has become much more than that. I assume the gaming emphasis is the reason floating point literals default to single precision. |
| ||
Hi Cap'n. Is there a way to get around this so you don't have to type :Double after your input values ? |
| ||
There's no way to avoid specifying double precision, but you can use the shorter forms 4585837.328! 4585837.328## 4585837.328:Double all mean the same thing. |
| ||
Floyd: This is very acceptable. I can use the exclamation mark for constants and 'real' variables. Local d!=.0001 Print d! Print numbertostring(d!,4) 9.9999997473787516e-005 0.0001 Didn't know about the "!" shortcut - thanks ! |
| ||
I did not check it...but is Local d! = 0.001 really enough... or does it only declare "d" to hold a double but the assigned value is a float (gets filled up with 0s)? So would Local d! = 0.001! be needed? Obviously this small number does not show the need..but more numbers after the point should show up a difference...doesn't? Bye Ron |
| ||
So would Local d! = 0.001! be needed? Yes. In the following example the numeric literal is single precision so Pie has only seven correct digits. Pie! = 3.1415926535897932 Print Print 3.1415926535897932! Print Pie |
| ||
Derron, do I mention you for the NumberToString() function ? I want everyone to receive credit for their work whenever I use them in my code. This one, for instance, will be good for building a programming language where you need a decent real number output. |
| ||
Source: https://github.com/GWRon/Dig/blob/master/base.util.math.bmx (optional: Author: Ronny Otto) Licence: ZLib/libPNG (or the complete text at the begin of the source-file...) should be enough. If you find an error in it - feel free to report. bye Ron |
| ||
Ronny Otto it is, thanks ! I have in mind a game programming language written in BlitzMAX where all elements on the screen have their own identity and definition. Further you can load or save the current state of variables in addition to map files and screen data. |
| ||
To save the state of variables I strongly suggest to use Brucey's Persistence.mod. Base class for all entities on your screen: TEntity Field rect:TRectangle 'pos + dimension Field data:TData ... End Type (data is a TMap-Wrapper in my case, with "Getters" and "Setters" for various base types) bye Ron |
| ||
I'll be honest, Derron. I don't understand it. I think Brucey said that you cannot save a single untyped variable with his code, only Types. My routine (top of the page) won't save types but does save common and simple variables. |
| ||
Yes you cannot save single untyped variables - which is why you save the entities itself (TEntity with all its properties). Imagine you have a TList full of TEntity-instances. With Persistence.mod (or via "import", if you comment out the module-commands in the file) you then just serialize this list. You get an xml describing the list and its contents. When deserializing you get back this list with all of its content. What you should _not_ serialize are things like TImage and so on - because on other machines you might not have OGL but DX - and they will not work together very nice. This is why I added some meta-data-processing to the Persistence.mod, so you could attach {nosave} to every "Field" and it wont get serialized (eg cache-/temp- data): https://github.com/GWRon/Dig/blob/master/external/persistence.mod/persistence.bmx Even if your routine is handy for simple variables or not - once you want to save information about every type, about the game state ... you end up storing them in some Collections or "TGameState" (aka SaveGame :-)). You then serialize your game when "saving" and deserialize when "loading". After loading, you should traverse through all objects and reinitialize the images/media as they might be broken now (they seem to work often, when _not_ restarting the app meanwhile - as things stay in memory until there is no reference anymore - which the deserialization brings back - if you do not use {nosave} for such media-properties). If you do not understand that now: try to understand it. If you still do not understand, then that information is a bit over your current skill (in that area) and you might want to come back in a view days, with your experience having grown a bit more (in that direction). Alternatively ask someone with a better knowledge of the English language, as mine seems to be a bit limited (which makes it harder to understand). bye Ron |