<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://www2.sqlblog.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Adam Machanic : T-SQL, bitmasks</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/bitmasks/default.aspx</link><description>Tags: T-SQL, bitmasks</description><dc:language>en</dc:language><generator>CommunityServer 2.1 SP2 (Build: 61129.1)</generator><item><title>Bitmask Handling, part 4: Left-shift and right-shift</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/bitmask-handling-part-4-left-shift-and-right-shift.aspx</link><pubDate>Thu, 13 Jul 2006 01:29:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:88</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>5</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/88.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=88</wfw:commentRss><description>Quick installment this time.  Left-shift and right-shift operators.
&lt;p&gt;
Left-shift and right-shift are integral to binary mathematical
operations as they have two important qualities: Left-shifting a
bitmask once multiplies by two. Right-shifting once divides by two. For
example:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;0011 (base 2) = 1 + 2 = 3&lt;br&gt;&lt;br&gt;3 &amp;lt;&amp;lt; 1 = 0110 (base 2) = 4 + 2 = 6&lt;br&gt;&lt;br&gt;-- Note that &amp;lt;&amp;lt; is the C++ left-shift operator&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Or we could go the other way and divide:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;0110 (base 2) = 4 + 2 = 6&lt;br&gt;&lt;br&gt;6 &amp;gt;&amp;gt; 1 = 0011 (base 2) = 2 + 1 = 3&lt;br&gt;&lt;br&gt;&lt;br&gt;-- But what happens if we do a second right-shift?&lt;br&gt;&lt;br&gt;3 &amp;gt;&amp;gt; 1 = 0001 (base 2) = 1&lt;br&gt;&lt;br&gt;&lt;br&gt;-- Now we've lost a bit (it "fell off the edge") -- causing rounding.&lt;br&gt;-- Luckily, that's the same way SQL Server treats this situation:&lt;br&gt;&lt;br&gt;SELECT 3 / 2 AS [3_Right_1]&lt;br&gt;&lt;br&gt;&lt;br&gt;3_Right_1&lt;br&gt;---------&lt;br&gt;1&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;So now you've seen the basics of left-shifting and right-shifting.
But how easy is it to implement given the bitmask handling framework
already established in previous installments?
&lt;/p&gt;&lt;p&gt;
&lt;i&gt;Very, very easy&lt;/i&gt;!
&lt;/p&gt;&lt;p&gt;Remember that 0011 (base 2) is 3 (base 10) or 0x0003 (base 16),
and has bits 1 and 2 turned on. Left-shifting once should produce 0110
/ 6 / 0x0006 -- bits 2 and 3 turned on.
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x0003&lt;br&gt;&lt;br&gt;DECLARE @LeftShiftNum INT&lt;br&gt;SET @LeftShiftNum = 1&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT Number + @LeftShiftNum AS Number&lt;br&gt;FROM dbo.splitBitmask(@Bitmask)&lt;br&gt;&lt;br&gt;&lt;br&gt;Number&lt;br&gt;------&lt;br&gt;2&lt;br&gt;3&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
And that's literally all there is to it.  Right-shift is just as easy, but we subtract:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x0006&lt;br&gt;&lt;br&gt;DECLARE @RightShiftNum INT&lt;br&gt;SET @RightShiftNum = 1&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT Number - @RightShiftNum AS Number&lt;br&gt;FROM dbo.splitBitmask(@Bitmask)&lt;br&gt;&lt;br&gt;&lt;br&gt;Number&lt;br&gt;------&lt;br&gt;1&lt;br&gt;2&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Right-shifting twice will produce an out-of-bounds bit, 0. But
that's not an issue, because the bitmask re-constitution pattern uses
the bitmaskNumbers table, which conveniently &lt;i&gt;doesn't contain a 0&lt;/i&gt;.  A bit of accidental foresight on my part, perhaps.
&lt;/p&gt;&lt;p&gt;
I have nothing more to say on this issue.  Plug everything back into the re-constitution pattern and we're done:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION bitwiseLeftShift&lt;br&gt;(&lt;br&gt;	@Bitmask VARBINARY(4096),&lt;br&gt;	@LeftShiftNum INT&lt;br&gt;)&lt;br&gt;RETURNS VARBINARY(4096)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;	INSERT @BitsInBitmask&lt;br&gt;	SELECT Number&lt;br&gt;	FROM&lt;br&gt;	(&lt;br&gt;		SELECT Number + @LeftShiftNum AS Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask)&lt;br&gt;	) x&lt;br&gt;	&lt;br&gt;	SET @Bitmask = 0x&lt;br&gt;	&lt;br&gt;	SELECT @Bitmask = @Bitmask +&lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;			)&lt;br&gt;	FROM @BitsInBitmask x&lt;br&gt;	RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;	WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;		(SELECT&lt;br&gt;			CASE MAX(Number) % 8&lt;br&gt;				WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;				ELSE  MAX(Number) / 8&lt;br&gt;			END + 1&lt;br&gt;		FROM @BitsInBitmask)&lt;br&gt;	GROUP BY BitmaskNumbers.Byte&lt;br&gt;	ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;	RETURN(@Bitmask)&lt;br&gt;END&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;-- Here are some examples to prove that everything works:&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseLeftShift(0x03, 1) AS [3_times_two]&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;3_times_two&lt;br&gt;-----------&lt;br&gt;0x06&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseLeftShift(0x03, 3) AS [3_times_eight]&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;3_times_eight&lt;br&gt;-------------&lt;br&gt;0x18&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT CONVERT(int, 0x18) AS [int_0x18]&lt;br&gt;&lt;br&gt;&lt;br&gt;int_0x18&lt;br&gt;--------&lt;br&gt;24&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseLeftShift(0x18, 12) AS [24_times_4096]&lt;br&gt;&lt;br&gt;&lt;br&gt;24_times_4096&lt;br&gt;-------------&lt;br&gt;0x018000&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT CONVERT(int, 0x018000) AS [int_0x018000]&lt;br&gt;&lt;br&gt;&lt;br&gt;int_0x018000&lt;br&gt;------------&lt;br&gt;98304&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT 98304 / 4096 AS [this_will_be_24]&lt;br&gt;&lt;br&gt;&lt;br&gt;this_will_be_24&lt;br&gt;---------------&lt;br&gt;24&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... and the same for right-shift:
&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION bitwiseRightShift&lt;br&gt;(&lt;br&gt;	@Bitmask VARBINARY(4096),&lt;br&gt;	@RightShiftNum INT&lt;br&gt;)&lt;br&gt;RETURNS VARBINARY(4096)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;	INSERT @BitsInBitmask&lt;br&gt;	SELECT Number&lt;br&gt;	FROM&lt;br&gt;	(&lt;br&gt;		SELECT Number - @RightShiftNum AS Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask)&lt;br&gt;	) x&lt;br&gt;	&lt;br&gt;	SET @Bitmask = 0x&lt;br&gt;	&lt;br&gt;	SELECT @Bitmask = @Bitmask +&lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;			)&lt;br&gt;	FROM @BitsInBitmask x&lt;br&gt;	RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;	WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;		(SELECT&lt;br&gt;			CASE MAX(Number) % 8&lt;br&gt;				WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;				ELSE  MAX(Number) / 8&lt;br&gt;			END + 1&lt;br&gt;		FROM @BitsInBitmask)&lt;br&gt;	GROUP BY BitmaskNumbers.Byte&lt;br&gt;	ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;	RETURN(@Bitmask)&lt;br&gt;END&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;--Right-shifting the same number we just left-shifted 12 bits should yeild the same input&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseRightShift(0x018000, 12) AS [should_be_0x18]&lt;br&gt;&lt;br&gt;&lt;br&gt;should_be_0x18&lt;br&gt;--------------&lt;br&gt;0x18&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;--Is overflow handled the same way that SQL Server handles it?&lt;br&gt;&lt;br&gt;SELECT &lt;br&gt;	CONVERT(INT, dbo.bitwiseRightShift(0x018000, 16)) AS [overflow],&lt;br&gt;	98304 / 65536 AS [98304_divide_65536]&lt;br&gt;&lt;br&gt;overflow	98304_divide_8192&lt;br&gt;--------	-----------------&lt;br&gt;1		1&lt;br&gt;&lt;br&gt;&lt;br&gt;--Apparently :)&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Enough for now.  Next installment, the long awaited mathematical operators.  Special thanks to &lt;a href="http://users.drew.edu/skass/" target="#"&gt;Steve Kass&lt;/a&gt; for smacking me around a bit in the comments section of the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/bitmask-handling-part-3-logical-operators.aspx"&gt;last installment&lt;/a&gt;
and teaching me how to properly implement signed numbers in a binary
system. So the next installment will actually be able to deal with
negative integers too. Thanks, Steve!!!&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=88" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/bitmasks/default.aspx">bitmasks</category></item><item><title>Bitmask Handling, part 3: Logical operators</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/bitmask-handling-part-3-logical-operators.aspx</link><pubDate>Thu, 13 Jul 2006 01:28:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:87</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>4</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/87.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=87</wfw:commentRss><description>It's been longer than I hoped since my last installment on &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/dealing-with-very-large-bitmasks.aspx"&gt;bitmask / big number handling&lt;/a&gt;.
Life caught up with me and I've had many thankless tasks to catch up
on. But that's over now and I'm back to the general slacking that
typifies my days, so welcome to Part 3, handling logical operators.
&lt;p&gt;I'll be discussing four operators in this post. AND, OR, XOR,
and NOT. The first three are extremely easy given the framework already
built out in the previous two posts. The last one has some problems --
so I'll discuss those first.
&lt;/p&gt;&lt;p&gt;I haven't been able to find any mathematical or computer
science texts that discuss how to deal with variable-length bitmasks,
which is what I'm attempting to define here. Most texts keep the
discussion to, at most, two or four bytes, and even then those two or
four bytes are stable. But the problem with using a variable number is
that it creates some inconsistencies with regards to signing. Take this
example:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;SELECT 1 AS Oops&lt;br&gt;WHERE &lt;br&gt;	-1 &amp;lt;&amp;gt; &lt;br&gt;	CONVERT(INT, CONVERT(VARBINARY, CONVERT(SMALLINT, -1)))&lt;br&gt;&lt;br&gt;&lt;br&gt;Oops&lt;br&gt;----&lt;br&gt;1&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;So what's happening here? The -1 on the right side of the comparison
is being cast into a 2-byte SMALLINT, then converted to VARBINARY
(which produces 0xFFFF). Then it's being converted back to INT. But
guess what..?
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;SELECT CONVERT(INT, 0xFFFF) AS Arrgh&lt;br&gt;&lt;br&gt;&lt;br&gt;Arrgh&lt;br&gt;-----&lt;br&gt;65535&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
So what is SQL Server telling us?  That a small -1 is not the same as a bigger -1.  &lt;b&gt;-1 &amp;lt;&amp;gt; -1&lt;/b&gt;!!!
&lt;/p&gt;&lt;p&gt;You're probably asking yourself, "what is this guy talking
about, and what does any of this have to do with the topic at hand,
implementing a binary NOT operation?" And if that's what you were
asking yourself, then good job, because you've asked the correct
question. So what do they have to do with each other?
&lt;/p&gt;&lt;p&gt;
The truth-table for NOT is quite simple:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;table&gt;
&lt;tr&gt;&lt;th&gt;Input&lt;/th&gt;&lt;th&gt;Result&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;But let's delve a little deeper. The SMALLINT representation of
the decimal number 1 in hex is 0x0001. In binary, that's
0000000000000001. If you run that through a logical NOT, the result is
1111111111111110, or 0xFFFE. That's -2. But remember, that's only -2 &lt;i&gt;if you're a SMALLINT&lt;/i&gt;.
If you're either an INT or a BIGINT, it's 65534. And that's just not
consistent. I want to know that any equivalent number into my function
yeilds an equivalent number on the way out. So NOT(0x000001) should
yeild the same result as NOT(0x000000000001).
&lt;/p&gt;&lt;p&gt;... and that result, in the function I provide, will be: 0xFE.
One byte in, one byte out. Similar to some prison gang mottos, but
that's a topic for a later post.
&lt;/p&gt;&lt;p&gt;
You'll notice that this is similar to the way I handled the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/bitmask-handling-part-2-bitmask-reconstitution.aspx"&gt;bitmask re-constitution&lt;/a&gt; in the last post.  So I feel pretty good about this.  Sparsity is a good thing.
&lt;/p&gt;&lt;p&gt;
But this has a very important &lt;i&gt;side-effect&lt;/i&gt;. These
numbers are now officially and permanently un-signed. We can't deal
with sign because we can't know which byte corresponds to the highest
byte -- that's variable. And since we can't determine the highest byte,
we also can't determine the highest bit in that byte, and so can't know
whether or not our number is negative.
&lt;/p&gt;&lt;p&gt;
But I can live with that.  And I hope you can, too.  And if you can't, write a solution and send it to me and I'll post it.
&lt;/p&gt;&lt;p&gt;
So on that note, and without further ado, here's how you figure out which bit positions should be output by a &lt;b&gt;NOT&lt;/b&gt; operation:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x01&lt;br&gt;&lt;br&gt;SELECT x.Number &lt;br&gt;FROM BitmaskNumbers x&lt;br&gt;LEFT JOIN dbo.SplitBitmask(@Bitmask) y ON y.Number = x.Number&lt;br&gt;WHERE y.Number IS NULL&lt;br&gt;	AND x.Byte &amp;lt;= &lt;br&gt;		(SELECT MAX(Byte)&lt;br&gt;		FROM BitmaskNumbers z&lt;br&gt;		WHERE z.Number =&lt;br&gt;			(SELECT MAX(Number)&lt;br&gt;			FROM dbo.SplitBitmask(@Bitmask)))&lt;br&gt;&lt;br&gt;&lt;br&gt;Number&lt;br&gt;-------&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;5&lt;br&gt;6&lt;br&gt;7&lt;br&gt;8&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;Pretty simple, really: Split the bitmask and take any numbers within
the same byte range that aren't in the bitmask. To reconstitute it,
simply modify the reconstitution pattern a bit, stuff it all into a
function, and you get:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;CREATE FUNCTION bitwiseNot&lt;br&gt;(&lt;br&gt;	@Bitmask VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS VARBINARY(4096)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;	INSERT @BitsInBitmask&lt;br&gt;	SELECT Number&lt;br&gt;	FROM dbo.splitBitmask(@Bitmask)&lt;br&gt;	&lt;br&gt;	SET @Bitmask = 0x&lt;br&gt;	&lt;br&gt;	SELECT @Bitmask = @Bitmask +&lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NOT NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;			)&lt;br&gt;	FROM @BitsInBitmask x&lt;br&gt;	RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;	WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;		(SELECT&lt;br&gt;			CASE MAX(Number) % 8&lt;br&gt;				WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;				ELSE  MAX(Number) / 8&lt;br&gt;			END + 1&lt;br&gt;		FROM @BitsInBitmask)&lt;br&gt;	GROUP BY BitmaskNumbers.Byte&lt;br&gt;	ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;	RETURN(@Bitmask)&lt;br&gt;END&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.BitwiseNot(0x01) AS Not01&lt;br&gt;&lt;br&gt;&lt;br&gt;Not01&lt;br&gt;-----&lt;br&gt;0xFE&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
And of course, given the properties I described above:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;SELECT dbo.BitwiseNot(0x0000000001) AS Not0000000001&lt;br&gt;&lt;br&gt;&lt;br&gt;Not0000000001&lt;br&gt;-------------&lt;br&gt;0xFE&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
And now on to the other three logical operations, which are much simpler...
&lt;/p&gt;&lt;p&gt;
The easiest is OR, which has the following truth table:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;table&gt;
&lt;tr&gt;&lt;th&gt;+&lt;/th&gt;&lt;th&gt;0&lt;/th&gt;&lt;th&gt;1&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;0&lt;/th&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;1&lt;/th&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
And what is that similar to, in relational parlance..?  A UNION, perhaps?
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;SELECT Number&lt;br&gt;FROM&lt;br&gt;(&lt;br&gt;	SELECT Number&lt;br&gt;	FROM dbo.splitBitmask(0x01)&lt;br&gt;&lt;br&gt;	UNION&lt;br&gt;&lt;br&gt;	SELECT Number&lt;br&gt;	FROM dbo.splitBitmask(0x03)&lt;br&gt;) x&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... and how about exclusive OR (XOR)?
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;table&gt;
&lt;tr&gt;&lt;th&gt;+&lt;/th&gt;&lt;th&gt;0&lt;/th&gt;&lt;th&gt;1&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;0&lt;/th&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;1&lt;/th&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
Similar to the UNION, but we only want intersections with exactly one bitmask position... Luckily, SQL is equipped for that:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;SELECT Number&lt;br&gt;FROM&lt;br&gt;(&lt;br&gt;	SELECT Number FROM dbo.SplitBitmask(0x01)&lt;br&gt;&lt;br&gt;	UNION ALL&lt;br&gt;&lt;br&gt;	SELECT Number FROM dbo.SplitBitmask(0x02)&lt;br&gt;) x&lt;br&gt;GROUP BY Number&lt;br&gt;HAVING COUNT(*) = 1&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Finally, the AND operation:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;table&gt;
&lt;tr&gt;&lt;th&gt;+&lt;/th&gt;&lt;th&gt;0&lt;/th&gt;&lt;th&gt;1&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;0&lt;/th&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;1&lt;/th&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
Just like XOR, but you need exactly &lt;i&gt;two&lt;/i&gt; bit positions in each intersection:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;SELECT Number&lt;br&gt;FROM&lt;br&gt;(&lt;br&gt;	SELECT Number FROM dbo.SplitBitmask(0x01)&lt;br&gt;&lt;br&gt;	UNION ALL&lt;br&gt;&lt;br&gt;	SELECT Number FROM dbo.SplitBitmask(0x02)&lt;br&gt;) x&lt;br&gt;GROUP BY Number&lt;br&gt;HAVING COUNT(*) = 2&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Putting it all together, I present the following OR, XOR, and AND UDFs:
&lt;/p&gt;&lt;p&gt;
&lt;b&gt;OR&lt;/b&gt;
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;CREATE FUNCTION bitwiseOr&lt;br&gt;(&lt;br&gt;	@Bitmask1 VARBINARY(4096),&lt;br&gt;	@Bitmask2 VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS VARBINARY(4096)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;	INSERT @BitsInBitmask&lt;br&gt;	SELECT Number&lt;br&gt;	FROM&lt;br&gt;	(&lt;br&gt;		SELECT Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask1)&lt;br&gt;&lt;br&gt;		UNION&lt;br&gt;&lt;br&gt;		SELECT Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask2)&lt;br&gt;	) x&lt;br&gt;	&lt;br&gt;	SET @Bitmask1 = 0x&lt;br&gt;	&lt;br&gt;	SELECT @Bitmask1 = @Bitmask1 +&lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;			)&lt;br&gt;	FROM @BitsInBitmask x&lt;br&gt;	RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;	WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;		(SELECT&lt;br&gt;			CASE MAX(Number) % 8&lt;br&gt;				WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;				ELSE  MAX(Number) / 8&lt;br&gt;			END + 1&lt;br&gt;		FROM @BitsInBitmask)&lt;br&gt;	GROUP BY BitmaskNumbers.Byte&lt;br&gt;	ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;	RETURN(@Bitmask1)&lt;br&gt;END&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseOr(0x01, 0x03) AS Or_01_03&lt;br&gt;&lt;br&gt;&lt;br&gt;Or_01_03&lt;br&gt;--------&lt;br&gt;0x03&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
&lt;b&gt;XOR&lt;/b&gt;
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;CREATE FUNCTION bitwiseXOr&lt;br&gt;(&lt;br&gt;	@Bitmask1 VARBINARY(4096),&lt;br&gt;	@Bitmask2 VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS VARBINARY(4096)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;	INSERT @BitsInBitmask&lt;br&gt;	SELECT Number&lt;br&gt;	FROM&lt;br&gt;	(&lt;br&gt;		SELECT Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask1)&lt;br&gt;&lt;br&gt;		UNION ALL&lt;br&gt;&lt;br&gt;		SELECT Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask2)&lt;br&gt;	) x&lt;br&gt;	GROUP BY Number&lt;br&gt;	HAVING COUNT(*) = 1&lt;br&gt;	&lt;br&gt;	SET @Bitmask1 = 0x&lt;br&gt;	&lt;br&gt;	SELECT @Bitmask1 = @Bitmask1 +&lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;			)&lt;br&gt;	FROM @BitsInBitmask x&lt;br&gt;	RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;	WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;		(SELECT&lt;br&gt;			CASE MAX(Number) % 8&lt;br&gt;				WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;				ELSE  MAX(Number) / 8&lt;br&gt;			END + 1&lt;br&gt;		FROM @BitsInBitmask)&lt;br&gt;	GROUP BY BitmaskNumbers.Byte&lt;br&gt;	ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;	RETURN(@Bitmask1)&lt;br&gt;END&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseXOr(0x01, 0x03) AS XOr_01_03&lt;br&gt;&lt;br&gt;&lt;br&gt;XOr_01_03&lt;br&gt;--------&lt;br&gt;0x02&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... And finally, &lt;b&gt;AND&lt;/b&gt;
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="CODE"&gt;CREATE FUNCTION bitwiseAnd&lt;br&gt;(&lt;br&gt;	@Bitmask1 VARBINARY(4096),&lt;br&gt;	@Bitmask2 VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS VARBINARY(4096)&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;	INSERT @BitsInBitmask&lt;br&gt;	SELECT Number&lt;br&gt;	FROM&lt;br&gt;	(&lt;br&gt;		SELECT Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask1)&lt;br&gt;&lt;br&gt;		UNION ALL&lt;br&gt;&lt;br&gt;		SELECT Number&lt;br&gt;		FROM dbo.splitBitmask(@Bitmask2)&lt;br&gt;	) x&lt;br&gt;	GROUP BY Number&lt;br&gt;	HAVING COUNT(*) = 2&lt;br&gt;	&lt;br&gt;	SET @Bitmask1 = 0x&lt;br&gt;	&lt;br&gt;	SELECT @Bitmask1 = @Bitmask1 +&lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;			)&lt;br&gt;	FROM @BitsInBitmask x&lt;br&gt;	RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;	WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;		(SELECT&lt;br&gt;			CASE MAX(Number) % 8&lt;br&gt;				WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;				ELSE  MAX(Number) / 8&lt;br&gt;			END + 1&lt;br&gt;		FROM @BitsInBitmask)&lt;br&gt;	GROUP BY BitmaskNumbers.Byte&lt;br&gt;	ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;	RETURN(@Bitmask1)&lt;br&gt;END&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT dbo.bitwiseAnd(0x01, 0x03) AS And_01_03&lt;br&gt;&lt;br&gt;&lt;br&gt;And_01_03&lt;br&gt;--------&lt;br&gt;0x01&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... And that's enough for today's installment.  Enjoy..!&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=87" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/bitmasks/default.aspx">bitmasks</category></item><item><title>Bitmask Handling, part 2: Bitmask reconstitution</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/bitmask-handling-part-2-bitmask-reconstitution.aspx</link><pubDate>Thu, 13 Jul 2006 01:27:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:86</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>2</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/86.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=86</wfw:commentRss><description>Posting the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/dealing-with-very-large-bitmasks.aspx"&gt;first part&lt;/a&gt;
of my series on bitmasks (yes, this is now officially a series) taught
me a lot about my readers: You don't care about handling bitmasks in
the database. And I respect you for that! I'm overjoyed, as a matter of
fact! That article has received the least hits of anything I've posted
in this blog to date. So good for you for not clicking.
&lt;p&gt;However, I'm going to continue and take this thing all the way
to the bitter end. Why? I'm not sure, but I think it has something to
do with my freshman year computer science course, "Data Structures". It
was a course taught in C++, and the very first project was to create a
series of classes that could support working with very large numbers.
Of course, this was all handled via a bunch of bit manipulation, but I
didn't know that because slacker that I was, &lt;i&gt;I never went to class&lt;/i&gt; and only started the project the night before it was due.
&lt;/p&gt;&lt;p&gt;
I never finished the project. But out of the kindness of his heart --
or madness in his brain -- my professor let it slide and I passed the
class anyway (I started going to class and completed the rest of the
projects after that little slip!)
&lt;/p&gt;&lt;p&gt;So here I am with this gaping hole in my education. I never
finished the project for large numbers. And I'm really rusty on my C++
at the moment, but I know my way around SQL pretty well.
&lt;/p&gt;&lt;p&gt;So what's next? I'm thinking we'll start with standard logical
operators: AND, OR, NOT. And then move on to right-shift and
left-shift. From there, it should be a pretty simple jump to addition,
subtraction, multiplication, and division. I think. My other goal -- if
I can figure out how to do it -- is to provide a mechanism by which the
user will be able to view a decimal representation (in the form of a
string) of the number contained in the bitmask. So this isn't really
"handling bitmasks" anymore -- it's now become "&lt;b&gt;how to represent really big numbers&lt;/b&gt;".
&lt;/p&gt;&lt;p&gt;
Anyway, on to the meat of this episode (for the 5 people who are probably going to bother reading this)...
&lt;/p&gt;&lt;p&gt;
We now have a way of taking a bitmask of arbitrary size (up to 4096
bytes, but could be bigger with more rows in the numbers table) and
producing a table of integers representing which "bits" are set to 1 in
the bitmask. That's thanks to the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/correction-on-bitmask-handling.aspx"&gt;splitBitmask function&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;
Integral to finishing this project is a method of going the &lt;i&gt;other way around&lt;/i&gt; -- we need to reconstitute bitmasks from a table of integers representing bit positions.
&lt;/p&gt;&lt;p&gt;
Since this series is a subseries of my series of things not to do in
SQL Server, I'm allowed to use all sorts of dirty tricks to accomplish
my goal. The trick du jour is &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/rowset-string-concatenation-which-method-is-best.aspx"&gt;aggregate concatenation&lt;/a&gt;
(don't you love all of these intra-blog links?). I considered looping
and tried to think of a truly set-based solution, but nothing beats the
fun of letting SQL Server handle the dirty work in a totally
undocumented way.
&lt;/p&gt;&lt;p&gt;As you may recall, the BitmaskNumbers table defined in the
first article contains three columns: A "Number", which represents an
individual bit, a "Byte", which represents a collection of 8 bits, and
a "BitValue", which represents the decimal value of that bit within the
byte.
&lt;/p&gt;&lt;p&gt;Assuming that we have a table of integers representing bit
positions, we can join to the BitmaskNumbers table to get the
associated bytes and bit values for those bits. So for instance, if we
were to split the bitmask 0x1001F3 using the splitBitmask function:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT&lt;br&gt;	BitmaskNumbers.Number,&lt;br&gt;	BitmaskNumbers.Byte,&lt;br&gt;	BitmaskNumbers.Bitvalue&lt;br&gt;FROM dbo.splitBitmask(0x1001F3) x&lt;br&gt;JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;&lt;br&gt;&lt;br&gt;Number	Byte	Bitvalue &lt;br&gt;------	------	-------- &lt;br&gt;1	1	1&lt;br&gt;2	1	2&lt;br&gt;5	1	16&lt;br&gt;6	1	32&lt;br&gt;7	1	64&lt;br&gt;8	1	128&lt;br&gt;9	2	1&lt;br&gt;21	3	16&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;We have three bytes, and the de-aggregated values for the bits in
those bytes. Aggregating them is a simple matter of using the SUM()
function:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT&lt;br&gt;	BitmaskNumbers.Byte,&lt;br&gt;	SUM(BitmaskNumbers.Bitvalue) AS TotalValue&lt;br&gt;FROM dbo.splitBitmask(0x1001F3) x&lt;br&gt;JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;GROUP BY BitmaskNumbers.Byte&lt;br&gt;&lt;br&gt;&lt;br&gt;Byte	TotalValue  &lt;br&gt;------	----------- &lt;br&gt;1	243&lt;br&gt;2	1&lt;br&gt;3	16&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Of course, that's a decimal representation and we require hexadecimal...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT&lt;br&gt;	BitmaskNumbers.Byte,&lt;br&gt;	CONVERT(VARBINARY(1), SUM(BitmaskNumbers.Bitvalue)) AS TotalValue&lt;br&gt;FROM dbo.splitBitmask(0x1001F3) x&lt;br&gt;JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;GROUP BY BitmaskNumbers.Byte&lt;br&gt;ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;&lt;br&gt;Byte	TotalValue &lt;br&gt;------	---------- &lt;br&gt;3	0x10&lt;br&gt;2	0x01&lt;br&gt;1	0xF3&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;... And now you can see our original three bytes: 0x10, 0x01, and
0xF3. Lucky for those of us who want to do weird things with big
bitmasks, SQL Server treats binary values like strings when using the +
operator:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;-- 0x10 + 0x01 != 17&lt;br&gt;&lt;br&gt;-- Instead:&lt;br&gt;&lt;br&gt;SELECT 0x10 + 0x01 AS Concat&lt;br&gt;&lt;br&gt;&lt;br&gt;Concat &lt;br&gt;------ &lt;br&gt;0x1001&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... And one other property that will be useful for aggregate concatenation:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT 0x00 + 0xFF AS Concat&lt;br&gt;&lt;br&gt;&lt;br&gt;Concat &lt;br&gt;------ &lt;br&gt;0x00FF&lt;br&gt;&lt;br&gt;--That's not very useful; how do we initialize a NULL varbinary variable to an empty value?&lt;br&gt;&lt;br&gt;&lt;br&gt;SELECT 0x + 0xFF AS Concat&lt;br&gt;&lt;br&gt;&lt;br&gt;Concat &lt;br&gt;------ &lt;br&gt;0xFF&lt;br&gt;&lt;br&gt;&lt;br&gt;--0x it is...&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
From here, it's a simple step to rebuilding the original bitmask:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x&lt;br&gt;&lt;br&gt;SELECT&lt;br&gt;	@Bitmask = @Bitmask + &lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(BitmaskNumbers.Bitvalue)&lt;br&gt;		)&lt;br&gt;FROM dbo.splitBitmask(0x1001F3) x&lt;br&gt;JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;GROUP BY BitmaskNumbers.Byte&lt;br&gt;ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;SELECT @Bitmask AS TheBitmask&lt;br&gt;&lt;br&gt;&lt;br&gt;TheBitmask&lt;br&gt;------------&lt;br&gt;0x1001F3&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
But this bitmask has no blank spots.  What if we switch to 0xFF00FF?
&lt;/p&gt;&lt;pre class="code"&gt;TheBitmask&lt;br&gt;------------&lt;br&gt;0xFFFF&lt;br&gt;&lt;br&gt;--That's not good!&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Changing the INNER JOIN to a RIGHT JOIN and adding a NULL check solves the problem a bit:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x&lt;br&gt;&lt;br&gt;SELECT&lt;br&gt;	@Bitmask = @Bitmask + &lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;		)&lt;br&gt;FROM dbo.splitBitmask(0xFF00FF) x&lt;br&gt;RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;GROUP BY BitmaskNumbers.Byte&lt;br&gt;ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;SELECT @Bitmask AS TheBitmask&lt;br&gt;&lt;br&gt;&lt;br&gt;TheBitmask&lt;br&gt;-------------&lt;br&gt;0x00000000000000000000000000000000000000000000 ... FF00FF&lt;br&gt;&lt;br&gt;--Long string of zeroes followed by the original bitmask truncated for brevity&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;To keep things sparse, we should only take as many bytes as we
require. We can filter the results post-join, and also take advantage
of the index that was created on the Byte column in the first
installment:
&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x&lt;br&gt;&lt;br&gt;SELECT&lt;br&gt;	@Bitmask = @Bitmask + &lt;br&gt;		CONVERT(VARBINARY(1), &lt;br&gt;			SUM(CASE&lt;br&gt;				WHEN x.Number IS NULL THEN 0&lt;br&gt;				ELSE BitmaskNumbers.BitValue&lt;br&gt;				END)&lt;br&gt;		)&lt;br&gt;FROM dbo.splitBitmask(0xFF00FF) x&lt;br&gt;RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;	(SELECT&lt;br&gt;		CASE MAX(Number) % 8&lt;br&gt;			WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;			ELSE  MAX(Number) / 8&lt;br&gt;		END + 1&lt;br&gt;	FROM dbo.splitBitmask(0xFF00FF))&lt;br&gt;GROUP BY BitmaskNumbers.Byte&lt;br&gt;ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;SELECT @Bitmask AS TheBitmask&lt;br&gt;&lt;br&gt;&lt;br&gt;TheBitmask&lt;br&gt;------------&lt;br&gt;0xFF00FF&lt;br&gt;&lt;br&gt;--Finally, the correct re-constituted output&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;A small optimization is using a table variable for the results of
splitBitmask so that it will only have to be executed once. So the
final pattern I'll present for solving this problem, ready for
insertion into a variety of new UDFs, is:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE @Bitmask VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x11002200330044&lt;br&gt;&lt;br&gt;DECLARE @BitsInBitmask TABLE(Number SMALLINT)&lt;br&gt;INSERT @BitsInBitmask&lt;br&gt;SELECT Number&lt;br&gt;FROM dbo.splitBitmask(@Bitmask)&lt;br&gt;&lt;br&gt;SET @Bitmask = 0x&lt;br&gt;&lt;br&gt;SELECT @Bitmask = @Bitmask +&lt;br&gt;	CONVERT(VARBINARY(1), &lt;br&gt;		SUM(CASE&lt;br&gt;			WHEN x.Number IS NULL THEN 0&lt;br&gt;			ELSE BitmaskNumbers.BitValue&lt;br&gt;			END)&lt;br&gt;		)&lt;br&gt;FROM @BitsInBitmask x&lt;br&gt;RIGHT JOIN BitmaskNumbers ON BitmaskNumbers.Number = x.Number&lt;br&gt;WHERE BitmaskNumbers.Byte &amp;lt;=&lt;br&gt;	(SELECT&lt;br&gt;		CASE MAX(Number) % 8&lt;br&gt;			WHEN 0 THEN (MAX(Number) - 1) / 8&lt;br&gt;			ELSE  MAX(Number) / 8&lt;br&gt;		END + 1&lt;br&gt;	FROM @BitsInBitmask)&lt;br&gt;GROUP BY BitmaskNumbers.Byte&lt;br&gt;ORDER BY BitmaskNumbers.Byte DESC&lt;br&gt;&lt;br&gt;SELECT @Bitmask AS TheBitmask&lt;br&gt;&lt;br&gt;&lt;br&gt;TheBitmask&lt;br&gt;------------&lt;br&gt;0x11002200330044&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;... And there you have it. Next time I'll investigate how to use
this technique to very easily implement binary logical operators. And
perhaps I'll disclose more tales about my poor study habits. Or maybe
not.&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=86" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/bitmasks/default.aspx">bitmasks</category></item><item><title>Correction on bitmask handling</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/correction-on-bitmask-handling.aspx</link><pubDate>Thu, 13 Jul 2006 01:26:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:85</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>3</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/85.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=85</wfw:commentRss><description>In the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/dealing-with-very-large-bitmasks.aspx"&gt;article on handling bitmasks&lt;/a&gt; I posted the other day, I made &lt;b&gt;a fatal error in the splitBitmask function&lt;/b&gt;.  The function treated the low byte as the first byte, instead of the high byte.  Therefore:
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;0x01 != 0x0001&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
... and that is not good!
&lt;/p&gt;&lt;p&gt;
So here's a corrected version that fixes the problem:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION dbo.splitBitmask&lt;br&gt;(&lt;br&gt;	@Bitmask VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS TABLE&lt;br&gt;AS&lt;br&gt;RETURN&lt;br&gt;(&lt;br&gt;	SELECT Number&lt;br&gt;	FROM BitmaskNumbers&lt;br&gt;	WHERE (SUBSTRING(@Bitmask, DATALENGTH(@Bitmask) - Byte + 1, 1) &amp;amp; BitValue) = BitValue&lt;br&gt;		AND Byte &amp;lt;= DATALENGTH(@Bitmask)&lt;br&gt;)&lt;br&gt;GO&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
More to come soon... Bit shifting, logical operators, and other fun ways to annoy this guy:
&lt;/p&gt;&lt;p&gt;
&lt;a href="http://www.celko.com/" target="#"&gt;&lt;img src="http://www.celko.com/celko3.gif" border="0"&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=85" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/bitmasks/default.aspx">bitmasks</category></item><item><title>Dealing with very large bitmasks</title><link>http://www2.sqlblog.com/blogs/adam_machanic/archive/2006/07/12/dealing-with-very-large-bitmasks.aspx</link><pubDate>Thu, 13 Jul 2006 01:25:00 GMT</pubDate><guid isPermaLink="false">21093a07-8b3d-42db-8cbf-3350fcbf5496:84</guid><dc:creator>Adam Machanic</dc:creator><slash:comments>10</slash:comments><comments>http://www2.sqlblog.com/blogs/adam_machanic/comments/84.aspx</comments><wfw:commentRss>http://www2.sqlblog.com/blogs/adam_machanic/commentrss.aspx?PostID=84</wfw:commentRss><description>&lt;p&gt;Continuing in my series of &lt;b&gt;things you should probably not do in SQL Server but sometimes have to&lt;/b&gt;, I'm going to do a few posts on dealing with very large bitmasks.
&lt;/p&gt;&lt;p&gt;
Let me first state my utter hatered of bitmasks in databases. I think
they're annoying, make the system difficult to understand, and whether
or not they violate the First Normal Form (that's up for discussion),
using them is just a sign of bad design.
&lt;/p&gt;&lt;p&gt;But as I've said in other posts, I realize that short deadlines
and tiny budgets are an issue at most shops, and sometimes we just need
to shoehorn in a solution real quick (yeah, as if it's not going to
last for the next 5+ years?)
&lt;/p&gt;&lt;p&gt;In one case in the past, bitmasks were a very convenient
solution to a problem I faced with an access control system. But alas,
I only had 8 bytes available to me. Only enough for 64 values. And so
that solution failed. And the company failed. And many, many tears were
shed... If only I'd been able to figure out how to manipulate a bigger
bitmask, I might have saved the little children...
&lt;/p&gt;&lt;p&gt;I won't go into any more detail on that particular issue since
there are still a few pending lawsuits, but suffice it to say that if
that situation were happening today, I probably wouldn't use a bitmask
anyway. But you might need one -- so here's how you do it:
&lt;/p&gt;&lt;p&gt;
First we're going to modify the &lt;a href="http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx"&gt;table of numbers&lt;/a&gt; that I'm always telling you that you absolutely must have in every single database.
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;SELECT (a.number * 256 + b.number) AS Number,&lt;br&gt;	CASE (a.number * 256 + b.number) % 8 &lt;br&gt;		WHEN 0 THEN ((a.number * 256 + b.number) - 1) / 8&lt;br&gt;		ELSE (a.number * 256 + b.number) / 8 &lt;br&gt;		END + 1 AS Byte,&lt;br&gt;	POWER(2, CASE (a.number * 256 + b.number) % 8 &lt;br&gt;		WHEN 0 THEN 8&lt;br&gt;		ELSE (a.number * 256 + b.number) % 8 &lt;br&gt;		END-1) AS BitValue&lt;br&gt;INTO BitmaskNumbers&lt;br&gt;FROM&lt;br&gt;	(&lt;br&gt;		SELECT number&lt;br&gt;		FROM master..spt_values&lt;br&gt;		WHERE &lt;br&gt;			type = 'P'&lt;br&gt;			AND number &amp;lt;= 255&lt;br&gt;	) a (Number),&lt;br&gt;	(&lt;br&gt;		SELECT number&lt;br&gt;		FROM master..spt_values&lt;br&gt;		WHERE &lt;br&gt;			type = 'P'&lt;br&gt;			AND number &amp;lt;= 255&lt;br&gt;	) b (Number)&lt;br&gt;WHERE &lt;br&gt;	(a.number * 256 + b.number) BETWEEN 1 AND 32767&lt;br&gt;GO&lt;br&gt;&lt;br&gt;CREATE CLUSTERED INDEX IX_Byte ON BitmaskNumbers (Byte)&lt;br&gt;GO&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;This will produce a table with 32767 rows. Each row has a Number,
which will represent a bit position in our bitmask, a Byte, which will
help to parse the bitmask, and a BitValue, which is the value that the
individual bits within each byte represent. Feel my 1960-esque skill!
&lt;/p&gt;&lt;p&gt;The brighter bulbs in my audience have now figured out that I'm
going to show you how to handle a 4096-byte bitmask -- capable of
handling up to 32767 values. Not bad. But if you need more, just put
more rows in the BitmaskNumbers table.
&lt;/p&gt;&lt;p&gt;So what do you want to do with bitmasks? Most of the queries
I've seen involve access control And for those queries, you want to use
a &lt;i&gt;logical and&lt;/i&gt;
and see if it evaluates to a number other than 0. That is, we want to
see if both bitmasks we're comparing have any of the same bits set.
&lt;/p&gt;&lt;p&gt;Using what little math knowledge I have managed to retain, I
conjured up the following, which indicates which bit positions, based
on the "number", are filled in a bitmask. For instance:
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;DECLARE@x VARBINARY(4096)&lt;br&gt;SET @Bitmask = 0x1F0000000000000000000000000000000000000000000000000000000000000000000000123000000000000000000000000001&lt;br&gt;&lt;br&gt;SELECT Number&lt;br&gt;FROM BitmaskNumbers&lt;br&gt;WHERE (SUBSTRING(@Bitmask, Byte, 1) &amp;amp; BitValue) = BitValue&lt;br&gt;	AND Byte &amp;lt;= DATALENGTH(@Bitmask)&lt;br&gt;&lt;br&gt;&lt;br&gt;Number&lt;br&gt;----------&lt;br&gt;1&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;5&lt;br&gt;290&lt;br&gt;293&lt;br&gt;301&lt;br&gt;302&lt;br&gt;401&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;
Fun stuff, no?  
&lt;/p&gt;&lt;p&gt;
The &lt;b&gt;Byte &amp;lt;= DATALENGTH(@x)&lt;/b&gt; allows SQL Server to
utilize the clustered index on Byte, so that a full table scan doesn't
have to happen every single time. Small optimization. I couldn't think
of any others. If you can, drop me a line...
&lt;/p&gt;&lt;p&gt;Those of you who've read this far are probably yawning and
wondering where the access control stuff is... Who cares about chunking
up the bitmask into its bit positions? Well, it's simply the first
step. Bear with me.
&lt;/p&gt;&lt;p&gt;What we need to do is wrap this in a UDF. Then if we had two
bitmasks, we could join the bit positions to eliminate those that
aren't in common...
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION dbo.splitBitmask&lt;br&gt;(&lt;br&gt;	@Bitmask VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS TABLE&lt;br&gt;AS&lt;br&gt;RETURN&lt;br&gt;(&lt;br&gt;	SELECT Number&lt;br&gt;	FROM BitmaskNumbers&lt;br&gt;	WHERE (SUBSTRING(@Bitmask, Byte, 1) &amp;amp; BitValue) = BitValue&lt;br&gt;		AND Byte &amp;lt;= DATALENGTH(@Bitmask)&lt;br&gt;)&lt;br&gt;GO&lt;br&gt;&lt;br&gt;DECLARE @Bitmask1 VARBINARY(4096)&lt;br&gt;SET @Bitmask1 = 0x1F0000000000000000000000000000000000000000000000000000000000000000000000123000000000000000000000000001&lt;br&gt;&lt;br&gt;DECLARE @Bitmask2 VARBINARY(4096)&lt;br&gt;SET @Bitmask2 = 0x0E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&lt;br&gt;&lt;br&gt;SELECT x.Number&lt;br&gt;FROM dbo.splitBitmask(@Bitmask1) x&lt;br&gt;JOIN dbo.splitBitmask(@Bitmask2) y ON x.Number = y.Number&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;Number&lt;br&gt;----------&lt;br&gt;2&lt;br&gt;3&lt;br&gt;4&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;We now know that these two bitmasks share bits 2, 3, and 4 in
common. But for most access control situations, we don't care what bits
they share in common -- just that they share some.
&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;pre class="code"&gt;CREATE FUNCTION dbo.HasAccess&lt;br&gt;(&lt;br&gt;	@Bitmask1 VARBINARY(4096),&lt;br&gt;	@Bitmask2 VARBINARY(4096)&lt;br&gt;)&lt;br&gt;RETURNS BIT&lt;br&gt;AS&lt;br&gt;BEGIN&lt;br&gt;	DECLARE @Result BIT&lt;br&gt;&lt;br&gt;	SELECT @Result = &lt;br&gt;		CASE COUNT(*)&lt;br&gt;			WHEN 0 THEN 0&lt;br&gt;			ELSE 1&lt;br&gt;		END&lt;br&gt;	FROM dbo.splitBitmask(@Bitmask1) x&lt;br&gt;	JOIN dbo.splitBitmask(@Bitmask2) y ON x.Number = y.Number&lt;br&gt;&lt;br&gt;	RETURN (@Result)&lt;br&gt;END&lt;br&gt;GO&lt;br&gt;&lt;br&gt;DECLARE @Bitmask1 VARBINARY(4096)&lt;br&gt;SET @Bitmask1 = 0x1F0000000000000000000000000000000000000000000000000000000000000000000000123000000000000000000000000001&lt;br&gt;&lt;br&gt;DECLARE @Bitmask2 VARBINARY(4096)&lt;br&gt;SET @Bitmask2 = 0x0E0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&lt;br&gt;&lt;br&gt;SELECT dbo.HasAccess(@Bitmask1, @Bitmask2) AS HasAccess&lt;br&gt;GO&lt;br&gt;&lt;br&gt;&lt;br&gt;HasAccess&lt;br&gt;-------------&lt;br&gt;1&lt;br&gt;&lt;/pre&gt;
&lt;p&gt;... And that is pretty much it for this installment. We can now
determine whether or not two bitmasks have bits in common, and if
necessary which bits they share.
&lt;/p&gt;&lt;p&gt;Future installments will cover how to manipulate large bitmasks
in other ways -- flip specific bits, perform a logical and that
produces a bitmask instead of a result set, and perform a logical or
that produces a bitmask. All very useful stuff if you need to work with
these bitmasks. But now I just need to figure out how to do all of that
stuff.
&lt;/p&gt;&lt;p&gt;
So until next time.... Don't use this technique.&lt;/p&gt;&lt;br&gt;&lt;img src="http://www2.sqlblog.com/aggbug.aspx?PostID=84" width="1" height="1"&gt;</description><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/T-SQL/default.aspx">T-SQL</category><category domain="http://www2.sqlblog.com/blogs/adam_machanic/archive/tags/bitmasks/default.aspx">bitmasks</category></item></channel></rss>