Author Topic: I don't often work with numbers, but when I do....  (Read 7670 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: I don't often work with numbers, but when I do....
« Reply #15 on: September 10, 2021, 10:07:32 pm »
Didn't we go over rounding to some decimal place a number of times, 2.99999 I think.

We have, but nobody real cares about it. 

People think in base-10 and they’re just not interested to learn why their pc thinks in base-2.  It’s simply easier to bitch about the issue being a glitch and report it as a bug, than it is to understand that it’s expected behavior.

Folks could resolve a lot of these issues by simply just adding a _DEFINE A-Z AS _FLOAT at the top of their code and/or limiting input.

If an input of .49999999 reaches the limits of your single precision variable and rounds to .5, write your code to limit precision to one less decimal point.

.999999999 might be the same internal value as 1, but
.999999990 probably isn’t, if you’re limiting your user input properly.

People just need to learn: Floating Point Precision isn’t PERFECT PRECISION. 
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #16 on: September 11, 2021, 01:57:40 am »
In regards to limiting input, that is an issue with the INPUT statement, because you can't manipulate it. So back we go to using the LINE INPUT, where the number is input as a string, and therefore is not manipulated by base 2 rounding. Without any declaring of the variable type, INPUT assigns the variable as SINGLE. BTW - I find it interesting that PRINT without a variable type declaration does not follow this same convention. Instead, PRINT assigns the variable as DOUBLE, go figure. I would think INPUT and PRINT should be on the same page here, but they clearly are not. You would need to define all INPUT variables as DOUBLE to be equivalent to PRINT. Meaning INPUT a# would allow .499999999999999 to remain that number after INPUT, as well as PRINT .499999999999999 would display the same. Add one more "9" to the end of either, and they both reach the max of their DOUBLE precision limits, and would both be rounded to .5.

So where should we go with this. Well, since you guys seem to appreciate graphics more than I, my recommendation would be to modify the INPUT statement so when the user is about to type that last digit, the Lost in Space robot appears in the IDE, waves its  arms frantically, and shouts, "Danger! Danger!" 

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #17 on: September 11, 2021, 12:32:44 pm »
Oh sorry to get to Pete's point so slowly, it's about the inconsistency between how Print handles a number literal and how Input handles the same? maybe?

@Pete If so, don't let it hobgoblin you, use INPUT$  :-))

But if you do use INPUT ""; var##

Here's a nice rounding of a _Float from Steve (I may have modified it, it says simplified):
Code: QB64: [Select]
  1. For i = -3 To 12
  2.     Print i, Round##(12345.67899999, i);
  3.     Locate , 30: Print _Round(12345.67899999)
  4.  
  5. Function Round## (num As Double, digits As Long) ' Steve's simplified
  6.     Round## = Int(num * 10 ^ digits + .5) / 10 ^ digits
  7.  



« Last Edit: September 11, 2021, 12:34:46 pm by bplus »

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #18 on: September 13, 2021, 11:55:08 am »
Applying that method, I see the following problem...

Code: QB64: [Select]
  1. i = 3
  2. a = .01
  3. FOR j = 1 TO 200
  4.     b = b + a
  5.     PRINT j, Round##(b, i);
  6.     LOCATE , 30: PRINT _ROUND(b)
  7.     IF j MOD 20 = 0 THEN SLEEP
  8.  
  9. FUNCTION Round## (num AS DOUBLE, digits AS LONG) ' Steve's simplified
  10.     Round## = INT(num * 10 ^ digits + .5) / 10 ^ digits
  11.  

This application of your code is used in counting 200 pennies, added one at a time, as in a ledger app. Note it fails when the tallies are at 7-cents, 56-cents, and 81-cents. I tackled this problem some 30+ years ago, when I wrote the ledger part of my office software. Either I accomplished it back then, or I just got darn lucky over 10 years of using it.  I don't have those routines on my present computer, but now I'm curious. I do recall some rounding methods do just as this one you posted. They fix most of the rounding base two to base ten counting errors, but not the famous 7-cents problem.

For all the good-hearted folks here, please note I do not need this solved, so don't bust you humps on my behalf. Rather, take your time to help others, or to further your own projects.

