Bit Twiddling Hacks in Batch
Posted: 29 Aug 2019 14:49
We already have a math library, arithmetic functions, arrithmetic conditional operations, and probably some more threads about math.
Hereby I'll try to extend this collection with a function library which is more or less based on the C codes found at Bit Twiddling Hacks.
The actual library begins in line 300. The examples on top may give you an indication what they are good for and how to use them.
Actually bitwise operations are incredibly fast. If you execute the test code you won't observe the potential performance though.
The first reason is that the code is quite long. Calling labels is expensive and is getting even worse the more lines a code has.
The second reason is that I try to leave the environment unchanged and to work around side effects of the logical NOT operator (!) in an environment with enabled delayed variable expansion. SETLOCAL internally triggers the creation of a stack with copies of the environment. This is slow, too.
Thus, this library is not necessarily meant to be used as such. The core of each of these functions is a single SET /A statement. Just take it out and include it in your code wherever you need the operation.
Maybe you are wondering for what these operations are useful and why I thought it was worth to open this topic.
I'll give you two examples below ...
Steffen
Hereby I'll try to extend this collection with a function library which is more or less based on the C codes found at Bit Twiddling Hacks.
The actual library begins in line 300. The examples on top may give you an indication what they are good for and how to use them.
Code: Select all
@echo off &setlocal
call :get_int_min &: get -2147483648 which can't be assigned directly
set /a "b1=0" &: 00000000000000000000000000000000
set /a "b2=1" &: 00000000000000000000000000000001
set /a "b3=14" &: 00000000000000000000000000001110
set /a "b4=8" &: 00000000000000000000000000001000
set /a "b5=10" &: 00000000000000000000000000001010
set /a "b6=int_min" &: 10000000000000000000000000000000
set /a "b7=-2147385343" &: 10000000000000011000000000000001
set /a "b8=-1" &: 11111111111111111111111111111111
echo has_single_flag
call :has_single_flag b1 &&echo yes||echo no
call :has_single_flag b2 &&echo yes||echo no
call :has_single_flag b3 &&echo yes||echo no
call :has_single_flag b4 &&echo yes||echo no
call :has_single_flag b5 &&echo yes||echo no
call :has_single_flag b6 &&echo yes||echo no
call :has_single_flag b7 &&echo yes||echo no
call :has_single_flag b8 &&echo yes||echo no
echo(
echo has_any_flag
call :has_any_flag b1 &&echo yes||echo no
call :has_any_flag b2 &&echo yes||echo no
call :has_any_flag b3 &&echo yes||echo no
call :has_any_flag b4 &&echo yes||echo no
call :has_any_flag b5 &&echo yes||echo no
call :has_any_flag b6 &&echo yes||echo no
call :has_any_flag b7 &&echo yes||echo no
call :has_any_flag b8 &&echo yes||echo no
echo(
echo has_no_flag
call :has_no_flag b1 &&echo yes||echo no
call :has_no_flag b2 &&echo yes||echo no
call :has_no_flag b3 &&echo yes||echo no
call :has_no_flag b4 &&echo yes||echo no
call :has_no_flag b5 &&echo yes||echo no
call :has_no_flag b6 &&echo yes||echo no
call :has_no_flag b7 &&echo yes||echo no
call :has_no_flag b8 &&echo yes||echo no
echo(
echo has_all_flags
call :has_all_flags b1 &&echo yes||echo no
call :has_all_flags b2 &&echo yes||echo no
call :has_all_flags b3 &&echo yes||echo no
call :has_all_flags b4 &&echo yes||echo no
call :has_all_flags b5 &&echo yes||echo no
call :has_all_flags b6 &&echo yes||echo no
call :has_all_flags b7 &&echo yes||echo no
call :has_all_flags b8 &&echo yes||echo no
echo(
echo has_msb
call :has_msb b1 &&echo yes||echo no
call :has_msb b2 &&echo yes||echo no
call :has_msb b3 &&echo yes||echo no
call :has_msb b4 &&echo yes||echo no
call :has_msb b5 &&echo yes||echo no
call :has_msb b6 &&echo yes||echo no
call :has_msb b7 &&echo yes||echo no
call :has_msb b8 &&echo yes||echo no
echo(
echo has_lsb
call :has_lsb b1 &&echo yes||echo no
call :has_lsb b2 &&echo yes||echo no
call :has_lsb b3 &&echo yes||echo no
call :has_lsb b4 &&echo yes||echo no
call :has_lsb b5 &&echo yes||echo no
call :has_lsb b6 &&echo yes||echo no
call :has_lsb b7 &&echo yes||echo no
call :has_lsb b8 &&echo yes||echo no
echo(
echo has_all_of
call :has_all_of b1 "2|8" &&echo yes||echo no
call :has_all_of b2 "2|8" &&echo yes||echo no
call :has_all_of b3 "2|8" &&echo yes||echo no
call :has_all_of b4 "2|8" &&echo yes||echo no
call :has_all_of b5 "2|8" &&echo yes||echo no
call :has_all_of b6 "2|8" &&echo yes||echo no
call :has_all_of b7 "2|8" &&echo yes||echo no
call :has_all_of b8 "2|8" &&echo yes||echo no
echo(
echo has_any_of
call :has_any_of b1 "2|8" &&echo yes||echo no
call :has_any_of b2 "2|8" &&echo yes||echo no
call :has_any_of b3 "2|8" &&echo yes||echo no
call :has_any_of b4 "2|8" &&echo yes||echo no
call :has_any_of b5 "2|8" &&echo yes||echo no
call :has_any_of b6 "2|8" &&echo yes||echo no
call :has_any_of b7 "2|8" &&echo yes||echo no
call :has_any_of b8 "2|8" &&echo yes||echo no
echo(
echo has_at_index
call :has_at_index b1 2 &&echo yes||echo no
call :has_at_index b2 2 &&echo yes||echo no
call :has_at_index b3 2 &&echo yes||echo no
call :has_at_index b4 2 &&echo yes||echo no
call :has_at_index b5 2 &&echo yes||echo no
call :has_at_index b6 2 &&echo yes||echo no
call :has_at_index b7 2 &&echo yes||echo no
call :has_at_index b8 2 &&echo yes||echo no
echo(
echo has_none_of
call :has_none_of b1 "2|8" &&echo yes||echo no
call :has_none_of b2 "2|8" &&echo yes||echo no
call :has_none_of b3 "2|8" &&echo yes||echo no
call :has_none_of b4 "2|8" &&echo yes||echo no
call :has_none_of b5 "2|8" &&echo yes||echo no
call :has_none_of b6 "2|8" &&echo yes||echo no
call :has_none_of b7 "2|8" &&echo yes||echo no
call :has_none_of b8 "2|8" &&echo yes||echo no
echo(
echo not_at_index
call :not_at_index b1 2 &&echo yes||echo no
call :not_at_index b2 2 &&echo yes||echo no
call :not_at_index b3 2 &&echo yes||echo no
call :not_at_index b4 2 &&echo yes||echo no
call :not_at_index b5 2 &&echo yes||echo no
call :not_at_index b6 2 &&echo yes||echo no
call :not_at_index b7 2 &&echo yes||echo no
call :not_at_index b8 2 &&echo yes||echo no
echo(
echo count_flags
call :count_flags b1
echo %errorlevel%
call :count_flags b2
echo %errorlevel%
call :count_flags b3
echo %errorlevel%
call :count_flags b4
echo %errorlevel%
call :count_flags b5
echo %errorlevel%
call :count_flags b6
echo %errorlevel%
call :count_flags b7
echo %errorlevel%
call :count_flags b8
echo %errorlevel%
echo(
echo get_lowest
call :get_lowest b1 flag
echo %flag%
call :get_lowest b2 flag
echo %flag%
call :get_lowest b3 flag
echo %flag%
call :get_lowest b4 flag
echo %flag%
call :get_lowest b5 flag
echo %flag%
call :get_lowest b6 flag
echo %flag%
call :get_lowest b7 flag
echo %flag%
call :get_lowest b8 flag
echo %flag%
echo(
echo get_highest
call :get_highest b1 flag
echo %flag%
call :get_highest b2 flag
echo %flag%
call :get_highest b3 flag
echo %flag%
call :get_highest b4 flag
echo %flag%
call :get_highest b5 flag
echo %flag%
call :get_highest b6 flag
echo %flag%
call :get_highest b7 flag
echo %flag%
call :get_highest b8 flag
echo %flag%
echo(
echo flag_to_index
call :flag_to_index 8
echo %errorlevel%
echo(
echo index_to_flag
call :index_to_flag 31 flag
echo %flag%
echo(
echo set_all_of
call :set_all_of b5 "1|4"
echo %b5%
echo(
echo clear_all_of
call :clear_all_of b5 "1|4"
echo %b5%
echo(
echo set_at_index
call :set_at_index b5 0
echo %b5%
echo(
echo clear_at_index
call :clear_at_index b5 0
echo %b5%
echo(
echo clear_lowest
call :clear_lowest b3
echo %b3%
echo(
echo clear_highest
call :clear_highest b3
echo %b3%
echo(
echo flip_all_of
call :flip_all_of b5 "2|4"
echo %b5%
call :flip_all_of b5 "2|4"
echo %b5%
echo(
echo flip_at_index
call :flip_at_index b5 2
echo %b5%
call :flip_at_index b5 2
echo %b5%
echo(
echo pull_next_flag
setlocal EnableDelayedExpansion
set /a "bitset=b5"
echo * %bitset% *
call :count_flags bitset
for /l %%i in (1 1 %errorlevel%) do (
call :pull_next_flag bitset flag
echo !flag!
)
set /a "bitset=b6"
echo * %bitset% *
call :count_flags bitset
for /l %%i in (1 1 %errorlevel%) do (
call :pull_next_flag bitset flag
echo !flag!
)
set /a "bitset=b7"
echo * %bitset% *
call :count_flags bitset
for /l %%i in (1 1 %errorlevel%) do (
call :pull_next_flag bitset flag
echo !flag!
)
set /a "bitset=b8"
echo * %bitset% *
call :count_flags bitset
for /l %%i in (1 1 %errorlevel%) do (
call :pull_next_flag bitset flag
echo !flag!
)
endlocal
echo(
echo logical_shift_right
echo * %b6% *
call :logical_shift_right b6 1
echo %b6%
echo * %b8% *
call :logical_shift_right b8 1
echo %b8%
echo(
pause
exit /b
:: Terminology used:
:: bitset - a set of 32 bits represented by a signed integer in two's complement (that's just the only numeric type which the cmd supports)
:: flag - one bit at a certain position in the bitset which represets a value of a power of two with exponent 0..31 if the bit itself has a value of 1, or 0 if the bit has a value of 0
:: index - zero based index of a bit in the bitset that represents the flag, it also represents the exponent of the power of two of the flag's value (log2)
:: has_ - returns a boolean in errorlevel logic (0 means true, 1 means false)
:: get_ - publish the value of a flag
:: set_ - turn the bit belonging to the flag into 1, regardless of its original value
:: clear_ - turn the bit belonging to the flag into 0, regardless of its original value
:: flip_ - toggle the value of the bit belonging to the flag from 0 to 1 and from 1 to 0 respectively
:: pull_ - get the value of a flag and clear it in the bitset
:: ref_ - parameter marked to expect an argument passed by reference - that is, a variable name has to be passed rather than a value because its value will be updated
:: Also prefer passing by reference of the other arguments if there is any risk to pass -2147483648 by value.
:: An argument for flags might be already precomposed to a bitmask, or passed as list of flags separated with | operators.
:: Surrounding quotation marks are mandatory if the arguments contain terms with special characters or token delimiters.
::::::::::::::::::::::::::::::::: work around assigning -2147483648 ::::::::::::::::::::::::::::::::
:get_int_min [ref_var]
:: defines either variable int_min or the optionally passed variable name with -2147483648 which can't be assigned directly
if "%~1"=="" (set /a "int_min=~2147483647") else set /a "%~1=~2147483647"
exit /b %errorlevel%
::::::::::::::::::::::::::::::::::::::::::: not modifying ::::::::::::::::::::::::::::::::::::::::::
:has_single_flag "bitset"
:: returns 0 if exactly one flag is set (that is, bitset is a power of two with exponent 0..31)
setlocal DisableDelayedExpansion
set /a "e=!(%~1)|!!((%~1)&((%~1)-1))"
endlocal&exit /b %e%
:has_any_flag "bitset"
:: returns 0 if at least one flag is set (that is, bitset is not 0)
setlocal DisableDelayedExpansion
set /a "e=!(%~1)"
endlocal&exit /b %e%
:has_no_flag "bitset"
:: returns 0 if no flag is set (that is, bitset is 0)
setlocal DisableDelayedExpansion
set /a "e=!!(%~1)"
endlocal&exit /b %e%
:has_all_flags "bitset"
:: returns 0 if all flags are set (that is, bitset is -1)
setlocal DisableDelayedExpansion
set /a "e=!!~(%~1)"
endlocal&exit /b %e%
:has_msb "bitset"
:: returns 0 if the most significant bit is set (that is, bitset is a negative value)
setlocal DisableDelayedExpansion
set /a "e=!((%~1)>>31)"
endlocal&exit /b %e%
:has_lsb "bitset"
:: returns 0 if the least significant bit is set (that is, bitset is an odd value)
setlocal DisableDelayedExpansion
set /a "e=!((%~1)&1)"
endlocal&exit /b %e%
:has_all_of "bitset" "flag_1[|flag_2[|...[|flag_n]]]"
:: returns 0 if bitset contains all of the specified flags
setlocal DisableDelayedExpansion
set /a "e=!!(((%~1)&(%~2))-(%~2))"
endlocal&exit /b %e%
:has_any_of "bitset" "flag_1[|flag_2[|...[|flag_n]]]"
:: returns 0 if bitset contains at least one of the specified flags
setlocal DisableDelayedExpansion
set /a "e=!((%~1)&(%~2))"
endlocal&exit /b %e%
:has_at_index "bitset" "index"
:: returns 0 if the flag is set at the specified index
setlocal DisableDelayedExpansion
set /a "e=!((%~1)&(1<<(%~2)))"
endlocal&exit /b %e%
:has_none_of "bitset" "flag_1[|flag_2[|...[|flag_n]]]"
:: returns 0 if bitset contains none of the specified flags
setlocal DisableDelayedExpansion
set /a "e=!!((%~1)&(%~2))"
endlocal&exit /b %e%
:not_at_index "bitset" "index"
:: returns 0 if the flag is 0 at the specified index
setlocal DisableDelayedExpansion
set /a "e=!!((%~1)&(1<<(%~2)))"
endlocal&exit /b %e%
:count_flags "bitset" [ref_num]
:: returns the number of set flags, optionally also assigns it to the variable name passed as second argument
setlocal DisableDelayedExpansion
set /a "b=(%~1)-(((%~1)>>1)&1431655765),b=(b&858993459)+((b>>2)&858993459),c=((b+(b>>4)&252645135)*16843009)>>24"
endlocal&(if "%~2" neq "" set "%~2=%c%")&exit /b %c%
:get_lowest "bitset" ref_flag
:: assigns the lowest set flag to the variable name passed as second argument
set /a "%~2=(%~1)&-(%~1)"
exit /b %errorlevel%
:get_highest "bitset" ref_flag
:: assigns the highest set flag to the variable name passed as second argument
set /a "%~2=(%~1)|((%~1)>>1),%~2|=%~2>>2,%~2|=%~2>>4,%~2|=%~2>>8,%~2|=%~2>>16,%~2-=(%~2>>1)&2147483647"
exit /b %errorlevel%
:flag_to_index "flag" [ref_index]
:: returns the zero-based index of a flag in a 32 bit integer where the flag must be a power of two with exponent 0..31, optionally also assigns it to the variable name passed as second argument
setlocal DisableDelayedExpansion
set /a "i=!!((%~1)&-1431655766),i|=(!!((%~1)&-65536))<<4,i|=(!!((%~1)&-16711936))<<3,i|=(!!((%~1)&-252645136))<<2,i|=(!!((%~1)&-858993460))<<1"
endlocal&(if "%~2" neq "" set "%~2=%i%")&exit /b %i%
:index_to_flag "index" ref_flag
:: assigns the power of two with exponent passed as index (0..31) to the variable name passed as second argument
set /a "%~2=1<<(%~1)"
exit /b %errorlevel%
::::::::::::::::::::::::::::::::::::::::::::: modifying ::::::::::::::::::::::::::::::::::::::::::::
:set_all_of ref_bitset "flag_1[|flag_2[|...[|flag_n]]]"
:: takes the variable name of the bitset, and sets the specified flags in the bitset
set /a "%~1|=(%~2)"
exit /b %errorlevel%
:set_at_index ref_bitset "index"
:: takes the variable name of the bitset, and sets the flag at the specified index
set /a "%~1|=1<<(%~2)"
exit /b %errorlevel%
:clear_all_of ref_bitset "flag_1[|flag_2[|...[|flag_n]]]"
:: takes the variable name of the bitset, and updates the specified flags to 0
set /a "%~1&=~(%~2)"
exit /b %errorlevel%
:clear_at_index ref_bitset "index"
:: takes the variable name of the bitset, and updates the flag at the specified index to 0
set /a "%~1&=~(1<<(%~2))"
exit /b %errorlevel%
:clear_lowest ref_bitset
:: takes the variable name of the bitset, and updates the lowest set flag to 0
set /a "%~1=%~1&(%~1-1)"
exit /b %errorlevel%
:clear_highest ref_bitset
:: takes the variable name of the bitset, and updates the highest set flag to 0
setlocal DisableDelayedExpansion
set /a "f=%~1|(%~1>>1),f|=f>>2,f|=f>>4,f|=f>>8,f|=f>>16,f-=(f>>1)&2147483647,b=%~1&(f-1)"
endlocal&set "%~1=%b%"&exit /b %errorlevel%
:flip_all_of ref_bitset "flag_1[|flag_2[|...[|flag_n]]]"
:: takes the variable name of the bitset, and flips the specified flags in the bitset
set /a "%~1^=(%~2)"
exit /b %errorlevel%
:flip_at_index ref_bitset "index"
:: takes the variable name of the bitset, and flips the flag at the specified index
set /a "%~1^=1<<(%~2)"
exit /b %errorlevel%
:pull_next_flag ref_bitset ref_flag
:: takes the variable name of the bitset, assigns the lowest set flag to the variable name passed as second argument, and updates the found flag to 0
set /a "%~2=%~1&-%~1,%~1=%~1&(%~1-1)"
exit /b %errorlevel%
:logical_shift_right ref_bitset "positions"
:: takes the variable name of the bitset, and shifts flags the specified number of positions to the right while zeros are shifted in to the left
set /a "%~1=(%~1>>(%~2))&(((2147483647>>(%~2))<<1)|1)"
exit /b %errorlevel%
The first reason is that the code is quite long. Calling labels is expensive and is getting even worse the more lines a code has.
The second reason is that I try to leave the environment unchanged and to work around side effects of the logical NOT operator (!) in an environment with enabled delayed variable expansion. SETLOCAL internally triggers the creation of a stack with copies of the environment. This is slow, too.
Thus, this library is not necessarily meant to be used as such. The core of each of these functions is a single SET /A statement. Just take it out and include it in your code wherever you need the operation.
Maybe you are wondering for what these operations are useful and why I thought it was worth to open this topic.
I'll give you two examples below ...
Steffen