Author Topic: defsng math going off the rails, creating a total from list of numbers in file  (Read 6835 times)

0 Members and 1 Guest are viewing this topic.

Offline doppler

  • Forum Regular
  • Posts: 241
    • View Profile
My problem maybe related to charliejv problem except I am getting with just single variables.

Code: QB64: [Select]
  1. _Title "Total it"
  2. Open "c:\qb64\total.txt" For Input As #1
  3. total = 0
  4. t = 0
  5.  
  6.     Line Input #1, t$
  7.     t = Val(t$)
  8.     Print t
  9.     Print Str$(total) + " plus " + t$ + " equals ";
  10.     total = total + t
  11.     Print total
  12. Print total
  13.  

list of numbers for the file total.txt

Code: QB64: [Select]
  1. -750
  2. 21.71
  3. 13.67
  4. 22.92
  5. 91.25
  6. 119.63
  7. 10.84
  8. 18.46
  9. 40.44
  10. 171.73
  11. 3.08
  12. 92.39
  13. 57.20
  14. 22.79
  15. -2.80
  16. 43.87
  17. 34.81
  18. 83.82
  19. 10.85
  20.  

Can't figure out why the math went off after 57.20 number.  I only add extra output to see where it went south.
Any pointers would be helpful.


Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
  [ You are not allowed to view this attachment ]  

Yes that's single doing it's messy thing by adding garbage to the amounts.

You need something to display the numbers in a formatted output. See Steve's suggestions for Charlie.

Offline doppler

  • Forum Regular
  • Posts: 241
    • View Profile
Something like print using "###.##" ?

It's not just the single thing.  Double and float do it too.  Only thing that does not do it is integer. But then I lose all .## 's

I know it's 4/1/22 but the math is like 2+2=3.999999
ps.
Tried using the using "###.##".  Looks right for a formatted output. I still want to know why the math got Borked.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Something like print using "###.##" ?

It's not just the single thing.  Double and float do it too.  Only thing that does not do it is integer. But then I lose all .## 's

I know it's 4/1/22 but the math is like 2+2=3.999999
ps.
Tried using the using "###.##".  Looks right for a formatted output. I still want to know why the math got Borked.

Singles and double value are stored in binary format. When calculations are made with those numbers they are done in binary and then translated back to decimal. The translation is not perfect due to rounding errors between the base math, sorry.