Oh, and if I somehow just did not apply the code function properly, just lets me know.

Pete

Pete

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #19 on: September 13, 2021, 12:10:48 pm »
Ha! looks like you used it correctly, either exponent notation or extra zero's come in out of nowhere and screw up a screen. It's enough to drive you to string math! :)

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: I don't often work with numbers, but when I do....
« Reply #20 on: September 13, 2021, 12:17:29 pm »
Ha! looks like you used it correctly, either exponent notation or extra zero's come in out of nowhere and screw up a screen. It's enough to drive you to string math! :)

Or, just keep in mind, floating point numbers are *always* going to be imprecise figures.  If you need to properly count all your pennies, use INTEGER values just like the banks do.

$1.23 = 123 pennies.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #21 on: September 13, 2021, 01:19:00 pm »
I think I did something like this 30 or so years ago...

Code: QB64: [Select]
  1. WIDTH 80, 42
  2. a = .01
  3. FOR i = 1 TO 200000
  4.     fail = a + fail
  5.     b = a * 100 + b * 100
  6.     b = INT(b + .05) / 100
  7.     PRINT i; "  "; b;: LOCATE , 25: PRINT "w/o adj ="; fail
  8.     IF i MOD 40 = 0 THEN SLEEP
  9.     IF INKEY$ = CHR$(27) THEN END
  10.  

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #22 on: September 13, 2021, 01:31:48 pm »
Very interesting. I tried INPUT, it can SINGLE, DOUBLE and _FLOAT, but really _FLOAT values can't be read back in full. The only way to do this is to read all 32 bytes of the FLOAT value using MEM and then compute it via the binary decomposition of these 32 bytes. In fact, this thread gives me the answer to the question why someone in the distant past had a question about how to do this, how to count numeric values from byte values in memory. STR$ converts the FLOAT value as if it were a DOUBLE value, attempting to get this value using _CV (_FLOAT, nr$), where nr$ is 32 bytes obtained using _MEM, cannot be verified for the same reason with the PRINT command ...

Basically, I'd say it's done this because of speed. But it's interesting and important for me to know this (maybe I'll remember when I come across it someday).
« Last Edit: September 13, 2021, 01:32:51 pm by Petr »

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #23 on: September 15, 2021, 07:16:05 pm »
PRINT 7! / 100! ' Single is the only one that will display correctly as .07.
PRINT 7 / 100 ' By default, PRINT is double-precision.
PRINT 7& / 100& ' Long
PRINT 7# / 100# ' Double
PRINT 7&& / 100&& ' Integer64
PRINT 7## / 100## ' Float
a = 7 / 100: PRINT a ' Where variable "a" is by default defined as single (!), so the default double of the print statement is negated.

Well isn't that special? So the snippet I posted earlier depends on using only the default single type declaration to work, and that means it is limited to 32,767. So whatever I did some 30 years ago, it wasn't that. Maybe I did go with string math back then.

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #24 on: September 16, 2021, 02:12:52 pm »
Quote
Code: QB64: [Select]
  1. a = 7 / 100: PRINT a ' Where variable "a" is by default defined as single (!), so the default double of the print statement is negated.

Hmm... did we get to the basic problem now? Print defaults to double when not guided by variable types.

Yes
Code: QB64: [Select]
  1. i = 3
  2. a = .01
  3. For j = 1 To 200
  4.     b = b + a
  5.     Print j, Round##(b, i);
  6.     r! = Round##(b, i)
  7.     Locate , 40: Print r!
  8.     If j Mod 20 = 0 Then Sleep
  9.  
  10. Function Round## (num As Double, digits As Long) ' Steve's simplified
  11.     Round## = Int(num * 10 ^ digits + .5) / 10 ^ digits
  12.  
« Last Edit: September 16, 2021, 02:16:24 pm by bplus »

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #25 on: September 16, 2021, 04:43:56 pm »
Well it's prettier than mine, but it accomplishes the same. Neat trick to eliminate the computational erroneous digits, by converting back to a SINGLE type variable, but this still means users are limited to totals of 32,767 before the routine is prone to throwing incorrect results. For instance, going ahead penny by penny, the routine as modified below, starting at b = 32767, has this result after 119 additions = 32768.2. The correct result is supposed to be 32768.19, not 32768.2. The routine displays the following...

