[How-To] Decimal numbers to numeral systems of any base, and vice versa
Posted: 18 Oct 2020 11:04
I was just looking for an exercise that makes me forget about the cheerless weather in Germany So I came around some code snippets written a couple of years ago. It turned out my :ToBaseX and :FromBaseX functions had some potentials for a refactoring. Here you go:
- Valid integer values: -2147483648 .. 2147483647
- Valid base values: 2 .. 36
- Invalid values cause undefined behavior
- The result of :ToBaseX is always unsigned
- The result of :FromBaseX is always signed
There are clearly algorithms having a better performance than these. Especially for numeral systems where the radix is a power of two. However, the above functions are aimed to work with any base.
Needless to say how much I hate the hard-coded corner cases in :ToBaseX. I'm wondering if anyone out there knows of a better algorithm that covers the whole range of signed integers? //EDIT: I found the corner case problem Code updated.
Steffen
Code: Select all
@echo off&setlocal EnableDelayedExpansion
:: integer: -2147483648 .. 2147483647
:: base: 2 .. 36
for %%n in (-2147483648 -2147483647 -2 -1 0 1 2 2147483646 2147483647) do (
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for /l %%i in (2 1 36) do (
call :ToBaseX %%n %%i b%%i
echo To Base %%i: !b%%i!
)
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for /l %%i in (2 1 36) do (
call :FromBaseX !b%%i! %%i dec
echo From Base %%i: !dec!
)
echo ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
)
pause
goto :eof
:ToBaseX
:: call :ToBaseX [decimal number] [base] [variable name for the resulting string]
setlocal EnableDelayedExpansion
set "val="&set "map=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2>nul (set /a "dec=(%~1)"||set /a "dec=~2147483647")
set /a "base=(%~2),b=base-1,sh=30-((-(b>>1)>>31)&1)-((-(b>>2)>>31)&1)-((-(b>>3)>>31)&1)-((-(b>>4)>>31)&1)-((-(b>>5)>>31)&1),hi=(dec>>sh)&((1<<(32-sh))-1),op=(dec&((1<<sh)-1))+((hi%%base)<<sh),hi/=base,chk=hi|op"
for /l %%i in (1 1 32) do if !chk! neq 0 (
set /a "d=op%%base,op=(op/base)+((hi%%base)<<sh),hi/=base,chk=hi|op"
for %%D in (!d!) do set "val=!map:~%%D,1!!val!"
)
if not defined val set "val=0"
endlocal&set "%~3=%val%"
exit /b
:FromBaseX
:: call :FromBaseX [numerical string using radix 'base'] [base] [variable name for the resulting decimal number]
setlocal EnableDelayedExpansion
set "val=%~1"
set /a "base=(%~2),dec=0,powers=1,d0=0,d1=1,d2=2,d3=3,d4=4,d5=5,d6=6,d7=7,d8=8,d9=9,dA=10,dB=11,dC=12,dD=13,dE=14,dF=15,dG=16,dH=17,dI=18,dJ=19,dK=20,dL=21,dM=22,dN=23,dO=24,dP=25,dQ=26,dR=27,dS=28,dT=29,dU=30,dV=31,dW=32,dX=33,dY=34,dZ=35"
for /l %%i in (1 1 32) do if defined val (
for %%D in (!val:~-1!) do set /a "dec+=!d%%D!*powers,powers*=base"
set "val=!val:~0,-1!"
)
endlocal&set "%~3=%dec%"
exit /b
- Valid base values: 2 .. 36
- Invalid values cause undefined behavior
- The result of :ToBaseX is always unsigned
- The result of :FromBaseX is always signed
There are clearly algorithms having a better performance than these. Especially for numeral systems where the radix is a power of two. However, the above functions are aimed to work with any base.
Needless to say how much I hate the hard-coded corner cases in :ToBaseX. I'm wondering if anyone out there knows of a better algorithm that covers the whole range of signed integers? //EDIT: I found the corner case problem Code updated.
Steffen