Format Numbers

Share your advanced PureBasic knowledge/code with the community.
User avatar
Frarth
Enthusiast
Enthusiast
Posts: 241
Joined: Tue Jul 21, 2009 11:11 am
Location: On the planet
Contact:

Format Numbers

Post by Frarth »

I've seen people on this forum asking for a Format$() procedure to format numbers. Years ago I wrote a Format library for QuickBASIC, and it was one of my first exercises to port it to PureBasic. Here it is, have fun with it:

Code: Select all

; Format Library
; written by Frank Hoogerbeets
; website: www.ditrianum.org
; e-mail: info@ditrianum.org

EnableExplicit

;{ DOCUMENTATION
; -----------------------------------------------------------------------
; FormatI, FormatF, FormatD
;
;   Syntax
;     FormatX(Value, Format$)
;
;   Parameters
;     Value     - number to be formatted
;     Format$   - format defintion
;
;   Usage
;     Use the following symbols to define a format:
;       #   - digit placeholder
;       .   - as rightmost occurance, decimal point
;       ,   - as rightmost occurance, decimal comma
;       0   - as first character, fills leading placeholders with zeros
;       /   - as first character, removes leading placeholders
;
;     Any other character will be part of the format
;
;     Note 1
;       Make sure to provide sufficient placeholders for a given number.
;       Example:  FormatI(21, "#") returns "1", not "21"
;                 FormatI(21, "##") returns "21"
;                 FormatI(21, "####") returns "  21"
;                 FormatI(21, "/####") returns "21"
;                 FormatI(-1, "#") returns "1"
;                 FormatI(-1, "##") returns "-1"

;     Note 2
;       The number of placeholders behind the decimal point or comma
;       determines the number of digits of the decimal part.
;       Example:  FormatD(2/3, "#.##") returns "0.67"
;                 FormatD(2/3, "#.####") returns "0.6667"
;                 FormatD(1/3, "#.##") returns "0.33"
;                 FormatD(1/3, "#.####") returns "0.3333"
;   Examples
;     See below
; -----------------------------------------------------------------------
;}

Structure CharacterType
  c.c[0]
EndStructure

;- INTERNAL PROCEDURES:

Procedure.i Format_mDecPoint(*TFormat.CharacterType)
  ;return position of decimal point/comma
  Protected flen = MemoryStringLength(*TFormat) - 1, i.i
  For i = flen To 0 Step -1
    Select *TFormat\c[i]
    Case ',', '.'
      ProcedureReturn flen - i + 1
    EndSelect
  Next
EndProcedure

Procedure.s Format_mRound(Buffer.s, rpos.i)
  Protected fpos.i, ascii.i, result.s, i.i
  
  ; floating point position
  fpos = FindString(Buffer, Chr(46), 1)
  If fpos = 0
    ProcedureReturn Buffer
  EndIf
  result = Left(Buffer, fpos + rpos)
  
  ;get next digit to determine round-off
  If rpos = 0
    result = Left(Buffer, fpos - 1)
  EndIf
  
  ; < 5 means nothing to round off
  If Val(Mid(Buffer, fpos + rpos + 1, 1)) < 5
    Goto Format_mRound_Exit
  EndIf
  
  ;round off
  For i = Len(result) To 1 Step -1
    ascii = Asc(Mid(result, i, 1))
    Select ascii
    Case 46
      If i = 1
        result = Chr(49) + result
        Break
      EndIf
    Case 48 To 56
      result = Left(result, i - 1) + Chr(ascii + 1) + Mid(result, i + 1)
      Break
    Case 57
      result = Left(result, i - 1) + Chr(48) + Mid(result, i + 1)
      If i = 1
        result = Chr(49) + result
        Break
      EndIf
    EndSelect
  Next
Format_mRound_Exit:
  If fpos = 1
    result = Chr(48) + result
  EndIf
  ProcedureReturn result
EndProcedure

