' Formatting numbers (alternatives to Print using)
' https://qb64forum.alephc.xyz/index.php?topic=4703.msg141127#msg141127
' BOOLEAN VALUES
' Used by RhoSigma's operator overloading workaround example:
' Global variables
' Start main menu
main
' Exit program
'Screen 0
' /////////////////////////////////////////////////////////////////////////////
Dim result$: result$
= "" Print "converting a number to a string, formatted like with 'print using'"
Print "1) Format Numbers Manually (parse string, insert commas)" Print "2) IndexFormat$ by RhoSigma example #1" Print "3) IndexFormat$ by RhoSigma example #2" Print "4) commaD$ by bplus" Print "5) format$ by SMcNeill"
FormatNumbersManually:
Exit Do
' /////////////////////////////////////////////////////////////////////////////
' Question 1: Is there an alternative to Print Using?
' Answer 1: Format numbers the manual way!
Sub FormatNumbersManually
'Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
'Cls
Print "-------------------------------------------------------------------------------" Print "Format numbers (add commas) by manually parsing the string:"
Print "Attempt to format integer:" MyInteger% = 32767
Print "FormatInteger$(" + _Trim$(Str$(MyInteger%
)) + ") returns " + Chr$(34) + FormatInteger$
(MyInteger%
) + Chr$(34) Print Using "MyInteger with PrintUsing: ###,###,###,###,###,###"; MyInteger%
MyLong& = 2147483647
Print Using "MyLong with PrintUsing: ###,###,###,###,###,###"; MyLong&
My_Integer64&& = 9223372036854775807
Print "Format_Integer64$(" + _Trim$(Str$(My_Integer64&&
)) + ") returns " + Chr$(34) + Format_Integer64$
(My_Integer64&&
) + Chr$(34) Print Using "My_Integer64 with PrintUsing: ###,###,###,###,###,###,###"; My_Integer64&&
Print "-------------------------------------------------------------------------------" End Sub ' FormatNumbersManually
' Question 2 (BONUS): Is there a way to define just one function that receives
' a variant (like in VB6/VBA) so you don't have to define
' seperate nearly identical functions for each type?
Dim StringValue$: StringValue$
= "" 'Print "TempString$=" + TempString$
iCount
= iCount
+ 1:
If iCount
> 3 Then StringValue$
= "," + StringValue$: iCount
= 1 ': Print "added comma: " + StringValue$ StringValue$
= Mid$(TempString$
, iLoop
, 1) + StringValue$
': Print "next value: " + StringValue$ FormatInteger$ = StringValue$
Dim StringValue$: StringValue$
= "" 'Print "TempString$=" + TempString$
iCount
= iCount
+ 1:
If iCount
> 3 Then StringValue$
= "," + StringValue$: iCount
= 1 ': Print "added comma: " + StringValue$ StringValue$
= Mid$(TempString$
, iLoop
, 1) + StringValue$
': Print "next value: " + StringValue$ FormatLong$ = StringValue$
Dim StringValue$: StringValue$
= "" 'Print "TempString$=" + TempString$
iCount
= iCount
+ 1:
If iCount
> 3 Then StringValue$
= "," + StringValue$: iCount
= 1 ': Print "added comma: " + StringValue$ StringValue$
= Mid$(TempString$
, iLoop
, 1) + StringValue$
': Print "next value: " + StringValue$ Format_Integer64$ = StringValue$
' /////////////////////////////////////////////////////////////////////////////
' Format$ function with indexed & reusable arguments by RhoSigma
' https://qb64forum.alephc.xyz/index.php?topic=2932.msg121898#msg121898
' As the title says,
' this is a Format$ function (PRINT USING style), which allows for explicit
' argument indexing in the given format template. The indexing feature lets
' you change the template being processed while keeping the arguments stream
' the same.
' This can be an invaluable tool in some situations, just think about
' different date formats. With PRINT USING alone you would probably need to
' reorder your day, month, year, weekday etc. arguments for every date format
' to match the order of the respective format tokens in the template, but not
' so with this function. Another example is when it comes to localizing your
' programs, where it helps when translating template strings to different
' languages and the sentence structure and thus the order of the arguments
' changes.
' Beside the indexing, which this function got its name from, it has a couple
' other useful extensions compared to the regular PRINT USING conventions,
' such as bin/hex/oct formatting, use of escape sequences to insert control
' or extended ASCII chars and more. All of this is detailed in the
' comprehensive HTML Documentation available for download below the example
' code block.
' As it is required to preserve the UTF-8 encoding of the HTML Documentation
' and some preformatted examples, it is packed into an 7-zip archive file
' attached below. The archive does also contain the example from the codebox
' above.
'--- Full description available in separate HTML document.
' My Projects: https://qb64forum.alephc.xyz/index.php?topic=809
' GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
' Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
' Bonus - Blankers, QB64/Notepad++ setup pack
Function IndexFormat$
(fmt$
, arg$
, sep$
) '--- option _explicit requirements ---
Dim args$
, shan&
, dhan&
, than&
, idx%
, cpos&
, res$
, lit%
, tok%
, ft$
, cch$
Dim och$
, opos&
, tmp$
, fp$
, tyl%
, typ$
, oval&&
, temp~&&
, curr%
, high%
'--- init ---
args$ = arg$ 'avoid side effects
ReDim argArr$
(0 To 35) 'all args empty '--- parse arguments ---
argArr$
(idx%
) = Left$(args$
, cpos&
- 1) args$
= Mid$(args$
, cpos&
+ Len(sep$
)) '--- process format template ---
res$ = "": lit% = 0: tok% = 0: ft$ = "": idx% = -1
cch$
= Mid$(fmt$
, cpos&
, 1) If cch$
= "_" And lit%
= 0 Then 'take next \{} as literal Case "V": och$
= Chr$(11) 'vertical tabulator Case "R": och$
= Chr$(13) 'carriage return Case "0", "1", "2", "3" ' octal ASCII (3 digits) cpos& = cpos& + 2
Case "X" ' hex ASCII (x + 2 digits) cpos& = cpos& + 2
res$ = res$ + och$
cpos& = cpos& + 1: opos& = cpos&
och$
= UCase$(Mid$(fmt$
, cpos&
- 1, 1)): tok%
= -1 If ((cpos&
- 1) = opos&
) Or ((och$
< "0" Or och$
> "9") And (och$
< "A" Or och$
> "Z")) Then och$
= "-" GoSub doArgFormat: res$
= res$
+ tmp$
tok% = 0: ft$ = "": idx% = -1
Else 'accumulate chars/symbols in correct channel If tok%
Then ft$
= ft$
+ cch$:
Else res$
= res$
+ cch$
lit% = 0
'--- cleanup & set result ---
IndexFormat$ = res$
'-----------------------------
doArgFormat:
'Cls:
tyl%
= InStr(2, ft$
, ":") Case "!", "&", "\" 'regular string formatting tyl%
= InStr(2, ft$
, "\"):
If tyl%
= 0 Then ft$
= "\" + ft$: tyl%
= 2 Case "C", "c": tyl%
= (tyl%
- Len(argArr$
(idx%
))) \
2 Case "R", "r": tyl%
= tyl%
- Len(argArr$
(idx%
)) Case Else: tyl%
= 0 'L or Unknown is default (left) argArr$
(idx%
) = Space$(tyl%
) + argArr$
(idx%
) Case "B", "D", "H", "O", "R" 'extended number formatting (bin/dec/hex/oct/real) If tyl%
> 0 Then 'adjust field length (if any) If typ$
<> "E" And typ$
<> "D" Then tmp$
= "-" + Left$(tmp$
, idx%
- 1) + Mid$(tmp$
, idx%
+ 1) tmp$ = "%" + tmp$
typ$ = "": tyl% = 0
typ$
= Mid$(tmp$
, idx%
, 1) + typ$: tyl%
= tyl%
+ 1 If tyl%
= Val(fp$
) Then typ$
= " " + typ$: tyl%
= 0 Case Else 'regular number formatting (or invalid nonsense) Print ft$;
'take nonsense as is tmp$ = tmp$ + ft$
'-----------------------------
doBinString:
oval&&
= Val(argArr$
(idx%
)): temp~&&
= oval&&
tmp$
= String$(64, "0"): curr%
= 64: high%
= 64 If (temp~&&
And 1) Then Asc(tmp$
, curr%
) = 49: high%
= curr%
curr% = curr% - 1: temp~&& = temp~&& \ 2
If -oval&&
< &H0080000000~&&
Then high%
= 33 If -oval&&
< &H0000008000~&&
Then high%
= 49 If -oval&&
< &H0000000080~&&
Then high%
= 57
' An example using the new IndexFormat function:
' Save as: FormatExample.bas (or whatever)
Dim RoutineName
As String: RoutineName
= "Example of IndexFormat$ by RhoSigma"
'_Title "FormatExample"
'=== Full description for the IndexFormat$() function is available
'=== in the separate HTML document.
'=====================================================================
'Width , 30
'-- The following format templates need its arguments in different order,
'-- no problem with indexing, no need to reorder the given arguments.
'-- You may even use different formatting for the same argument, as long
'-- as its types are compatible (ie. string vs. number).
dateDE$ = "Date format Germany: 0{#}. 1{&} 2{####}" ' 1{} = full string
dateUS$ = "Date format US : 1{\ \}/0{#} 2{####}" '1{} = first 3 chars only
'-- The easiest way to pass a variable number of arguments, which may
'-- even be of different types, to a user function is using a string.
'-- All arguments will be concatenated in this string, separated by a
'-- designated char/string which does not appear in the arguments itself.
'-- Strings can be added as is, numbers can be added as literal strings
'-- too, or in the form STR$(variable).
year% = 2021
argStr$
= "2|Januar|" + Str$(year%
) '-- In this example the | is the argument separator. Use whatever is
'-- suitable for your needs, maybe even a CHR$(0).
'-- Now let's test the whole thing, we've different token orders in the
'-- format templates, but use the same argument string for both calls.
Print "dateDE$=" + Chr$(34) + IndexFormat$
(dateDE$
, argStr$
, "|") + Chr$(34) Print "dateUS$=" + Chr$(34) + IndexFormat$
(dateUS$
, argStr$
, "|") + Chr$(34)
'-- And here the examples from the function description, which also
'-- shows the reuse of arguments without the need to pass more arguments
'-- for the additional "feet" and "toes" format tokens.
head = 1: hands = 2: fingers = 10
Print "WITH PRINT USING:" Print Using "## head, ## hands and ## fingers"; head
, hands
, fingers
Print Using "## fingers, ## head and ## hands"; head
, hands
, fingers
Print "WITH IndexFormat$:" argStr$
= Str$(head
) + "|" + Str$(hands
) + "|" + Str$(fingers
) Print IndexFormat$
("2{##} fingers, 0{##} head and 1{##} hands", argStr$
, "|") Print IndexFormat$
("0{##} head, 1{##} hands and 2{##} fingers, also 1{##} feet and 2{##} toes", argStr$
, "|")
'-- The function can also handle escape sequences as known from C/C++,
'-- so you may use those sequences within your format templates.
Print "EXAMPLE OF ESCAPE SEQUENCES:" Print IndexFormat$
("Column-1\tColumn-2\tColumn-3\n0{#.##}\t\t1{#.##}\t\t2{#.##}", "1.11|2.22|3.33", "|") Print IndexFormat$
("This is a \x220{&}\x22 section.", "quoted", "|")
Print "EXAMPLE OF ESCAPE SEQUENCES and the new bin/dec/hex/oct/real formatting:" '-- Using escape sequences and the new bin/dec/hex/oct/real formatting,
'-- while reusing the same argument for all tokens. Also showing the use
'-- of preferences specifiers to group bin and hex outputs.
Print IndexFormat$
(" Bin: 0{?4:B16}\n Dec: 0{D}\n Hex: 0{?2:H8}\n Oct: 0{O}\nReal: 0{R}\n", "2021.00548", "|")
'-- Alignment of strings in a fixed length field, the square brackets are
'-- just used to better visualize the field.
Print "ALIGNMENT OF STRINGS:" Print IndexFormat$
("[0{?L:\ \}]", "RhoSigma", "|") Print IndexFormat$
("[0{?C:\ \}]", "RhoSigma", "|") Print IndexFormat$
("[0{?R:\ \}]", "RhoSigma", "|")
'-- Finally a currency example with replaced dollar sign and flipped
'-- comma/dot notation. I'd like to get that much for this function ;)
Print "CURRENCY EXAMPLE:" Print IndexFormat$
("Account balance: 0{?î,:**$#####,.##}", "12345.67", "|")
' /////////////////////////////////////////////////////////////////////////////
'insert FUNCTION IndexFormat$ here, get it from:
'https://qb64forum.alephc.xyz/index.php?topic=2932.msg121898#msg121898
'(7z inclusive example and docs)
Print "-------------------------------------------------------------------------------" Print "IndexFormat$ by RhoSigma:"
Print IndexFormat$
("MyInteger% = 0{R}", Str$(32767), "~") Print "number with thousands separator:" Print IndexFormat$
("MyInteger% = 0{#####,}", Str$(32767), "~") Print "number with thousands separator flipped to:" Print IndexFormat$
("MyInteger% = 0{?,:#####,}", Str$(32767), "~") Print "number as grouped Hex/Oct/Bin:" Print IndexFormat$
("MyInteger% = 0{?2:H8}", Str$(32767), "~") Print IndexFormat$
("MyInteger% = 0{?4:O8}", Str$(32767), "~") Print IndexFormat$
("MyInteger% = 0{?4:B16}", Str$(32767), "~") Print "-------------------------------------------------------------------------------"
' /////////////////////////////////////////////////////////////////////////////
' Re: converting a number to a string, formatted like with "print using"
' RhoSigma
' « Reply #3 on: March 05, 2022, 04:49:33 pm »
' « Last Edit: March 05, 2022, 04:55:02 pm »
' Ah, so you talk about function overloading. Not possible in QB64 as is,
' but look on IndexFormat$, it's a function which needs to handle several
' different arguments/types too, it takes all the different parameters in
' the argument string, all added together, strings as is, numbers via STR$().
' Then, inside the functions you need to parse that whole argument string
' to get what you need.
' Another way would be a user defined type.
' As you can see, there are multiple ways to build universal functions,
' but unfortunately no such easy thing like a 'variants" type.
' Somewhere you have to go in and do your own coding.
' Guess [member=9]SMcNeill[/member] could also show you a variant doing this with _MEM variables.
' He made a universal QuickSort function somewhere here in the forum,
' using _MEM variables to determine the array type which is to be sorted
' in his function.
' EDIT:
' here's the _MEM sort, maybe you can derive something useful for your needs...
' https://qb64forum.alephc.xyz/index.php?topic=1601.0
'Type aNumber
' typ AS STRING * 3
' n8 AS _BYTE
' n16 AS INTEGER
' n32 AS LONG
' n64 AS _INTEGER64
' ns AS SINGLE
' nd AS DOUBLE
' nf AS _FLOAT
'END TYPE
'do _BYTE stuff here
DoSomthingWithNumber$ = "type _BYTE"
'do INTEGER stuff here
DoSomthingWithNumber$ = "type INTEGER"
'do LONG stuff here
DoSomthingWithNumber$ = "type LONG"
'do _INTEGER64 stuff here
DoSomthingWithNumber$ = "type _INTEGER64"
'do SINGLE stuff here
DoSomthingWithNumber$ = "type SINGLE"
'do DOUBLE stuff here
DoSomthingWithNumber$ = "type DOUBLE"
'do _FLOAT stuff here
DoSomthingWithNumber$ = "type _FLOAT"
'unsupported type
DoSomthingWithNumber$ = "type unknown"
Sub DoSomthingWithNumber_Test
Print "-------------------------------------------------------------------------------" Print "Function overloading example by RhoSigma:" num.typ = "n16"
num.n16 = 32767
res = DoSomthingWithNumber$(num)
Print "-------------------------------------------------------------------------------"
End Sub ' DoSomthingWithNumber_Test
' /////////////////////////////////////////////////////////////////////////////
' bplus
' « Reply #5 on: March 05, 2022, 08:00:10 pm »
' Seems like Double parameter can handle various Types:
Function commaD$
(n#
, nDecPlaces%
) 'only works right for double# type place
= InStr(func$
, ".") func$
= Mid$(func$
, 1, place
- 4) + "," + Mid$(func$
, place
- 3) place
= InStr(func$
, ",") 'fix to nDecPlaces
place
= InStr(func$
, ".") front$
= Mid$(func$
, 1, place
) back$
= Mid$(func$
, place
+ 1) If Len(back$
) > nDecPlaces%
Then func$
= front$
+ Left$(back$
, nDecPlaces%
) func$
= func$
+ "." + String$(nDecPlaces%
, "0") commaD$ = s$ + func$
Print "-------------------------------------------------------------------------------" Print "commaD$ by bplus:"
i%
= 100000 * Rnd - 50000 Print "commaD$(i%, 2) returns " + Chr$(34) + commaD$
(i%
, 2) + Chr$(34)
j&
= 10000000 * Rnd - 5000000 Print "commaD$(j&, 2) returns " + Chr$(34) + commaD$
(j&
, 2) + Chr$(34)
k##
= 100000 * Rnd - 50000 Print "commaD$(k##, 4) returns " + Chr$(34) + commaD$
(k##
, 4) + Chr$(34) Print "-------------------------------------------------------------------------------"
' /////////////////////////////////////////////////////////////////////////////
' SMcNeill
' « Reply #6 on: March 05, 2022, 10:26:57 pm »
' Try something like this:
' Print "FOO: "; format$("###.###", "123.456789")
' Print format$("###,.##", "123456789.987654321")
' Pass it the same format string you'd use with PRINT USING, and then
' the STR$ value of your number. It'll return a string formatted just as
' PRINT USING would do.
' bplus
' « Reply #7 on: March 06, 2022, 11:52:41 am »
' Oh! that looks like it gets around the Type problem.
' n = _NewImage(80, 80, 0)
' 0 the one mode you can read characters from, a point for Pete ;-))
' Juanjogomez
' « Reply #8 on: March 14, 2022, 08:52:16 am »
' Re: converting a number to a string, formatted like with "print using"
' Very original, simple and effective
Print "-------------------------------------------------------------------------------" Print "format$ by SMcNeill:"
MySingle = 123.456789
MyString
= format$
("###.###", _Trim$(Str$(MySingle
))) Print "format$(" + Chr$(34) + "###.###" + Chr$(34) + ", _Trim$(Str$(MySingle))) returns " + Chr$(34) + MyString
+ Chr$(34)
MyDouble = 123456789.987654321
MyString
= format$
("###,.##", _Trim$(Str$(MyDouble
))) Print "format$(" + Chr$(34) + "###,.##" + Chr$(34) + ", _Trim$(Str$(MyDouble))) returns " + Chr$(34) + MyString
+ Chr$(34)
Print "-------------------------------------------------------------------------------"