improve.dk
Just another mindless drone looking for the perfect stack
posts - 227, comments - 489

Converting between base 2, 10 and 16 in T-SQL

Written on July 11, 2011 by Mark S. Rasmussen in SQL Server: Tricks

There are many numeral systems, the most common ones in computer science being binary (base 2), decimal (base 10) and hexadecimal (base 16). All numbers can be expressed in either system and you may now and then need to convert between them.

Take the number 493.202.384 as an example, it can be be expressed as either 0n493202384 in decimal, 0x1D65ABD0 in hexadecimal or 0b11101011001011010101111010000 in binary. Note how the 0n prefix declares a decimal value, 0x a hexadecimal and 0b a binary value.

Converting using Google

If you’ve got an internet connection, the quickest and simplest way is often to just use Google. We can convert the above number using “in X” queries:

493202384 in hex
493202384 in binary

Converting using Windows Calculator

You can also open Windows Calculator, switch to the programmer mode and type in the decimal value (or the hex/binary value):

image

And from then on we can just switch the numerical system selector to the left:

image image

Converting between decimal & hex in T-SQL

Sometimes however, it’s just a tad easier if we could do it directly from a T-SQL query. Converting between decimal and hexadecimal is straightforward and can be done using just built in functions:

-- Decimal to hex
SELECT CAST(493202384 AS varbinary)

-- Hex to decimal
SELECT CAST(0x1D65ABD0 AS int)

-- Decimal to hex to decimal
SELECT CAST(CAST(493202384 AS varbinary) AS int)

image

Converting binary to decimal using T-SQL

Converting to/from binary is a bit more tricky though, as there are no built in functions for formatting a decimal number as a binary string, nor converting the latter to the first.

The following function takes in a binary string and returns a bigint with the decimal value:

CREATE FUNCTION [dbo].[BinaryToDecimal]
(
	@Input varchar(255)
)
RETURNS bigint
AS
BEGIN

	DECLARE @Cnt tinyint = 1
	DECLARE @Len tinyint = LEN(@Input)
	DECLARE @Output bigint = CAST(SUBSTRING(@Input, @Len, 1) AS bigint)

	WHILE(@Cnt < @Len) BEGIN
		SET @Output = @Output + POWER(CAST(SUBSTRING(@Input, @Len - @Cnt, 1) * 2 AS bigint), @Cnt)
		
		SET @Cnt = @Cnt + 1
	END

	RETURN @Output	

END

The function looks at each char in the input string (starting from behind), adding POWER(2, @Cnt) to the result if the bit is set – with special handling of the first (that is, from behind) character since POWER(2, 0) is 1 while we need it to be 0.

Usage is straight forward:

SELECT dbo.BinaryToDecimal('11101011001011010101111010000')

image

Converting decimal to binary using T-SQL

The following function takes a bigint as input and returns a varchar with the binary representation, using the short division by two with remainder algorithm:

CREATE FUNCTION [dbo].[DecimalToBinary]
(
	@Input bigint
)
RETURNS varchar(255)
AS
BEGIN
	
	DECLARE @Output varchar(255) = ''

	WHILE @Input > 0 BEGIN
		
		SET @Output = @Output + CAST((@Input % 2) AS varchar)
		SET @Input = @Input / 2
		
	END

	RETURN REVERSE(@Output)
	
END

Again usage is straight forward:

SELECT dbo.DecimalToBinary(493202384)

image

Ensuring correctness

A simple test to ensure correct conversions would be to convert from A to B and back to A again, using both of the above functions. Thus whatever we give as input should be the output as well:

SELECT dbo.DecimalToBinary(dbo.BinaryToDecimal('11101011001011010101111010000'))
SELECT dbo.BinaryToDecimal(dbo.DecimalToBinary(493202384))

image

Et voilá! Once we have the functions, they can easily be used in a normal query:

SELECT
	object_id,
	CAST(object_id AS varbinary) AS object_id_hex,
	dbo.DecimalToBinary(object_id) AS object_id_binary
FROM
	sys.objects

image

Feedback

Gravatar

robert matthew cook wrote on 7/13/2011 10:59 PM

mark thank you for the functions.

while working with sql 2000 i had to do some binary conversion. the encapsulation into a function is a nice touch.

played with cte for fun to get something working without the explicit while loop but performance may be worse...

