5

How to convert a number to it string representation for a desired numeric base using SQL, for example convert 45 to the base 2(binary), 8(octantal),16(hexadecimal), ..36.

The requirement is to use the numbers [0-9] and the uppercase characters [A-Z], the total of available characters is 36.

I need to convert for example 45 to base 36, the output must be "19", or use any range base form 2 to 36.

4 Answers 4

7

This is the solution made to convert a number to the string representation to any numeric base. The solution is a function that run on SQL Server, it receives the base and number parameter. The first one parameter is the base number that you want to get and the second one parameter is the number that you want to convert. The algorithm used was taken from the site mathbits.com .

Using the same example from the site of the algorithm, if you want to convert 5 base 10 into base 2.

enter image description here

The process is:

  1. Divide the "desired" base (in this case base 2) INTO the number you are trying to convert.
  2. Write the quotient (the answer) with a remainder like you did in elementary school.
  3. Repeat this division process using the whole number from the previous quotient (the number in front of the remainder).
  4. Continue repeating this division until the number in front of the remainder is only zero.
  5. The answer is the remainders read from the bottom up.

You can see the algorithm and more examples here.

A function in SQL is the best choice to make them useful globally in the SQL Server Instance, the code to do the conversion is the following:

IF OBJECT_ID (N'dbo.NUMBER_TO_STR_BASE', N'FN') IS NOT NULL
    DROP FUNCTION dbo.NUMBER_TO_STR_BASE;
GO
CREATE FUNCTION dbo.NUMBER_TO_STR_BASE (@base int,@number int)
RETURNS varchar(MAX)
WITH EXECUTE AS CALLER
AS
BEGIN
     DECLARE @dividend int = @number
        ,@remainder int = 0 
        ,@numberString varchar(MAX) = CASE WHEN @number = 0 THEN '0' ELSE '' END ;
     SET @base = CASE WHEN @base <= 36 THEN @base ELSE 36 END;--The max base is 36, includes the range of [0-9A-Z]
     WHILE (@dividend > 0 OR @remainder > 0)
         BEGIN
            SET @remainder = @dividend % @base ; --The reminder by the division number in base
            SET @dividend = @dividend / @base ; -- The integer part of the division, becomes the new divident for the next loop
            IF(@dividend > 0 OR @remainder > 0)--check that not correspond the last loop when quotient and reminder is 0
                SET @numberString =  CHAR( (CASE WHEN @remainder <= 9 THEN ASCII('0') ELSE ASCII('A')-10 END) + @remainder ) + @numberString;
     END;
     RETURN(@numberString);
END
GO

After you execute the above code, you can test them calling the function in any query or even in a complex TSL code.

SELECT dbo.NUMBER_TO_STR_BASE(16,45) AS 'hexadecimal';
-- 45 in base 16(hexadecimal) is 2D 
SELECT dbo.NUMBER_TO_STR_BASE(2,45) AS 'binary';
-- 45 in base 2(binary) is 101101
SELECT dbo.NUMBER_TO_STR_BASE(36,45) AS 'tricontahexadecimal';
-- 45 in base (tricontaexadecimal) is 19
SELECT dbo.NUMBER_TO_STR_BASE(37,45) AS 'tricontahexadecimal-test-max-base';
--The output will be 19, because the maximum base is 36,
-- which correspond to the characters [0-9A-Z]

Feel free to comment or suggest improvements, i hope it to be useful

Sign up to request clarification or add additional context in comments.

Comments

3

There doesn't seem to exist a built-in function to handle this. The following is the neatest solution that I have been able to come up with...

create function it_num2Base (@n as BigInt, @b as int)
returns varchar(64)
as
begin

    declare 
    @result varchar(64) ='',
    @chars varchar(36)='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

    if (@n<0) or (@b < 2) or (@b> 36) 
        return null;
 
    while @n>0
    begin
        set @result=substring(@chars,@n % @b + 1,1) + @result;
        set @n = @n / @b;
    end

    return @result;
end

Convert 500,000 to base 36 - expected result APSW

select dbo.it_num2Base(500000,36)

1 Comment

Do you have reverse one ?
1

Hope this helps:

-- 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)

-- Binary to decimal 
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

-- Decimal to binary 
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

Comments

0

Functions can have performance problems, especially multi-statement functions, because of how cardinality is estimated and how the optimizer is limited in re-arranging the function's content. Also writing procedural code (WHILE loops) in a declarative language is sub-optimal. The desired results can be achieved by using a recursive CTE.

declare @Dividend   int = 32;
declare @Divisor    int = 16;

with Division as
(
    select
        Quotient    = @Dividend / @Divisor,
        Remainder   = @Dividend % @Divisor,
        Level       = 0

    union all

    select
        Quotient    = d.Quotient / @Divisor,
        Remainder   = d.Quotient % @Divisor,
        Level       = d.Level + 1
    from Division as d
    where d.Quotient > 0
),
OuputGlyphs as
(
    select *
    from 
    (
        values
            (0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4'),
            (5, '5'), (6, '6'), (7, '7'), (8, '8'), (9, '9'),
            (10, 'A'), (11, 'B'), (12, 'C'), (13, 'D'),
            (14, 'E'), (15, 'F')    -- extend this list as required
    ) as T(Given, Returned)
)
select
    CAST(@Dividend as varchar(99)) + ' in base ' + CAST(@Divisor as varchar(99)) + ' = ' +
    STRING_AGG(gg.Returned, ',')  within group ( order by Level DESC ) 
from Division as dd
inner join OuputGlyphs as gg
    on gg.Given = dd.Remainder;

This can be packaged as a single-statement table valued function or as a stored procedure. Either way the cardinality estimates will be accurate. The declared variables will become input parameters.

The recursive CTE (called "Division") performs long division on @Dividend, just like we learnt in school. By default the CTE is limited to 100 recursions i.e. a 100-digit ouput can be produced. If this is not long enough the limit can be changed - see MAXRECURSION.

OutputGlyphs translates the decimal remainder from each recursion to whatever symbol you want to see. I stopped at 16 for brevity. This list can be extended ad nauseam for whatever base you choose to use. Indeed, non-ASCII characters or emojis are possible with a little tweaking. I used an in-line CTE for convenience but a regular table, view or table-valued function would do just as well.

It is important to sort the output by Level to ensure the correct glyph appears in the correct position.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.