Procedure.s Format_mFormat(Buffer.s, Format.s)
  Protected blen.i, flen.i, lzero.i, nospc.i, bdpnt.i, fdpnt.i, n.i
  Protected char.s, result.s, i.i
  
  Select Left(Format, 1)
  Case Chr(47) ;no (leading) space
    nospc = #True
  Case Chr(48) ;leading zero
    lzero = #True
  EndSelect
  
  ;remove special character if present
  If lzero Or nospc
    Format = Mid(Format, 2)
  EndIf
  
  flen = Len(Format)
  n = 0
  
  ;get round-off position (n)
  fdpnt = Format_mDecPoint(@Format)
  If fdpnt > 0
    For i = flen - fdpnt + 2 To flen
      If Mid(Format, i, 1) = Chr(35)
        n + 1
      EndIf
    Next
  EndIf
  Buffer = Format_mRound(Buffer, n)
  blen = Len(Buffer)
  bdpnt = Format_mDecPoint(@Buffer)
  fdpnt = flen - fdpnt + 1
  
  ; do the part left of the decimal point
  n = blen - bdpnt
  For i = fdpnt - 1 To 1 Step -1
    char = Mid(Format, i, 1)
    Select char
    Case Chr(35)
      If n > 0
        char = Mid(Buffer, n, 1)
        n - 1
      ElseIf lzero
        char = Chr(48)
      ElseIf nospc
        char = ""
      Else
        char = Chr(32)
      EndIf
    Case Chr(44), Chr(46)
      If n = 0
        If nospc
          char = ""
        Else
          char = Chr(32)
        EndIf
      EndIf
    EndSelect
    result = char + result
  Next
  
  ; do the part right of the decimal point
  n = blen - bdpnt + 2
  For i = fdpnt To flen
    char = Mid(Format, i, 1)
    Select char
    Case Chr(35)
      If n <= blen
        char = Mid(Buffer, n, 1)
        n + 1
      Else
        char = Chr(48)
      EndIf
    EndSelect
    result + char
  Next
  ProcedureReturn result
EndProcedure

Procedure.s Format_mFloatD(Value.d, Format.s)
  Protected decimals.i
  ;find decimal sign (point or comma)
  decimals = Format_mDecPoint(@Format) - 1
  ;convert to string
  If decimals > 0
    ProcedureReturn StrD(Value, decimals)
  EndIf
  ProcedureReturn Str(Value)
EndProcedure

Procedure.s Format_mFloatF(Value.f, Format.s)
  Protected decimals.i
  ;find decimal sign (point or comma)
  decimals = Format_mDecPoint(@Format) - 1
  ;convert to string
  If decimals > 0
    ProcedureReturn StrF(Value, decimals)
  EndIf
  ProcedureReturn Str(Value)
EndProcedure

;- FORMAT PROCEDURES:

Procedure.s FormatD(Value.d, Format.s)
  Protected Buffer.s = Format_mFloatD(Value, Format)
  ProcedureReturn Format_mFormat(Buffer, Format)  
EndProcedure

Procedure.s FormatF(Value.f, Format.s)
  Protected Buffer.s = Format_mFloatF(Value, Format)
  ProcedureReturn Format_mFormat(Buffer, Format)
EndProcedure

Procedure.s FormatI(Value.q, Format.s)
  Protected Buffer.s = Str(Value)
  ProcedureReturn Format_mFormat(Buffer, Format)
EndProcedure


;- EXAMPLES
;{
; -----------------------------------------------------------------------
;
; ;6-digits precision with decimal *point*
; x.d = 33 / 7
; Debug x
; Debug FormatD(x, "#.######")

; ;6-digits precision with decimal *comma*
; x.d = 33 / 7
; Debug x
; Debug FormatD(x, "#,######")

; ;leading zeros
; x.l = 75
; Debug FormatI(x, "0####")

; ;decimal notation with integer
; x.l = 75
; Debug FormatI(x, "$####.##")

; ;remove unused leading placeholders
; x.d = 333 / 7
; Debug x
; Debug FormatD(x, "/####.########")
; ;round to integer
; Debug FormatD(x, "/###")

; negative numbers
; Debug FormatD(-1.33435,"##.##")

; Define x.d = -33 / 7
; Debug x
; Debug FormatD(x, "##.######")

; currency notation
; OpenConsole()
;   PrintN(FormatD(1.35, "$ #.###.###,##"))
;   PrintN(FormatD(4895.208, "$ #.###.###,##"))
;   ; remove unused digit placeholders
;   PrintN(FormatD(4895.208, "/$ #.###.###,##"))
;   Input()
; CloseConsole()
; -----------------------------------------------------------------------
;}
Last edited by Frarth on Sat Mar 27, 2010 4:04 pm, edited 3 times in total.
PureBasic 5.41 LTS | Xubuntu 16.04 (x32) | Windows 7 (x64)
jamba
Enthusiast
Enthusiast
Posts: 144
Joined: Fri Jan 15, 2010 2:03 pm
Location: Triad, NC
Contact:

Re: Format Numbers

Post by jamba »

cool! thanks for sharing
-Jon

Fedora user
But I work with Win7
jamba
Enthusiast
Enthusiast
Posts: 144
Joined: Fri Jan 15, 2010 2:03 pm
Location: Triad, NC
Contact:

Re: Format Numbers

Post by jamba »

it doesn't handle printing negative numbers?

Code: Select all

Debug FormatD(1.33435,"/#.##")
Debug FormatD(-1.33435,"/#.##")

x.d = -33 / 7
Debug x
Debug FormatD(x, "#.######")
-Jon