This is why Pete pipes up every now and then and says String Math (pure base 10 calcs) Rules! (even though it's so much slower!)

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
The problem is just like what Charlie was posting about -- it's simply the nature of floating point imprecision at work. 

Computers store values in binary format (Base 2).  <-- This is an immutable fact of how computers perform.

Now, as base 2 values, our values are stored via bits.

1001 <  this is the bit value to represent 9.  That first one is in the (2 ^ 3) position, and that last one is in the (2 ^ 0) position, representing a total value of 9.   In case you've never seen the binary values before, let's count in binary from 0 to 10.

0000   <-- Represents 0
0001 
0010
0011
0100
0101
0110
0111
1000
1001
1010  <-- Represents 10

Each digit is an increase in the power of 2, as you can see from above.  The rightmost bit represents (2^0), and in this case, the leftmost bit represents (2 ^ 3).

(2 ^ 3) + (2 ^ 2) + (2 ^ 1) + (2 ^ 0) is what those 4 bits represent.

But, let's now add decimals to our system!  What would 0001.1001 represent??

(2 ^ 3) + (2 ^ 2) + (2 ^ 1) + (2 ^ 0) + <decimal point placement for reference> + (2 ^ -1) + (2 ^ -2) + (2 ^ -3) + (2 ^ -4)

So the leftmost 1 represents a value of (2 ^ 0), or 1.
The 1 right after the decimal represents a value of (2 ^ -1), or 0.5.
And that last 1 on the far right represents a value of (2 ^ -4), or 0.0625.

Our total says that 0001.1001 represents a decimal value of 1.5625.



Now, here's a logic exercise for you:  Try to add up powers of two until they equal 0.1 (one tenth).

1/2 is too large.
1/4 is too large.
1/8 is too large.
1/16 is too small.  (0.0625) We have to add more onto it...

But at this point, we've now hit a basic mathematical enigma: How do we take infinite halves and add them together to get to 1??

We can't!

1/2 + 1/4 + 1/8 + 1/16 + 1/32 + 1/64 + 1/128 + 1/256 + 1/2^n... will never add up to 1!  You just can't do it!  It'll endlessly approach 1, but you can never get that value back *exactly*...

And that's what's going on when a PC tries to represent 0.1 internally as a binary, floating point number.  It *CAN'T* store that number perfectly -- there's no way to perfectly represent 0.1 in binary format, just as there's no way to perfectly represent 1/3 in decimal format.   1/3, in decimal, is 0.33333333333333333333333333*** with an infinite number of 3s involved!   1/10, in binary, hits the exact same issue -- it simply *CAN'T* be represented properly!

And so, floating point math has rounding errors. 

And the more you add, subtract, or work with those values, the more those rounding errors compound and add up over time, until they become glaringly obvious like in your code.



The fix??

In this case, I simply wouldn't use floating point values.  Using LONGs instead.

Code: QB64: [Select]
  1. _Title "Total it"
  2. Open "c:\qb64\total.txt" For Input As #1
  3. total = 0
  4. T = 0
  5.  
  6.     Line Input #1, t$
  7.     T = 100 * Val(t$)
  8.     Print T / 100
  9.     Print Str$(total / 100) + " plus " + t$ + " equals ";
  10.     total = total + T
  11.     Print total / 100
  12. Print total / 10
  13.  
  14.  

We're working with integer values.  Storing integer values.  Adding and subtracting integer values.  There's no floating point involved here -- UNTIL we get to the display. 

Need absolute precision?

Don't work with floating point variables!  ;)

« Last Edit: April 01, 2022, 02:24:55 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Removing need to download some data, I put in a data statement and sleeping after each total so you can watch from start on screen.

Code: QB64: [Select]
  1. _Title "Total it" ' from Doppler "defsng math going off the rails, creating a total from list of numbers in file"
  2. ' ref: https://qb64forum.alephc.xyz/index.php?topic=4753.msg141698#msg141698
  3.  
  4. ' b+ mod removed file and replced with data line
  5. total = 0
  6. t = 0
  7.  
  8.     Read t$
  9.     If t$ = "EOD" Then Exit Do
  10.     t = Val(t$)
  11.     Print t
  12.     Print Str$(total) + " plus " + t$ + " equals ";
  13.     total = total + t
  14.     Print format$("#,###.##", Str$(total));
  15.     Print " press any to contiue..."
  16.     Sleep
  17. Data -750,21.71,13.67,22.92,91.25,119.63,10.84,18.46,40.44,171.73,3.08,92.39,57.20,22.79,-2.80,43.87,34.81,83.82,10.85,EOD
  18.  
  19. Function format$ (template As String, Source As String)
  20.     Dim d, s, n, i, t$
  21.     d = _Dest: s = _Source
  22.     n = _NewImage(80, 80, 0)
  23.     _Dest n: _Source n
  24.     Print Using template; Val(Source)
  25.     For i = 1 To 79
  26.         t$ = t$ + Chr$(Screen(1, i))
  27.     Next
  28.     If Left$(t$, 1) = "%" Then t$ = Mid$(t$, 2)
  29.     format$ = _Trim$(t$)
  30.     _Dest d: _Source s
  31.     _FreeImage n
  32.  
  33.  
  34.  

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
I hate to be a party pooper but  I avoid anything DEF this or DEF that. Please see some of my old code on this forum, my code is crisp and clean no caffeine - well that is a lie - I drank a pot today and gonna make another.

Offline doppler

  • Forum Regular
  • Posts: 241
    • View Profile
@bplus using read and data statements defeats the whole purpose.  I needed a double check way to verify some number tables.  Using a file with line by line entry of the numbers is faster and reusable.  I only need to change the file and re-execute.


Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
@bplus using read and data statements defeats the whole purpose.  I needed a double check way to verify some number tables.  Using a file with line by line entry of the numbers is faster and reusable.  I only need to change the file and re-execute.

Same difference when talking about Single precision and a Data Read is more convenient to forum folks, maybe next time put the bas and dat file in a zip, just a suggestion.

BTW my format$ fix fails if your totals kept going on and on... eventually the garbage will add up to significant difference specially if you start multiplying. Sorry if I was no help. I try.

Offline doppler

  • Forum Regular
  • Posts: 241
    • View Profile
Sorry if I was no help. I try.
I will quote Mickey Mouse "If at first you don't succeed, try try again."
I have found many alternate ideas to something after asking questions here.  I was never a big fan of the "Not invented here".  Mentality of some companies.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
@doppler have you tried Steve's suggestion with Long Type?

I made a sloppily coded adding machine that has same problem with singles collecting garbage .9999 stuff or .00006 for dollars and cents numbers, think I will try Longs and just show numbers with a decimal inserted 2 digits in from right, see how that works.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Overhauled my adding machine with just Long type for Cents Amounts or values has Dollar$ function to display Dollars and cents of Long Running Total:
https://qb64forum.alephc.xyz/index.php?topic=1639.msg141814#msg141814