Setting the 32nd and 64th bits is tricky.
32-bit Solution:
I got it to work for 32-bit fields. The trick is to cast the return value of the POWER function to binary(4) before casting it to int. If you try to cast directly to int, without first casting to binary(4), you will get an arithmetic overflow exception when operating on the 32nd bit (index 31). Also, you must ensure the expression passed to POWER is of a sufficiently large type (e.g. bigint) to store the maximum return value (2^31), or the POWER function will throw an arithmetic overflow exception.
CREATE FUNCTION [dbo].[SetIntBit]
(
@bitfieldvalue int,
@bitindex int, --(0 to 31)
@bit bit --(0 or 1)
)
RETURNS int
AS
BEGIN
DECLARE @bitmask int = CAST(CAST(POWER(CAST(2 as bigint),@bitindex) as binary(4)) as int);
RETURN
CASE
WHEN @bit = 1 THEN (@bitfieldvalue | @bitmask)
WHEN @bit = 0 THEN (@bitfieldvalue & ~@bitmask)
ELSE @bitfieldvalue --NO CHANGE
END
END
64-bit Problem:
I was going to use a similar approach for 64-bit fields, however I'm finding that the POWER function is returning inaccurate values, despite using the decimal(38) type for the expression/return value. For example: "select POWER(CAST(2 as decimal(38)), 64)" returns 18446744073709552000 (only the first 16 digits are accurate) rather than the correct value of 18446744073709551616. And even though I'd only raise 2 to the 63rd power, that result is still inaccurate.
The documentation of the POWER function indicates that "Internal conversion to float can cause loss of precision if either the money or numeric data types are used." (note that numeric type is functionally equivalent to decimal type).
I think the only way to handle 64-bit fields properly is to operate on their 32-bit halves, but that involves an extra check on the @bitindex property to see which half I need to operate on. Are there any built-in function or better ways to explicitly set those final bits in 32-bit and 64-bit bitmasked fields in TSQL?