Author Topic: A million dollar accountant's headache  (Read 3899 times)

0 Members and 1 Guest are viewing this topic.

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
A million dollar accountant's headache
« on: February 03, 2022, 04:00:35 pm »
Since, as far as I know, a USING clause is not available to _PRINTSTRING, I wanted to come up with a quick way to align numbers along their decimal columns. So I came up with the following function (with a test bed display)

It's largely based upon example #1 from the wiki
https://wiki.qb64.org/wiki/LOG

Code: QB64: [Select]
  1. 'LogBack% function test bed for _printstring command
  2. SCREEN _NEWIMAGE(600, 600, 32)
  3. dpos% = 400 'we'll line up our decimal points at x=400
  4.  
  5. 'ledger lines
  6. FOR ln% = -7 TO 7
  7.     c& = &H7F7F7F7F * (ln% = 0) - &H3F7F007F * (ln% <> 0)
  8.     LINE (400 + (ln% * 8), 0)-(400 + (ln% * 8), _HEIGHT - 1), c& '&H7F7F7F7F
  9. NEXT ln%
  10.  
  11. 'print the number list in columnar fashion
  12. FOR y% = 1 TO 20
  13.     READ a##
  14.     _PRINTSTRING (0, y% * 16), "y position=" + STR$(LogBack%(dpos%, a##))
  15.     _PRINTSTRING (LogBack%(dpos%, a##), y% * 16), _TRIM$(STR$(a##))
  16. NEXT y%
  17.  
  18. DATA -1000000.0001,-1000000,-999999.999,-999999,-10000,-1000,-50,-10,-5.678,-1,0
  19. DATA 1,4.912,23,99.999,4587.23,12000,999999,1000000,1000001
  20.  
  21.  
  22. '²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
  23. FUNCTION LogBack% (decpos AS INTEGER, value AS _FLOAT)
  24.  
  25.     'compute position setback to align (value) decimal points at (decpos)
  26.     IF ABS(value) >= 1 THEN '           back up 8 pixels / decimal place
  27.         LogBack% = decpos - _SHL(INT(LOG(value * SGN(value)) / LOG(10.#)) + 1 - (value < 0), 3)
  28.     ELSE '                              a fractional value
  29.         IF SGN(value) > 0 THEN
  30.             LogBack% = decpos '         start at the decimal place for positive decimal
  31.         ELSE
  32.             LogBack% = decpos - 8 '     start 8 pixels behind for zero & negative decimals
  33.         END IF
  34.     END IF
  35.  
  36. END FUNCTION 'LogBack%
  37.  

OK, so far so good, but what's the deal with the 1,000,000 value?

Is it something wrong with my algorithm? A QB64 bug? or just the vicissitudes of binary to decimal math?

I haven't yet found any other values that do this.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: A million dollar accountant's headache
« Reply #1 on: February 03, 2022, 04:43:14 pm »
Heh! Whew! I'd do it this way:
Code: QB64: [Select]
  1. Screen _NewImage(600, 600, 32)
  2. dpos% = 400 'we'll line up our decimal points at x=400
  3.  
  4. 'ledger lines
  5. For ln% = -7 To 7
  6.     c& = &H7F7F7F7F * (ln% = 0) - &H3F7F007F * (ln% <> 0)
  7.     Line (400 + (ln% * 8), 0)-(400 + (ln% * 8), _Height - 1), c& '&H7F7F7F7F
  8. Next ln%
  9.  
  10. 'print the number list in columnar fashion
  11. For y% = 1 To 20
  12.     Read a$
  13.     dot = InStr(a$, ".")
  14.     If dot Then
  15.         back = Len(a$) - (Len(a$) - dot + 1)
  16.     Else
  17.         back = Len(a$)
  18.     End If
  19.     _PrintString (400 - back * 8, 16 * (y% - 1)), a$
  20. Next y%
  21.  
  22. Data -1000000.0001,-1000000,-999999.999,-999999,-10000,-1000,-50,-10,-5.678,-1,0
  23. Data 1,4.912,23,99.999,4587.23,12000,999999,1000000,1000001
  24.  
 [ You are not allowed to view this attachment ]  

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: A million dollar accountant's headache
« Reply #2 on: February 03, 2022, 07:29:23 pm »
That certainly gets around the problem and could still be sub'ed out and a string converstion added in the sub. Cool.

The LOG function worked very well for some dynamically resizing grid work I've done, but it's probably not exact enough for what I'm trying to accomplish here. I tried an ascending 10 power loop and it had a hitch at 1000 too, not just 1M.

Code: QB64: [Select]
  1. x## = 1
  2.     x## = x## * 10
  3.     LOCATE , 1
  4.     PRINT x##;
  5.     LOCATE , 20
  6.     PRINT INT(LOG(x##) / LOG(10.#))
  7. LOOP UNTIL x## = 10000000
  8.  


Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: A million dollar accountant's headache
« Reply #3 on: February 03, 2022, 07:45:25 pm »
That certainly gets around the problem and could still be sub'ed out and a string converstion added in the sub. Cool.

The LOG function worked very well for some dynamically resizing grid work I've done, but it's probably not exact enough for what I'm trying to accomplish here. I tried an ascending 10 power loop and it had a hitch at 1000 too, not just 1M.

Code: QB64: [Select]
  1. x## = 1
  2.     x## = x## * 10
  3.     LOCATE , 1
  4.     PRINT x##;
  5.     LOCATE , 20
  6.     PRINT INT(LOG(x##) / LOG(10.#))
  7. LOOP UNTIL x## = 10000000
  8.  



Ha! that works fine without Int()
Code: QB64: [Select]
  1. x## = 1
  2.     x## = x## * 10
  3.     Locate , 1
  4.     Print x##;
  5.     Locate , 20
  6.     Print Log(x##) / Log(10.#)
  7. Loop Until x## = 10000000
  8.  

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: A million dollar accountant's headache
« Reply #4 on: February 03, 2022, 08:08:02 pm »
I think I added INT because I was having some issues with positioning and it subsequently screwed up every third power of ten. I can't see why, but there it is...

When using it in my grid work, I didn't have INT in the mix.

So I put your string solution in, with my syntax scheme, and it crunched down to even tighter code and functioned flawlessly. Amazing stuff. Thanks.

Code: QB64: [Select]
  1. 'LogBack% function test bed for _printstring command
  2. SCREEN _NEWIMAGE(600, 600, 32)
  3. dpos% = 400 'we'll line up our decimal points at x=400
  4.  
  5. 'ledger lines
  6. FOR ln% = -7 TO 7
  7.     c& = &H7F7F7F7F * (ln% = 0) - &H3F7F007F * (ln% <> 0)
  8.     LINE (400 + (ln% * 8), 0)-(400 + (ln% * 8), _HEIGHT - 1), c& '&H7F7F7F7F
  9. NEXT ln%
  10.  
  11. 'print the number list in columnar fashion
  12. FOR y% = 1 TO 20
  13.     READ a##
  14.     _PRINTSTRING (0, y% * 16), "y position=" + STR$(LogBackII%(dpos%, a##))
  15.     _PRINTSTRING (LogBackII%(dpos%, a##), y% * 16), _TRIM$(STR$(a##))
  16. NEXT y%
  17.  
  18. DATA -1000000.0001,-1000000,-999999.999,-999999,-10000,-1000,-50,-10,-5.678,-1,0
  19. DATA 1,4.912,23,99.999,1000,12000,999999,1000000,1000001
  20.  
  21.  
  22. '²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
  23. FUNCTION LogBackII% (decpos AS INTEGER, value AS _FLOAT)
  24.  
  25.     'featuring Bplus' string fix
  26.     a$ = _TRIM$(STR$(value))
  27.     dot = INSTR(a$, ".")
  28.     LogBackII% = decpos - _SHL(LEN(a$) + (LEN(a$) - dot + 1) * (dot <> 0), 3)
  29.  
  30. END FUNCTION 'LogBackII%
  31.  

Offline Galleon

  • QB64 Developer
  • Newbie
  • Posts: 25
    • View Profile
Re: A million dollar accountant's headache
« Reply #5 on: February 08, 2022, 01:05:22 am »
A workaround for _PRINTSTRING-ing a PRINT USING (no doubt others have shared similar things in the past)

Code: QB64: [Select]
  1. DIM SHARED printUsing_backBuffer AS LONG
  2. DIM SHARED printUsing_sourceBackup AS LONG
  3. DIM SHARED printUsing_destBackup AS LONG
  4.  
  5. SCREEN _NEWIMAGE(800, 600, 32)
  6.  
  7.  
  8.     first$ = "Bobby": last$ = "Smith"
  9.     boxes% = 1510: sales! = 4530
  10.     tmp$ = "Salesperson: & &  #####,.   $$#####,.##"
  11.  
  12.     COLOR _RGB(RND * 255, RND * 255, RND * 255)
  13.  
  14.     PrintUsingInit
  15.     PRINT USING tmp$; first$; last$; boxes%; sales!
  16.     _PRINTSTRING (RND * 800 - 200, RND * 600), PrintUsingResult
  17.  
  18.     _LIMIT 10
  19.  
  20. SUB PrintUsingInit
  21.     printUsing_sourceBackup = _SOURCE
  22.     printUsing_destBackup = _DEST
  23.     IF printUsing_backBuffer = 0 THEN printUsing_backBuffer = _NEWIMAGE(1000, 3, 0)
  24.     _DEST printUsing_backBuffer
  25.     _SOURCE printUsing_backBuffer
  26.     CLS , 1
  27.  
  28. FUNCTION PrintUsingResult$
  29.     DIM result AS STRING
  30.     DIM i AS LONG
  31.     FOR i = 1 TO 1000
  32.         IF SCREEN(1, i, 1) = 23 THEN EXIT FOR
  33.         result = result + CHR$(SCREEN(1, i))
  34.     NEXT
  35.     PrintUsingResult = result
  36.     _DEST printUsing_destBackup
  37.     _SOURCE printUsing_sourceBackup
  38.  

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: A million dollar accountant's headache
« Reply #6 on: February 08, 2022, 08:25:25 am »
Since everyone is tossing in their solution to this type problem, I thought I'd add in mine:

Code: QB64: [Select]
  1. 'LogBack% function test bed for _printstring command
  2. Screen _NewImage(600, 600, 32)
  3. dpos% = 400 'we'll line up our decimal points at x=400
  4.  
  5. 'ledger lines
  6. For ln% = -7 To 7
  7.     c& = &H7F7F7F7F * (ln% = 0) - &H3F7F007F * (ln% <> 0)
  8.     Line (400 + (ln% * 8), 0)-(400 + (ln% * 8), _Height - 1), c& '&H7F7F7F7F
  9. Next ln%
  10.  
  11. 'print the number list in columnar fashion
  12. For y% = 1 To 20
  13.     Read a##
  14.     a$ = formatMoney(a##, 4) 'a max of 4 decimal places for my money
  15.     _PrintString (437 - _PrintWidth(a$), 16 * (y% - 1)), a$
  16. Next y%
  17.  
  18. Data -1000000.0001,-1000000,-999999.999,-999999,-10000,-1000,-50,-10,-5.678,-1,0
  19. Data 1,4.912,23,99.999,4587.23,12000,999999,1000000,1000001
  20.  
  21.  
  22. Function formatMoney$ (money##, maxLength)
  23.     m$ = Str$(money##)
  24.     d = (Len(m$) - _InStrRev(m$, ".")) Mod Len(m$)
  25.     If d = 0 Then m$ = m$ + "." 'add a decimal if none exists
  26.     formatMoney$ = m$ + String$(maxLength - d, "0") 'add the necessary number of periods
  27.  

Since this is a "million dollar accountant's" problem, I thought it might be best to simply add in the sufficient number of zeros to the end of the money.  Personally, nothing gets me as distracted when working with numbers, as trying to sort out which figures go with which column or row, and misaligned formatting is one of those things that has my OCD twitching constantly.  Even though there's nothing in the world wrong with everyone else's solution, somehow I find my mind and attention always being drawn to that lone -5.678 which has numbers hanging off to the right side there, with none of the other values around it having any.

With everything displaying their zeroes properly, nothing stands out as taking precedence on the right side of the line, which makes it easier to find and focus on the truly important things -- which stands out the most on the left side of the line.  (Also known as. "What's the greatest expense and greatest source of income!")

Maybe it's just me, but I'd rather have those trailing zeroes when dealing with accountant stuff, rather than not have them.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline tomxp411

  • Newbie
  • Posts: 28
    • View Profile
Re: A million dollar accountant's headache
« Reply #7 on: February 08, 2022, 04:25:58 pm »
All this really highlights how terrible IEEE-754 floats actually are for representing decimal values. While they're perfectly fine for values that don't need to be printed, but any time you're dealing with money or with explicit powers of 10 (ie decimal positions), it's better to use integer types and deal with the decimal yourself.

When I worked in the banking industry, we always stored values as integers. We had a special "print money" function to convert integers to dollars and cents, then print that. It went something like this:

Code: QB64: [Select]
  1. DEFLNG A-Z
  2.  
  3. PrintDec 1
  4. PrintDec 10
  5. PrintDec 100
  6. PrintDec 1000
  7. PrintDec 10000
  8. PrintDec 100000
  9. PrintDec 1000000
  10. PrintDec 10000000
  11. PrintDec 100000000
  12. PrintDec -1
  13. PrintDec -10
  14. PrintDec -100
  15. PrintDec -10000000
  16. PrintDec 12350
  17. PrintDec -12350
  18.  
  19. SUB PrintDec (N AS LONG)
  20.     M$ = _TRIM$(STR$(ABS(N)))
  21.     IF LEN(M$) < 2 THEN M$ = "00" + M$
  22.     IF LEN(M$) < 3 THEN M$ = "0" + M$
  23.  
  24.     lm = LEN(M$)
  25.     N$ = " "
  26.     IF N < 0 THEN N$ = "-"
  27.     PRINT SPACE$(10 - lm); N$; LEFT$(M$, lm - 2); "."; RIGHT$(M$, 2)
  28.