Fedora user
But I work with Win7
User avatar
Frarth
Enthusiast
Enthusiast
Posts: 241
Joined: Tue Jul 21, 2009 11:11 am
Location: On the planet
Contact:

Re: Format Numbers

Post by Frarth »

It does, but you must reserve a placeholder for the minus sign as well:

Code: Select all

Debug FormatD(1.33435,"/##.##")
Debug FormatD(-1.33435,"/##.##")

Define x.d = -33 / 7
Debug x
Debug FormatD(x, "##.######")
PureBasic 5.41 LTS | Xubuntu 16.04 (x32) | Windows 7 (x64)
jamba
Enthusiast
Enthusiast
Posts: 144
Joined: Fri Jan 15, 2010 2:03 pm
Location: Triad, NC
Contact:

Re: Format Numbers

Post by jamba »

Frarth wrote:It does, but you must reserve a placeholder for the minus sign as well:

Code: Select all

Debug FormatD(1.33435,"/##.##")
Debug FormatD(-1.33435,"/##.##")

Define x.d = -33 / 7
Debug x
Debug FormatD(x, "##.######")

ah! indeed.
Thanks for setting me straight :wink:
-Jon

Fedora user
But I work with Win7
rsts
Addict
Addict
Posts: 2736
Joined: Wed Aug 24, 2005 8:39 am
Location: Southwest OH - USA

Re: Format Numbers

Post by rsts »

Nice one!

Many thanks for sharing with us :D

cheers
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Re: Format Numbers

Post by ts-soft »

Change the ByteType Structure to .c (character) and it works with unicode!

greetings
Thomas
User avatar
Frarth
Enthusiast
Enthusiast
Posts: 241
Joined: Tue Jul 21, 2009 11:11 am
Location: On the planet
Contact:

Re: Format Numbers

Post by Frarth »

ts-soft wrote:Change the ByteType Structure to .c (character) and it works with unicode!

greetings
Thomas
You're right. It makes more sense to support bytes 0 through 255 than -128 through 127. However .c is not unicode; it's still a single byte, whereas Unicode supports 4 bytes.

In the old days the characters 128 through 255 were considered extended ascii. Now they are part of UTF-8 and Unicode. And if I'm not mistaken these characters are not the same on every system. So I avoid using them as much as possible, unless if I were to develop an app for a specific language.

Edit: I've adjusted the above code.
PureBasic 5.41 LTS | Xubuntu 16.04 (x32) | Windows 7 (x64)
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Re: Format Numbers

Post by srod »

.c is a single byte only if compiling an Ascii app. With the Unicode switch set, .c defaults to two bytes. The UTF-16 encoding of the Unicode planes does indeed support 4-bytes, but for the majority of uses the 16-bit .c type is fine and the one to go for.
I may look like a mule, but I'm not a complete ass.
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Re: Format Numbers

Post by Kaeru Gaman »

indeed.
.c is a dynamic type, thus .a and .u were introduced as non dynamic versions.
oh... and have a nice day.
SFSxOI
Addict
Addict
Posts: 2970
Joined: Sat Dec 31, 2005 5:24 pm
Location: Where ya would never look.....

Re: Format Numbers

Post by SFSxOI »

I vote for .z myself :)

Thanks for posting this, it should come in handy.
The advantage of a 64 bit operating system over a 32 bit operating system comes down to only being twice the headache.
User avatar
Frarth
Enthusiast
Enthusiast
Posts: 241
Joined: Tue Jul 21, 2009 11:11 am
Location: On the planet
Contact:

Re: Format Numbers

Post by Frarth »

srod wrote:.c is a single byte only if compiling an Ascii app. With the Unicode switch set, .c defaults to two bytes. The UTF-16 encoding of the Unicode planes does indeed support 4-bytes, but for the majority of uses the 16-bit .c type is fine and the one to go for.
Thanks for this clarification srod!
PureBasic 5.41 LTS | Xubuntu 16.04 (x32) | Windows 7 (x64)
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: Format Numbers

Post by WilliamL »

[deleted]
Last edited by WilliamL on Sun Jan 30, 2011 10:05 pm, edited 30 times in total.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
User avatar
Rook Zimbabwe
Addict
Addict
Posts: 4322
Joined: Tue Jan 02, 2007 8:16 pm
Location: Cypress TX
Contact:

Re: Format Numbers

Post by Rook Zimbabwe »

Looks nice... I haven't played with it yet but I have been reading some of the code!

My question... does it round numbers up or down or simply truncate? :D
Binarily speaking... it takes 10 to Tango!!!

Image
http://www.bluemesapc.com/
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: Format Numbers

Post by WilliamL »

[deleted]
Last edited by WilliamL on Sun Jan 30, 2011 10:06 pm, edited 1 time in total.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
Post Reply