robhagemans / pcbasic

PC-BASIC - A free, cross-platform emulator for the GW-BASIC family of interpreters
http://www.pc-basic.org
Other
396 stars 48 forks source link

weird math approximation #143

Closed theruler closed 2 years ago

theruler commented 2 years ago

It happens in 1.2.14 and 2.0.3, I don't know if it was an original "issue" and if it is an issue at all.

If I do a simple math like ?.6+.1 GW BASIC returns .7000001 all other combinations are OK but if I put this math in a for cycle, something weird happens: M=0:FOR A=1 TO 120:M=M+.1:?M;:NEXT

.1 .2 .3 .4 .5 .6 .7000001 .8000001 .9000001 1 1.1 1.2 1.3 1.4
1.5 1.6 1.7 1.8 1.9 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9
2.999999 3.099999 3.199999 3.299999 3.399999 3.499999 3.599999 3.699999 3.799999 3.899999 3.999998 4.099999 4.199999 4.299999 4.399998 4.499998 4.599998 4.699998 4.799998 4.899998 4.999998 5.099998 5.199998 5.299998 5.399997 5.499997 5.599997 5.699997 5.799997 5.899997 5.999997 6.099997 6.199997 6.299997 6.399997 6.499996 6.599996 6.699996 6.799996 6.899996 6.999996 7.099996 7.199996 7.299996 7.399996 7.499995 7.599995 7.699995 7.799995 7.899995 7.999995 8.099995 8.199995 8.299996 8.399996 8.499996 8.599997 8.699997 8.799997 8.899998 8.999998 9.099998 9.199999 9.299999 9.4 9.5 9.6 9.700001 9.800001 9.900002 10 10.1 10.2 10.3 10.4 10.5
10.6 10.70001 10.80001 10.90001 11.00001 11.10001 11.20001 11.30001
11.40001 11.50001 11.60001 11.70001 11.80001 11.90001 12.00001
Ok 

Could someone explain the logic?

Marrin commented 2 years ago

@theruler That's to be expected because 0.1 can't be represented exactly internally in the binary format. See https://floating-point-gui.de/ for more details you probably want to know. :-) The error can get larger when adding repeatedly the not quite 0.1 value. You get better, but not perfect results by multiplying with A instead:

M=0:FOR A=1 TO 120:M=A*.1:PRINT M;:NEXT

 .1  .2  .3  .4  .5  .6  .7  .8  .9  1  1.1  1.2  1.3  1.4  1.5  1.6  1.7  1.8 
 1.9  2  2.1  2.2  2.3  2.4  2.5  2.6  2.7  2.8  2.9  3  3.1  3.2  3.3  3.4 
 3.5  3.6  3.7  3.8  3.9  4  4.1  4.2  4.3  4.4  4.5  4.6  4.7  4.8  4.9  5 
 5.1  5.2  5.3  5.4  5.5  5.6  5.7  5.8  5.9  6  6.1  6.2  6.3  6.4  6.5  6.6 
 6.7  6.8  6.9  7  7.1  7.200001  7.3  7.4  7.5  7.6  7.700001  7.8  7.9  8 
 8.100001  8.2  8.3  8.4  8.5  8.6  8.7  8.8  8.9  9  9.1  9.2  9.3  9.4  9.5 
 9.6  9.7  9.8  9.9  10  10.1  10.2  10.3  10.4  10.5  10.6  10.7  10.8  10.9 
 11  11.1  11.2  11.3  11.4  11.5  11.6  11.7  11.8  11.9  12 
Ok
theruler commented 2 years ago

WOW, that's a thing I was totally unaware of, thanks for pointing me to the clarification!

Cheers

robhagemans commented 2 years ago

One reason why you will notice this more in PC-BASIC (or GW-BASIC) than in modern languages is that BASIC uses 4-byte single-precision by default, while the standard is now 8-byte double-precision floating point. You'd get better precision using doubles:

M#=0:FOR A#=1 TO 120:M#=A#*.1:PRINT M#;:NEXT

A further interesting detail is that GW-BAsIC and PC-BASIC use MBF floating point, which predates the IEEE 754 standard that is now used everywhere. https://en.wikipedia.org/wiki/Microsoft_Binary_Format