;with table_64_bit as
(
select CONVERT(varchar(64), '0') as bit_check, CONVERT(bigint, 0) as decimal_value
union all
select CONVERT(varchar(64), '1%'), CONVERT(bigint, 1)
union all
select CONVERT(varchar(64), REPLACE(SPACE(LEN(bit_check)-1), ' ', '_') + '1%'), CONVERT(bigint, POWER(CONVERT(bigint, 2), LEN(bit_check)-1)) from table_64_bit where LEN(bit_check) between 2 and 63
)

select
from_binary, sum(decimal_value) as to_decimal
from
table_64_bit
join
(select '11101011001011010101111010000' as from_binary) as fake_table on reverse(from_binary) like bit_check
group by
from_binary;
Gravatar

mdlueck wrote on 2/16/2012 6:16 PM

In a SQL Server 2008 R2 context...

Is it possible to SQL SELECT WHERE value is "Binary AND a particular Binary number"?

I am storing various bit mask data as a Base 10 number in a table column.

With said value, I run a VBA Binary AND upon the stored number to determine if a particular bit is 1/0, thus I know the state of that attribute value.

Is there some way to have SQL Server select based on that same scenario... only select records where the stored value is true of "AND 8" as example? I would need returned all records where the 8 bit is true of the Base 10 number.
Gravatar

Mark S. Rasmussen wrote on 2/17/2012 12:11 AM

mdlueck,

Sure, you can use bitmasking directly in your where query. If you want to select all records with the second bit set, you can do:

SELECT * FROM sys.databases WHERE database_id & 2 = 2

And if you need the second and fifth bits set, you just need to convert the binary into decimal like so:
10010 = 18

And then query like this:

SELECT * FROM sys.databases WHERE database_id & 18 = 18

Just note that this'll render any indexes useless so it'll always result in a scan of the binary column, and whatever other indexes are needed to retrieve the desired columns.
Gravatar

mdlueck wrote on 2/17/2012 3:48 PM

"& 2 = 2" hhhmmm... Looks a bit odd. It will take my mind some time to get around that syntax. (The need to double provide the decimal representation of the binary mask.) Thanks for the quick reply.

Ack that such will force a table scan. I would have expected that. Neat to hear that such is possible. Thanks! :-)
Gravatar

Mark S. Rasmussen wrote on 2/17/2012 4:12 PM

Optimally you'd store the bitmask contents as normal bit fields - it'd take up the same, and possibly even less, space. That'd mean you could index the bit columns and avoid the scans. If you've got legacy though, this should allow you to work with it :)
Gravatar

mdlueck wrote on 2/17/2012 6:49 PM

Re: "as normal bit fields"

Rather than unnecessarily widen a table, and have to add cols when ever I need to keep track of "one more permission" I simply pack all of the perm mask into a single Base 10 integer number. I have plenty of extra space for the foreseeable future... "not that many additional perms that would need to be tracked".

The unique twist came that "find me all records with XYZ perm set"... which means that the database would need to be able to do the same Binary And function that is able to be done via standard programming languages.

So, I opt towards leaving the DB schema alone longer (until the first perm mask col runes out of space).
Gravatar

Mark S. Rasmussen wrote on 2/17/2012 6:56 PM

The thing is, assuming you're storing your value in an integer - that takes up a fixed 4 bytes, no matter if you're storing 0 or 32 bit flags.

A bit column will take up 1, rounded up to nearest multiple of 8. One bit column = 1 byte. Seven bit columns = 1 byte. Nine bit columns = 2 bytes, etc.

If you use a tinyint to avoid the overhead of an int, make sure you plan for it when you have to convert that to a smallint so support 9+ flags.

What happens when flag 2 is suddenly deprecated - will you reuse that bit in your bitmask? If you want to reuse it, you'll have to update all records to clear that bit. Make sure to plan for it.

By using bit columns, you can add now bit values for free as it's a metadata operation. You can clear old ones for free as it's a metadata operation. You can reference the flags by name instead of bit index - and avoid the whole scan issue.
Gravatar

mdlueck wrote on 2/17/2012 9:22 PM

Agreed. I am erring on the side of packing all of the perms into one column rather than needing to reorg the table each time a new perm is thought of.

And I am using int, currently using 6 slots. This is a userid permission table, for an application which at the most will have 100 people using. I think this company can handle 400 bytes in their SQL Server! ;-)

Post Comment

Name  
Email
Url
Comment
Please add 1 and 6 and type the answer here: