Page 1 of 2

Radix conversion

Posted: Mon Aug 05, 2013 11:20 am
by Little John
//edit 2015-01-17:
I've created a new module for radix conversion, which is considerably improved compared to this code. However, I leave this old code here (with slight cosmetic changes), since it clearly demonstrates the basic principles.

The new code is below in this thread.

Code: Select all

; tested with PB 5.24 LTS, 5.31 (x86 and x64) on Windows
; <http://www.purebasic.fr/english/viewtopic.php?f=12&t=55799>

EnableExplicit

; Note: If #Symbols$ contains Non-ASCII characters,
;       be sure to compile your program in Unicode mode!
#Symbols$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"


Procedure.q ValB (number$, base.i=10)
   ; -- convert a string with base 'base' to a number
   Protected i.i, digit.i, sign.i=0, srcLen.i, ret.q=0
   
   If base < 2 Or base > Len(#Symbols$)
      ProcedureReturn 0                 ; error
   EndIf
   
   If Left(number$, 1) = "-"
      sign = 1
      number$ = Mid(number$, 2)
   EndIf
   
   srcLen = Len(number$)
   For i = 1 To srcLen
      digit = FindString(#Symbols$, Mid(number$,i,1)) - 1
      If digit < 0 Or digit >= base
         ProcedureReturn 0              ; error
      EndIf
      ret * base + digit
   Next
   
   If sign = 1
      ret = -ret
   EndIf
   ProcedureReturn ret
EndProcedure


Procedure.s StrB (number.q, base.i=10)
   ; -- convert 'number' to a string with base 'base'
   Protected digit.i, sign$="", ret$=""
   
   If base < 2 Or base > Len(#Symbols$)
      ProcedureReturn ""                ; error
   EndIf
   
   If number < 0
      sign$ = "-"
      number = -number
   EndIf
   
   Repeat
      digit = number % base
      ret$ = Mid(#Symbols$, digit+1, 1) + ret$
      number / base
   Until number = 0
   
   ProcedureReturn sign$ + ret$
EndProcedure


CompilerIf #PB_Compiler_IsMainFile
   ;-- Demo
   
   Macro Display (_number_, _base_=10)
      Debug "decimal: " + _number_
      s$ = StrB(_number_, _base_)
      If ValB(s$, _base_) = _number_
         Debug "base " + _base_ + ": " + s$
      Else
         Debug "Error: ValB() <> " + _number_
      EndIf
      Debug ""
   EndMacro
   
   Define s$, i.i, number.i, base.i, n.i=10000
   
   Display( 255, 2)
   Display(-255)
   Display( 255, 16)
   Display(9223372036854775807, 16)
   Display(9223372036854775807,  8)
   
   ; Automatically check StrB() and ValB() for a lot of different numbers and bases:
   For i = 1 To n
      number = Random(2147483647)        ; max. integer = max. argument for Random()
      base   = Random(Len(#Symbols$), 2)
      s$ = StrB(number, base)
      If ValB(s$, base) <> number
         Debug "-- Error:"
         Debug "decimal = " + number
         Debug "base    = " + base
         Debug "StrB()  = " + s$
         Debug "ValB()  = " + ValB(s$, base)
         Debug ""
      EndIf
   Next
   
   Debug "Finished."
CompilerEndIf

Re: Str() and Val() for integers to any base

Posted: Mon Aug 05, 2013 3:11 pm
by Demivec
Thanks. :)

Re: Str() and Val() for integers to any base

Posted: Mon Aug 05, 2013 3:16 pm
by davido
Thank you for sharing. :D

Re: Str() and Val() for integers to any base

Posted: Mon Aug 05, 2013 4:37 pm
by Little John
Thanks, you are welcome!
  • Demo code improved.

Re: Str() and Val() for integers to any base

Posted: Mon Aug 05, 2013 4:56 pm
by LuCiFeR[SD]
very nice.... very nice indeed :)

Re: Str() and Val() for integers to any base

Posted: Mon Aug 05, 2013 5:12 pm
by ts-soft
:D
thx for sharing!

Re: Str() and Val() for integers to any base

Posted: Tue Aug 06, 2013 4:10 pm
by said
nice, thanks for sharing :o

Re: Str() and Val() for integers to any base

Posted: Tue Aug 06, 2013 5:30 pm
by skywalk
I use this variation with some more flexibility. :wink:

Code: Select all

EnableExplicit
;- Base Conversions and KeyCode$
#Base2$  = "01"                   ; Binary
#Base8$ =  "01234567"             ; Octal
#Base10$ = "0123456789"           ; Decimal
#Base16$ = "0123456789ABCDEF"     ; Hex
#Base26$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                                       ; Caps
#Base36$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"                             ; Caps and numerals
#Base62$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"   ; Upper/Lower Case and numerals
#Base64$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"
; Numeric overflow will occur quickly, so it is required to breakup the input string 
; prior To calling the SF_BaseToInt()
#Base94$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz `~!@#$%^&*()-_=+[{]}\|;:',<.>/?"
#Base95$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz `~!@#$%^&*()-_=+[{]}\|;:',<.>/?" + #DQUOTE$
#BaseSecret$ = "ZBCDEFGHIJKLMNOPQRSTUVWXYA" ; Secret scramble, Rearrange as required.
CompilerIf #PB_Compiler_Unicode
  ; MidFast(), PBv4.51 -> minimum 2x faster, except when Length = -1
  Macro MidF(inString, StartPos, Length=-1)
    PeekS(@inString + ((StartPos - 1) * SizeOf(Character)), Length, #PB_Unicode)
  EndMacro
CompilerElse
  Macro MidF(inString, StartPos, Length=-1)
    PeekS(@inString + ((StartPos - 1) * SizeOf(Character)), Length, #PB_Ascii)
  EndMacro
CompilerEndIf
Macro MidFC(inString, StartPos, Length=-1)
  PeekC(@inString + ((StartPos - 1) * SizeOf(Character)))
EndMacro
Procedure.q SF_BaseToInt(base$, BaseToUse$=#Base36$)
  ; Convert baseXX string to positive integer.
  ; Debug SF_BaseToInt("LLPWF6")            ; 1306270050
  ; Debug SF_BaseToInt("1QOyVm",#Base62$)   ; 1306270050
  Protected.i i
  Protected.i BaseLen = Len(base$)
  Protected.i UseBaseLen = Len(BaseToUse$)
  Protected.q x
  For i = 1 To BaseLen
    x = (x * UseBaseLen) + (FindString(BaseToUse$, MidF(base$, i, 1)) - 1)
  Next i
  ProcedureReturn x
EndProcedure

Procedure.s SF_IntToBase(x.q, BaseToUse$=#Base36$)
  ; Convert positive integer to baseXX string.
  ; Debug SF_IntToBase(1306270050)          ; LLPWF6
  ; Debug SF_IntToBase(1306270050,#Base62$) ; 1QOyVm
  Protected.q BaseLen = Len(BaseToUse$)
  Protected.s r$
  If x > 0
    While x <> 0
      r$ = Mid(BaseToUse$, (x % BaseLen) + 1, 1) + r$
      x / BaseLen
    Wend
  Else
    r$ = "0"
  EndIf
  ProcedureReturn r$
EndProcedure

CompilerIf #PB_Compiler_IsMainFile  ;- TEST
  ; Ordered Bases
  Debug RSet(SF_IntToBase(32e6, #Base2$),32)                 +     " [2 <-Base-> 10]      " + Str(SF_BaseToInt("1111010000100100000000000", #Base2$))
  Debug RSet(SF_IntToBase(9223372036854775807, #Base8$),32)  +     " [8 <-Base-> 10]      " + Str(SF_BaseToInt("777777777777777777777", #Base8$))
  Debug RSet(SF_IntToBase(9223372036854775807, #Base16$),32) +     " [16 <-Base-> 10]     " + Str(SF_BaseToInt("7FFFFFFFFFFFFFFF", #Base16$))
  ; Scrambled Bases
  Debug RSet(SF_IntToBase(9223372036854775807, #BaseSecret$),32) + " [Secret <-Base-> 10] " + Str(SF_BaseToInt("DSQYOMTLWMKGIH", #BaseSecret$))
CompilerEndIf
EDIT: repasted, Thanks Demivec.

Re: Str() and Val() for integers to any base

Posted: Tue Aug 06, 2013 6:50 pm
by Demivec
@skywalk: I think your code got butchered when you pasted into your message. :shock:

Re: Str() and Val() for integers to any base

Posted: Tue Aug 06, 2013 8:06 pm
by skywalk
Repasted, thanks Demivec. :oops:

Re: Str() and Val() for integers to any base

Posted: Wed Aug 07, 2013 3:44 pm
by said
Thanks Skywalk for sharing :lol: cool

Re: Radix conversion

Posted: Sat Jan 17, 2015 4:07 pm
by Little John
Version 2.0 (now as module)

Improved
  • ValB()
  • StrB()
New
  • NumDigits()
  • MaxDestDigits()
  • UniqueSymbols()
  • Vast():
    combination of ValB() and StrB(), even for big numbers
  • some macros that wrap Vast()
Internal procedure _ConvertNumber() and related functions are based on
http://jonnydee.wordpress.com/2011/05/0 ... to-base-y/.
Many thanks for that!


Version 2.1
New
Version 2.11
Fixed
  • Bugs in functions for balanced ternary

Code: Select all

; -- Radix Conversion 2.11
; tested with PB 5.24 LTS, 5.31 (x86 and x64) on Windows
; <http://www.purebasic.fr/english/viewtopic.php?f=12&t=55799>
;
; _ConvertNumber() and related functions extended after
; <http://jonnydee.wordpress.com/2011/05/01/convert-a-block-of-digits-from-base-x-to-base-y/>, access on 2015-01-01


DeclareModule Radi
   EnableExplicit
   
   #StdSymbols$ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
   
   ; -- optional utility functions
   Declare.i NumDigits (number.q, base.i)
   Declare.i MaxDestDigits (srcLen.i, srcBase.i, destBase.i)
   Declare.i UniqueSymbols (symbols$)
   
   ; -- similar to PB's Val() and Str(), but for any base >= 2
   Declare.q ValB (number$, srcBase.i, srcSymbols$=#StdSymbols$)
   Declare.s StrB (number.q, destBase.i, groupSize.i=0, destSymbols$=#StdSymbols$)
   
   ; -- combination of ValB() and StrB(), even for big numbers
   Declare.s Vast (source$, srcBase.i, destBase.i, groupSize.i=0, srcSymbols$=#StdSymbols$, destSymbols$=#StdSymbols$)
   
   ; -- some wrappers of Vast()
   Macro Dec2Hex (_dec_, _groupSize_=0)
      Radi::Vast(_dec_, 10, 16, _groupSize_)
   EndMacro
   
   Macro Hex2Dec (_hex_, _groupSize_=3)
      Radi::Vast(UCase(_hex_), 16, 10, _groupSize_)
   EndMacro
   
   Macro Dec2Bin (_dec_, _groupSize_=4)
      Radi::Vast(_dec_, 10, 2, _groupSize_)
   EndMacro
   
   Macro Bin2Dec (_bin_, _groupSize_=3)
      Radi::Vast(_bin_, 2, 10, _groupSize_)
   EndMacro
   
   Macro Dec2Sex (_dec_)
      Radi::Vast(_dec_, 10, 60, -1)
   EndMacro
   
   Macro Sex2Dec (_sex_, _groupSize_=3)
      Radi::Vast(_sex_, 60, 10, _groupSize_, "")
   EndMacro
   
   ; -- balanced ternary, even for big numbers
   Declare.s Any2Bt (source$, srcBase.i,  groupSize.i=0, srcSymbols$=#StdSymbols$)
   Declare.s Bt2Any (source$, destBase.i, groupSize.i=0, destSymbols$=#StdSymbols$)
EndDeclareModule


Module Radi
   Structure CharArray
      char.c[0]
   EndStructure
   
   #SignSymbols$ = "-+"
   #ListLeft$    = "{"
   #ListRight$   = "}"
   #ListDelim$   = ","
   #GroupDelim$  = " "
   #BtSymbols$   = "T01"
   
   
   Procedure.i NumDigits (n.q, base.i)
      ; in : n   : whole number >= 0
      ;      base: whole number >= 2
      ; out: return value: number of digits that 'n' has,
      ;                    when represented with base 'base'
      
      If n < 0 Or base < 2
         ProcedureReturn 0       ; error
      ElseIf n > 0
         ProcedureReturn Round(Log(n) / Log(base), #PB_Round_Down) + 1
      Else
         ProcedureReturn 1
      EndIf
   EndProcedure
   
   
   Procedure.i MaxDestDigits (srcLen.i, srcBase.i, destBase.i)
      ; in : srcLen  : number of digits that a given number has when expressed with
      ;                base 'srcBase' in a standard positional numeral system
      ;      srcBase : base of given number (>= 2)
      ;      destBase: destination base     (>= 2)
      ; out: return value: maximum number of digits needed for expressing the number with
      ;                    base 'destBase' in a standard positional numeral system,
      ;                    or 0 on error
      
      If srcBase < 2 Or destBase < 2
         ProcedureReturn 0                      ; error
      Else
         ProcedureReturn Round(srcLen * Log(srcBase) / Log(destBase), #PB_Round_Up)
      EndIf
   EndProcedure
   
   
   Procedure.i UniqueSymbols (symbols$)
      ; in : list of symbols, as used by ValB(), StrB(), Vast(), Any2Bt(), and Bt2Any()
      ; out: return value: #False if 'symbols$' contains duplicate characters,
      ;                    #True otherwise
      
      Protected i.i, symbolsLen.i=Len(symbols$)
      Protected *sym.CharArray = @ symbols$
      
      For i = 2 To symbolsLen
         If FindString(symbols$, Chr(*sym\char[i-2]), i)
            ProcedureReturn #False
         EndIf
      Next
      
      ProcedureReturn #True
   EndProcedure
   
   
   Procedure.s _Groups (s$, groupSize.i, delim$)
      ; -- partition 's$' by inserting 'delim$'
      
      Protected i.i=Len(s$)
      
      While i > groupSize
         i - groupSize
         s$ = InsertString(s$, delim$, i+1)
      Wend
      
      ProcedureReturn s$
   EndProcedure
   
   
   Macro _ValPrepareSource
      ; used in ValB() and _ValDigits()
      
      If srcBase < 2
         ProcedureReturn 0                    ; error
      EndIf
      
      srcSymbols$ = Left(srcSymbols$, srcBase)
      left  = FindString(source$, #ListLeft$)
      right = FindString(source$, #ListRight$)
      
      If (left And right) And
         (FindString(srcSymbols$, #ListLeft$)  = 0 And
          FindString(srcSymbols$, #ListRight$) = 0)
         srcSymbolsLen = 0
      Else
         srcSymbolsLen = Len(srcSymbols$)
      EndIf
      
      If srcSymbolsLen > 0
         If srcSymbolsLen < srcBase
            ProcedureReturn 0                 ; error
         EndIf
         
         sign$ = Left(source$, 1)
         If FindString(srcSymbols$, sign$) = 0
            sign = FindString(#SignSymbols$, sign$)
            If sign > 0
               source$ = Mid(source$, 2)
            EndIf
         EndIf
         If FindString(srcSymbols$, #GroupDelim$) = 0
            source$ = RemoveString(source$, #GroupDelim$)
         EndIf
         
      Else
         sign$ = Trim(Left(source$, left-1))
         sign = FindString(#SignSymbols$, sign$)
         source$ = Mid(source$, left+1, right-left-1)
      EndIf
      
      If sign > 0 And offset < 0
         ProcedureReturn 0                    ; error
      EndIf
      
      srcLen = Len(source$)
      If srcLen = 0
         ProcedureReturn 0                    ; error
      ElseIf srcSymbolsLen = 0
         srcLen = CountString(source$, #ListDelim$) + 1
      EndIf
   EndMacro
   
   
   Procedure.q ValB (source$, srcBase.i, srcSymbols$=#StdSymbols$)
      ; -- convert 'source$' from string representation with base 'srcBase' to a quad
      ; in : see elaborate description of parameters at Vast()
      ; out: return value: value of 'source$',
      ;                    or 0 on error
      
      Protected i.i, digit.i, sign.i=0, sign$="", ret.q=0
      Protected *src.CharArray, srcLen.i, srcSymbolsLen.i
      Protected left.i, right.i, offset.i
      
      _ValPrepareSource
      
      If srcSymbolsLen > 0
         *src = @ source$
         For i = 1 To srcLen
            digit = FindString(srcSymbols$, Chr(*src\char[i-1])) - 1
            If digit < 0
               ProcedureReturn 0              ; error
            EndIf
            ret * srcBase + digit
         Next
         
      Else
         For i = 1 To srcLen
            digit = Val(StringField(source$, i, #ListDelim$))
            If digit < 0 Or digit >= srcBase
               ProcedureReturn 0              ; error
            EndIf
            ret * srcBase + digit
         Next
      EndIf
      
      If ret < 0
         ret = 0                              ; error: overflow
      ElseIf sign = 1                         ; 1 means negative
         ret = -ret
      EndIf
      
      ProcedureReturn ret
   EndProcedure
   
   
   Procedure.s StrB (number.q, destBase.i, groupSize.i=0, destSymbols$=#StdSymbols$)
      ; -- convert 'number' to a string representation with base 'destBase'
      ; For details about input parameters and return value see elaborate description at Vast().
      
      Protected digit.i, sign$="", ret$
      Protected *destSymbols.CharArray, destSymbolsLen.i=Len(destSymbols$)
      
      If destBase < 2
         ProcedureReturn ""                ; error
      EndIf
      
      If groupSize < 0 Or destSymbolsLen < destBase
         destSymbolsLen = 0
      EndIf
      
      If number < 0
         sign$ = Left(#SignSymbols$, 1)
         number = -number
      EndIf
      
      If destSymbolsLen > 0
         *destSymbols = @ destSymbols$
         ret$ = ""
         Repeat
            digit = number % destBase
            ret$ = Chr(*destSymbols\char[digit]) + ret$
            number / destBase
         Until number = 0
         
         If groupSize > 0 And FindString(destSymbols$, #GroupDelim$) = 0
            ret$ = _Groups(ret$, groupSize, #GroupDelim$)
         EndIf
         ProcedureReturn sign$ + ret$
         
      Else
         digit = number % destBase
         ret$ = Str(digit)
         number / destBase
         While number <> 0
            digit = number % destBase
            ret$ = Str(digit) + #ListDelim$ + ret$
            number / destBase
         Wend
         
         ProcedureReturn sign$ + #ListLeft$ + ret$ + #ListRight$
      EndIf
   EndProcedure
   
   
   Procedure.i _ValDigits (source$, srcBase.i, srcSymbols$, Array srcDigits.i(1), offset.i=0)
      ; -- convert 'source$' from string representation with base 'srcBase' to an array of digits
      ;    (array element 0 is reserved for the sign)
      ; returns 1 on success, 0 on error
      
      Protected i.i, digit.i, sign.i=0, sign$=""
      Protected *src.CharArray, srcLen.i, srcSymbolsLen.i
      Protected left.i, right.i
      
      _ValPrepareSource
      
      Dim srcDigits.i(srcLen)
      srcDigits(0) = sign                       ; 1 means negative
      
      If srcSymbolsLen > 0
         *src = @ source$
         For i = 1 To srcLen
            digit = FindString(srcSymbols$, Chr(*src\char[i-1])) - 1 + offset
            If digit < offset
               ProcedureReturn 0                ; error
            EndIf
            srcDigits(i) = digit
         Next
         
      Else
         For i = 1 To srcLen
            digit = Val(StringField(source$, i, #ListDelim$))
            If digit < offset Or digit >= srcBase + offset
               ProcedureReturn 0                ; error
            EndIf
            srcDigits(i) = digit
         Next
      EndIf
      
      ProcedureReturn 1                         ; success
   EndProcedure
   
   
   Procedure.s _StrDigits (Array destDigits.i(1), destBase.i, groupSize.i, destSymbols$, offset.i=0)
      ; -- convert array of digits with base 'destBase' to a string representation
      ;    (array element 0 is reserved for the sign)
      ; 'destBase' is always >= 2
      
      Protected msd.i, i.i, sign$="", ret$, destLen.i=ArraySize(destDigits())
      Protected *destSymbols.CharArray, destSymbolsLen.i=Len(destSymbols$)
      
      If groupSize < 0 Or destSymbolsLen < destBase
         destSymbolsLen = 0
      EndIf
      
      If destDigits(0) = 1                      ; 1 means negative
         sign$ = Left(#SignSymbols$, 1)
      EndIf
      
      msd = 1                                   ; first index behind the sign
      While destDigits(msd) = 0 And msd < destLen
         msd + 1                                ; skip leading zeros
      Wend
      
      If destSymbolsLen > 0
         *destSymbols = @ destSymbols$
         ret$ = ""
         For i = msd To destLen
            ret$ + Chr(*destSymbols\char[destDigits(i)-offset])
         Next
         
         If groupSize > 0 And FindString(destSymbols$, #GroupDelim$) = 0
            ret$ = _Groups(ret$, groupSize, #GroupDelim$)
         EndIf
         ProcedureReturn sign$ + ret$
         
      Else
         ret$ = Str(destDigits(msd))
         For i = msd+1 To destLen
            ret$ + #ListDelim$ + Str(destDigits(i))
         Next
         
         ProcedureReturn sign$ + #ListLeft$ + ret$ + #ListRight$
      EndIf
   EndProcedure
   
   ;--------------------------------------------------------------------
   
   Procedure _NegNumber (Array digits.i(1))
      ; -- negation of each element in 'digits()'
      
      Protected i.i
      
      For i = ArraySize(digits()) To 1 Step -1
         digits(i) = -digits(i)
      Next
   EndProcedure
   
   
   Procedure _IncDigitsByValue (Array digits.i(1), msd.i, value.i, base.i)
      ; -- increment each element of 'digits()' in the given range by 'value',
      ;    using radix-'base' arithmetic
      
      Protected tmp.q, i.i, carry.i=0
      
      For i = ArraySize(digits()) To 1 Step -1  ; Array element 0 is reserved for the sign.
         If i >= msd
            carry + value
         ElseIf carry = 0
            Break
         EndIf   
         tmp = digits(i) + carry
         digits(i) = tmp % base
         carry = tmp / base
      Next
   EndProcedure
   
   
   Procedure _IncNumberByValue (Array digits.i(1), value.i, base.i)
      ; -- increment 'digits()' by 'value', using radix-'base' arithmetic
      
      Protected tmp.q, i.i, carry.i=value
      
      For i = ArraySize(digits()) To 1 Step -1  ; Array element 0 is reserved for the sign.
         If carry = 0
            Break
         EndIf
         tmp = digits(i) + carry
         digits(i) = tmp % base
         carry = tmp / base
         If digits(i) < 0
            digits(i) + base
            carry - 1
         EndIf
      Next
   EndProcedure
   
   
   Procedure _MultNumberByValue (Array digits.i(1), value.i, base.i)
      ; -- multiply 'digits()' by 'value', using radix-'base' arithmetic
      
      Protected tmp.q, i.i, carry.i=0
      
      For i = ArraySize(digits()) To 1 Step -1  ; Array element 0 is reserved for the sign.
         tmp = digits(i) * value + carry        ; Very big source and destination bases can cause an overflow here.
         digits(i) = tmp % base
         carry = tmp / base
      Next
   EndProcedure
   
   
   Procedure.i _ConvertNumber (Array srcDigits.i(1), srcBase.i, Array destDigits.i(1), destBase.i)
      ; -- convert source digits to destination digits
      ; returns 1 on success, 0 on error
      
      Protected i.i, destLen.i, srcLen.i=ArraySize(srcDigits())
      
      destLen = MaxDestDigits(srcLen, srcBase, destBase)
      If destLen = 0
         ProcedureReturn 0                      ; error
      Else
         Dim destDigits(destLen+1)              ; set sufficient array size
      EndIf                                     ; (+1 is needed for Any2Bt())
      
      ; -- do conversion
      _IncNumberByValue(destDigits(), srcDigits(1), destBase)
      For i = 2 To srcLen
         _MultNumberByValue(destDigits(), srcBase, destBase)
         _IncNumberByValue (destDigits(), srcDigits(i), destBase)
      Next
      destDigits(0) = srcDigits(0)              ; copy the sign
      
      ProcedureReturn 1                         ; success
   EndProcedure
   
   ;--------------------------------------------------------------------
   
   Procedure.s Vast (source$, srcBase.i, destBase.i, groupSize.i=0, srcSymbols$=#StdSymbols$, destSymbols$=#StdSymbols$)
      ; -- combination of ValB() and StrB() for numbers of (almost) arbitrary size;
      ;    converts a whole number from any integer base >= 2 to any integer base >= 2
      ;    (On 32 bit systems, both bases can be as big as the largest positive signed integer value (> 2*10^9).
      ;     On 64 bit systems, both bases can be even > 3*10^9.)
      ; in : source$     : string that represents a whole number (may have a leading '+' or '-'),
      ;                    given in one of the following formats:
      ;                    (a) list of characters that are contained in 'srcSymbols$'
      ;                        (optionally including '#GroupDelim$' at any positions
      ;                        for cosmetic reasons), e.g.
      ;                             1101 0010
      ;                            -FF
      ;                    (b) list of decimal numbers, surrounded by '#ListLeft$' and
      ;                        '#ListRight$', and separated by '#ListDelim$', e.g.
      ;                             {200,31,17,5,124}
      ;                            -{4,15,9}
      ;                        A number in this format can only be recognized correctly if
      ;                        'srcSymbols$' does not contain '#ListLeft$' or '#ListRight$'.
      ;      srcBase     : base of 'source$'
      ;      destBase    : destination base
      ;      groupSize   : If 'groupSize' is > 0 and 'destSymbols$' is not empty, then for
      ;                    pure cosmetic reasons the returned string will be partitioned from
      ;                    right to left into groups of 'groupSize' symbols, separated by
      ;                    '#GroupDelim$' (unless '#GroupDelim$' is contained in 'destSymbols$').
      ;      srcSymbols$ : list of unique characters;
      ;                    The first 'srcBase' characters of the list are allowed in 'source$'
      ;                    of format (a).
      ;      destSymbols$: list of unique characters;
      ;                    The first 'destBase' characters of the list are allowed for
      ;                    building a destination number of format (a).
      ;            The values of the symbols in 'srcSymbols$' and 'destSymbols$' are 0,1,2,...,
      ;            increasing from left to right.
      ;            Note: If 'srcSymbols$' or 'destSymbols$' contain any non-ASCII characters,
      ;                  be sure to compile your program in Unicode mode!
      ; out: return value: 'source$' expressed with 'destBase' (may have a leading '-'),
      ;                    in one of the following formats:
      ;                    (a) If 'groupSize' is >= 0 and 'destSymbols$' has at least 'destBase'
      ;                        characters, then the return value will be a list of characters
      ;                        that are contained in 'destSymbols$' (optionally separated by
      ;                        '#GroupDelim$'), e.g.
      ;                             1101 0010
      ;                            -FF
      ;                    (b) Otherwise it will be a list of decimal numbers, surrounded by
      ;                        '#ListLeft$' and '#ListRight$', and separated by '#ListDelim$', e.g.
      ;                             {200,31,17,5,124}
      ;                            -{4,15,9}
      ;                        For human readers, this format is especially useful if 'destBase'
      ;                        is rather big, so they don't have to memorize the meaning of many
      ;                        symbols.
      ;                    or "" on error
      
      Protected Dim srcDigits.i(0)
      Protected Dim destDigits.i(0)
      
      If _ValDigits(source$, srcBase, srcSymbols$, srcDigits())
         If _ConvertNumber(srcDigits(), srcBase, destDigits(), destBase)
            ProcedureReturn _StrDigits(destDigits(), destBase, groupSize, destSymbols$)
         EndIf
      EndIf
      
      ProcedureReturn ""            ; error
   EndProcedure
   
   
   Procedure.s Any2Bt (source$, srcBase.i, groupSize.i=0, srcSymbols$=#StdSymbols$)
      ; -- convert even a big whole standard number with any integer base >= 2 to a "balanced ternary" number
      ; Balanced ternary is a non-standard positional numeral system with base 3,
      ; using the digits -1, 0, and 1. Balanced ternary numbers do not have an explicit
      ; sign, but it is implicitly given by the most significant (= leftmost) nonzero digit.
      ; (see also http://en.wikipedia.org/wiki/Balanced_ternary)
      ; [code according to Knuth: TAOCP Vol. 2, 3rd ed. 1997, p. 207]
      ;
      ; For details about input parameters and return value see elaborate description at Vast().
      
      Protected msd.i, i.i, destLen.i
      Protected Dim srcDigits.i(0)
      Protected Dim destDigits.i(0)
      
      If _ValDigits(source$, srcBase, srcSymbols$, srcDigits())
         If _ConvertNumber(srcDigits(), srcBase, destDigits(), 3)  ; initially, convert to standard base 3 number
            destLen = ArraySize(destDigits())
            msd = 1                                                ; first index behind the sign
            While destDigits(msd) = 0 And msd < destLen
               msd + 1                                             ; skip leading zeros
            Wend
            _IncDigitsByValue(destDigits(), msd, 1, 3)
            For i = msd To destLen
               destDigits(i) - 1
            Next
            
            If destDigits(0) = 1                                   ; 1 means negative
               destDigits(0) = 0
               _NegNumber(destDigits())
            EndIf
            
            ProcedureReturn _StrDigits(destDigits(), 3, groupSize, #BtSymbols$, -1)
         EndIf
      EndIf
      
      ProcedureReturn ""                                           ; error
   EndProcedure
   
   
   Procedure.s Bt2Any (source$, destBase.i, groupSize.i=0, destSymbols$=#StdSymbols$)
      ; -- convert even a big whole "balanced ternary" number to a standard number with any integer base >= 2
      ; [code according to Knuth: TAOCP Vol. 2, 3rd ed. 1997, p. 207]
      ;
      ; For details about input parameters and return value see elaborate description at Vast().
      
      Protected msd.i, srcLen.i
      Protected Dim srcDigits.i(0)
      Protected Dim destDigits.i(0)
      
      If _ValDigits(source$, 3, #BtSymbols$, srcDigits(), -1)
         srcLen = ArraySize(srcDigits())
         msd = 1                                      ; first index behind the sign
         While srcDigits(msd) = 0 And msd < srcLen
            msd + 1                                   ; skip leading zeros
         Wend
         
         If srcDigits(msd) < 0
            srcDigits(0) = 1                          ; 1 means negative
            _NegNumber(srcDigits())
         EndIf
         
         If _ConvertNumber(srcDigits(), 3, destDigits(), destBase)
            ProcedureReturn _StrDigits(destDigits(), destBase, groupSize, destSymbols$)
         EndIf
      EndIf
      
      ProcedureReturn ""                              ; error
   EndProcedure
EndModule


CompilerIf #PB_Compiler_IsMainFile
   ; * Module demo *
   
   EnableExplicit
   
   Define n.i, src$, dest.q, dest$, symbols$
   
   Macro Demo_NumDigits (_number_, _base_)
      n = Radi::NumDigits(_number_, _base_)
      Debug "When represented with base " + _base_ + ", the decimal number " + _number_ + " has " + n + " digit(s)."
   EndMacro
   
   Macro Demo_MaxDestDigits (_srcLen_, _srcBase_, _destBase_)
      n = Radi::MaxDestDigits(_srcLen_, _srcBase_, _destBase_)
      Debug "A number that has " + _srcLen_ + " digits when expressed with base " + _srcBase_
      Debug "  has at most " + n + " digits when expressed with base " + _destBase_ + "."
   EndMacro
   
   Macro Demo_UniqueSymbols (_symbols_)
      n = Radi::UniqueSymbols(_symbols_)
      Debug "UniqueSymbols(" + #DQUOTE$ + _symbols_ + #DQUOTE$ + ") = " + n
   EndMacro
   
   Macro Demo_ValB (_number_, _srcBase_, _srcSymbols_=Radi::#StdSymbols$)
      dest = Radi::ValB(_number_, _srcBase_, _srcSymbols_)
      Debug "ValB(" + #DQUOTE$ + _number_ + #DQUOTE$ + ", " + _srcBase_ + ") = " + dest
   EndMacro
   
   Macro Demo_StrB (_number_, _destBase_, _groupSize_=0, _destSymbols_=Radi::#StdSymbols$)
      dest$ = Radi::StrB(_number_, _destBase_, _groupSize_, _destSymbols_)
      Debug "StrB(" + _number_ + ", " + _destBase_ + ") = " + #DQUOTE$ + dest$ + #DQUOTE$
   EndMacro
   
   Macro Demo_Vast (_number_, _srcBase_, _destBase_, _groupSize_=0, _srcSymbols_=Radi::#StdSymbols$, _destSymbols_=Radi::#StdSymbols$)
      dest$ = Radi::Vast(_number_, _srcBase_, _destBase_, _groupSize_, _srcSymbols_, _destSymbols_)
      Debug "   base " + _srcBase_  + ": " + _number_
      Debug "-> base " + _destBase_ + ": " + dest$
      Debug ""
   EndMacro
   
   Macro Demo_Any2Bt (_number_, _srcBase_, _groupSize_=0, _srcSymbols_=Radi::#StdSymbols$)
      dest$ = Radi::Any2Bt(_number_, _srcBase_, _groupSize_, _srcSymbols_)
      Debug "Any2Bt(" + #DQUOTE$ + _number_ + #DQUOTE$ + ", " + _srcBase_ + ") = " + dest$
   EndMacro
   
   Macro Demo_Bt2Any (_number_, _destBase_, _groupSize_=0, _destSymbols_=Radi::#StdSymbols$)
      dest$ = Radi::Bt2Any(_number_, _destBase_, _groupSize_, _destSymbols_)
      Debug "Bt2Any(" + #DQUOTE$ + _number_ + #DQUOTE$ + ", " + _destBase_ + ") = " + dest$
   EndMacro
   
   ;--------------------------------------------------------------------
   
   Debug "-- NumDigits()"
   
   Demo_NumDigits(12,  2)
   Demo_NumDigits(12, 10)
   Demo_NumDigits(12, 16)
   
   Debug ""
   Debug "-- MaxDestDigits()"
   
   Demo_MaxDestDigits(154, 10,   2)
   Demo_MaxDestDigits(128, 16,   2)
   Demo_MaxDestDigits(160, 10, 100)
   
   Debug ""
   Debug "-- UniqueSymbols()"
   
   Demo_UniqueSymbols("ABab")
   Demo_UniqueSymbols("ABCA")
   
   Debug ""
   Debug "-- ValB()"
   
   Demo_ValB("1111 1111",  2)
   Demo_ValB("-FF"      , 16)
   Demo_ValB("-{15,15}" , 16)
   
   ; In contrast to PB's built-in Val() function,
   ; ValB() will return 0 to indicate an error,
   ; if the source contains any not permitted symbol:
   Debug Val("$CX")             ; -> 12
   Debug Radi::ValB("CX", 16)   ; -> 0
   
   Debug ""
   Debug "-- StrB()"
   
   Demo_StrB(-255, 16)          ; output in format (a)
   Demo_StrB( 255,  2,  4)      ; output in format (a) with group delimiter
   Demo_StrB(-255, 16, -1)      ; groupSize = -1 forces output in format (b)
   Demo_StrB(1000, 60)          ; format (b) is automatically chosen for output because 60 > Len(destSymbols$)
   
   Debug ""
   Debug "-- Vast()"
   
   Demo_Vast("40 000"    , 10, 16)
   Demo_Vast("40 000"    , 10, 16, -1)
   Demo_Vast("-123456789012345678901234567890", 10,  16)
   Demo_Vast("-123456789012345678901234567890", 10, 256)
   Demo_Vast("9C40"      , 16, 10, 3)
   Demo_Vast("{9,12,4,0}", 16, 10)
   Demo_Vast("9C40"      , 16, 36)
   Demo_Vast("9C40"      , 16, 60)
   
   Demo_Vast("{2,46,40}", 60, 10, 3)
   Demo_Vast("{2,46,40}", 60, 16)
   Demo_Vast("{2,46,40}", 60, 36)
   
   src$ = "AHZ46H67JLR4KCGFWQBDYIHO"
   symbols$ = "~#.^°/&!"
   If Radi::UniqueSymbols(symbols$)
      Debug "Using special destination symbols ..."
      Demo_Vast(src$, Len(Radi::#StdSymbols$), Len(symbols$), 0, Radi::#StdSymbols$, symbols$)
   Else
      Debug "Error: Duplicate symbols in " + #DQUOTE$ + symbols$ + #DQUOTE$
      Debug ""
   EndIf
   
   Debug "-- Dec2Sex()"
   
   src$ = "20 000"
   dest$ = Radi::Dec2Sex(src$)
   Debug src$ + "     (seconds)"
   Debug dest$ + "  (hours,minutes,seconds)"
   
   Debug ""
   Debug "-- Sex2Dec()"
   
   src$ = dest$
   dest$ = Radi::Sex2Dec(src$)
   Debug src$ + "  (hours,minutes,seconds)"
   Debug dest$ + "     (seconds)"
   
   Debug ""
   Debug "-- Any2Bt()"
   
   Demo_Any2Bt(  "8", 10)
   Demo_Any2Bt(  "8", 10, -1)
   Demo_Any2Bt( "33", 10)
   Demo_Any2Bt("-33", 10)
   
   Debug ""
   Debug "-- Bt2Any()"
   
   Demo_Bt2Any( "10T", 10)
   Demo_Bt2Any("{1,0,-1}", 10)
   Demo_Bt2Any("11T0", 10)
   Demo_Bt2Any("TT10", 10)
   Demo_Bt2Any("+10T", 10)       ; A balanced ternary number ...
   Demo_Bt2Any("-10T", 10)       ; ... can not have an explicit sign.
   Demo_Bt2Any( "10T",  2)
CompilerEndIf

Re: Radix conversion

Posted: Sun Jan 18, 2015 1:37 am
by davido
@Little John,

Very neat!
Thank you for sharing. :D

Re: Radix conversion

Posted: Sun Jan 18, 2015 8:11 am
by Little John
When I woke up this morning, I was aware of a shortcoming in the macro Hex2Dec(). :-)
I just changed it, so that now also hex numbers that contain lowercase letters are handled properly:

Code: Select all

Macro Hex2Dec (_hex_, _groupSize_=3)
   Radi::Vast(UCase(_hex_), 16, 10, _groupSize_)
EndMacro
Davido, you are welcome!

Re: Radix conversion

Posted: Sun Jan 18, 2015 5:30 pm
by wilbert
When I run the code on OS X, the output for the base 36 to base 8 conversion seems strange to me

Code: Select all

base 36: AHZ46H67JLR4KCGFWQBDYIHO
base 8: °!^..~.&&#^&^°^///#°.&&/~~°^°/°/^~..&/°^°