Get Real !

BlitzMax Forums/BlitzMax Beginners Area/Get Real !

dw817(Posted 2016) [#1]
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 ?


Derron(Posted 2016) [#2]
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


Brucey(Posted 2016) [#3]
And how would I make a function that would display a real number as it is entered ?

Store it as a string :-)


Yasha(Posted 2016) [#4]
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).


dw817(Posted 2016) [#5]
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 ?


Floyd(Posted 2016) [#6]
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.


Brucey(Posted 2016) [#7]
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.


skidracer(Posted 2016) [#8]
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.


TomToad(Posted 2016) [#9]
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



Floyd(Posted 2016) [#10]
And here is BlitzMax output, after changing to double precision.
0.10000000149011612
0.99099999666213989
1.0909999981522560
I 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.


TomToad(Posted 2016) [#11]
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


Floyd(Posted 2016) [#12]
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 = " + display
Yes, I have a peculiar notion of fun.


dw817(Posted 2016) [#13]
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.


Brucey(Posted 2016) [#14]
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... ?


TomToad(Posted 2016) [#15]
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


TomToad(Posted 2016) [#16]
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


Floyd(Posted 2016) [#17]
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.


dw817(Posted 2016) [#18]
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


Brucey(Posted 2016) [#19]
There are modules for formatting numbers/text.


dw817(Posted 2016) [#20]
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 ?


Yasha(Posted 2016) [#21]
Looks like you just want snprintf, but to use a higher precision specifier than the default.


dw817(Posted 2016) [#22]
Yasha, urff ... in C. Could you please post a translate working example for Blitzmax, and does this visually format real variables correctly ?


Derron(Posted 2016) [#23]
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


Matty(Posted 2016) [#24]
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....


Yasha(Posted 2016) [#25]
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



dw817(Posted 2016) [#26]
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.


Derron(Posted 2016) [#27]
@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


Brucey(Posted 2016) [#28]
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.


Derron(Posted 2016) [#29]
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


dw817(Posted 2016) [#30]
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)


Floyd(Posted 2016) [#31]
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.


dw817(Posted 2016) [#32]
Hi Cap'n. Is there a way to get around this so you don't have to type :Double after your input values ?


Floyd(Posted 2016) [#33]
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.


dw817(Posted 2016) [#34]
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 !


Derron(Posted 2016) [#35]
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


Floyd(Posted 2016) [#36]
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



dw817(Posted 2016) [#37]
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.


Derron(Posted 2016) [#38]
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


dw817(Posted 2016) [#39]
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.


Derron(Posted 2016) [#40]
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


dw817(Posted 2016) [#41]
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.


Derron(Posted 2016) [#42]
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