...
118  32768.18
119  32768.2
119  32768.21

 So again, there is too much limitation for practical use with any of these methods when we have to rely of the reported results using the SINGLE variable type.

Code: QB64: [Select]
  1. _SCREENMOVE 0, 0: WIDTH 80, 42
  2. a = .01
  3. b = 32767
  4. FOR j = 1 TO 120
  5.     b = b + a
  6.     r! = Round##(b, i)
  7.     PRINT j; r!
  8.     ' IF j MOD 40 = 0 THEN SLEEP
  9.  
  10. FUNCTION Round## (num AS DOUBLE, digits AS LONG) ' Steve's simplified
  11.     Round## = INT(num * 10 ^ digits + .5) / 10 ^ digits
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #26 on: September 16, 2021, 05:45:08 pm »
Pete what's with 32768? That's integer limit. You need a number of digits where you have i
Code: QB64: [Select]
  1. _ScreenMove 0, 0: Width 80, 42
  2. a = .01
  3. b = 32767
  4. For j = 1 To 120
  5.     b = b + a
  6.     r! = Round##(b, 2)
  7.     Print j; r!
  8.     ' IF j MOD 40 = 0 THEN SLEEP
  9.  
  10. Function Round## (num As Double, digits As Long) ' Steve's simplified
  11.     Round## = Int(num * 10 ^ digits + .5) / 10 ^ digits
  12.  
  13.  

Everyone is yelling for me to come for supper, I might have been rushed here.
« Last Edit: September 16, 2021, 05:46:18 pm by bplus »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #27 on: September 16, 2021, 07:08:25 pm »
Ohppp! see I knew I was rushed missed the missed .19

OK fixed:
Code: QB64: [Select]
  1. _ScreenMove 0, 0: Width 80, 42
  2. a = .01
  3. b = 32767
  4. For j = 1 To 120
  5.     b = b + a
  6.     r! = Round##(b, 2)
  7.     Print j; r!
  8.     ' IF j MOD 40 = 0 THEN SLEEP
  9.  
  10. Function Round## (num As Double, digits As Long) ' Steve's simplified
  11.     Round## = Int(num * 10 ^ digits + .5) / 10 ^ digits
  12.  
« Last Edit: September 16, 2021, 07:09:57 pm by bplus »

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #28 on: September 16, 2021, 09:01:55 pm »
The revision did work for 32767, but then I tried a larger number for b, 327670, and messed around with the digits a bit, but no success.

Code: QB64: [Select]
  1. _SCREENMOVE 0, 0: WIDTH 80, 42
  2. a = .01
  3. b = 327670
  4. FOR j = 1 TO 12000
  5.     b = b + a
  6.     r! = Round##(b, 2)
  7.     PRINT j; r!
  8.     IF j MOD 40 = 0 THEN SLEEP
  9.  
  10. FUNCTION Round## (num AS DOUBLE, digits AS LONG) ' Steve's simplified
  11. Round## = INT(num * 10 ^ digits + .5) / 10 ^ digits
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: I don't often work with numbers, but when I do....
« Reply #29 on: September 16, 2021, 09:29:25 pm »
Well when you exceed 7 digits you have to go double.

I just figured out why I was calling it simplified, here is the better (I hope) unsimplified:
Code: QB64: [Select]
  1. _ScreenMove 100, 0: Width 80, 42
  2. Dim As Double a, b, c
  3. a = .01
  4. b = 327670.004
  5. c = 327670.005
  6. For j = 1 To 12000
  7.     b = b + a
  8.     c = c + a
  9.     r## = Round##(b, 2) ' round to 2 decimals
  10.     r2## = Round##(c, 2)
  11.     Print j; r##; r2##
  12.     If j Mod 40 = 0 Then Sleep
  13.  
  14. Function Round## (num As Double, digits As Long) 'unsimplified
  15.     Round## = Int((num + .5 * 10 ^ -digits) * 10 ^ digits) / 10 ^ digits
« Last Edit: September 16, 2021, 09:30:46 pm by bplus »