PostgreSQL convert integer to bit

4 minute read

背景

昨天一位同事问我PostgreSQL 有没有十进制转二进制的函数.

看了一下目前PostgreSQL没有此类函数, 所以需要自己写一下.

正文

有2种方法,

第一种方法如下, 涉及符号与类型宽度(假设int为signed int).

do language plpgsql $$  
declare  
  o_bit text;  
  o_len int;  
  i_num int;  
  i_conv int;  
  i_num_abs int;  
  i_res int;  
  i_mod int;  
begin  
  o_len := 32;  
  i_num := -10234;  
  i_conv := 2;  
  i_num_abs := abs(i_num);  
  i_res := i_num_abs/i_conv;  
  i_mod := mod(i_num_abs,i_conv);  
  o_bit := i_mod::text;  
  loop  
    if i_res = 0 then  
      exit;  
    end if;  
    i_mod := mod(i_res,i_conv);  
    i_res := i_res/i_conv;  
    o_bit := i_mod||o_bit;  
  end loop;  
  o_len := o_len - char_length(o_bit) - 1;  
  o_bit := repeat('0', o_len)||o_bit;  
  if i_num >=0 then  
    o_bit := '0'||o_bit;  
  else  
    o_bit := '1'||o_bit;  
  end if;  
  raise notice '%', o_bit;  
end;  
$$;  

创建函数如下 :

create or replace function i4tob(i_num int) returns varbit as $$  
declare  
  o_bit text;  
  o_len int;  
  i_conv int;  
  i_num_abs int;  
  i_res int;  
  i_mod int;  
begin  
  o_len := 32;  
  i_conv := 2;  
  i_num_abs := abs(i_num);  
  i_res := i_num_abs/i_conv;  
  i_mod := mod(i_num_abs,i_conv);  
  o_bit := i_mod::text;  
  loop  
    if i_res = 0 then  
      exit;  
    end if;  
    i_mod := mod(i_res,i_conv);  
    i_res := i_res/i_conv;  
    o_bit := i_mod||o_bit;  
  end loop;  
  o_len := o_len - char_length(o_bit) - 1;  
  if i_num >=0 then  
    null;  
  else  
    o_bit := repeat('0', o_len)||o_bit;  
    o_bit := '1'||o_bit;  
  end if;  
  raise notice '%', o_bit;  
  return o_bit::varbit;  
end;  
$$ language plpgsql;  

测试 :

postgres=# select i4tob(-100);  
NOTICE:  10000000000000000000000001100100  
              i4tob                 
----------------------------------  
 10000000000000000000000001100100  
(1 row)  
  
postgres=# select i4tob(100);  
NOTICE:  1100100  
  i4tob    
---------  
 1100100  
(1 row)  
  
postgres=# select i4tob(256);  
NOTICE:  100000000  
   i4tob     
-----------  
 100000000  
(1 row)  
  
postgres=# select i4tob(-256);  
NOTICE:  10000000000000000000000100000000  
              i4tob                 
----------------------------------  
 10000000000000000000000100000000  
(1 row)  

另一种方法是使用位移算法 :

do language plpgsql $$  
declare  
  o_bit text;  
  o_len int;  
  i_num int;  
  i_conv int;  
  i_num_abs int;  
  i_pos int;  
begin  
  o_len := 32;  
  i_num := -1024;  
  i_conv := 2;  
  i_num_abs := abs(i_num);  
  i_pos := trunc((dlog1(i_num_abs))/0.693147180559945);  
  o_bit := mod(i_num_abs,i_conv)::text;  
  if i_pos >= 1 then  
    for i in 1..i_pos loop  
      o_bit := mod(i_num_abs>>i, i_conv)||o_bit;  
    end loop;  
  end if;  
  if i_num >=0 then  
    null;  
  else  
    o_len := o_len - char_length(o_bit) - 1;  
    o_bit := repeat('0', o_len)||o_bit;  
    o_bit := '1'||o_bit;  
  end if;  
  raise notice '%', o_bit;  
end;  
$$;  

改成函数 :

create or replace function si32tob(i_num int) returns varbit as $$  
declare  
  o_bit text;  
  o_len int;  
  i_conv int;  
  i_num_abs int;  
  i_pos int;  
begin  
  o_len := 32;  
  i_conv := 2;  
  i_num_abs := abs(i_num);  
  i_pos := trunc((dlog1(i_num_abs))/0.693147180559945);  
  o_bit := mod(i_num_abs,i_conv)::text;  
  if i_pos >= 1 then  
    for i in 1..i_pos loop  
      o_bit := mod(i_num_abs>>i, i_conv)||o_bit;  
    end loop;  
  end if;  
  if i_num >=0 then  
    null;  
  else  
    o_len := o_len - char_length(o_bit) - 1;  
    o_bit := repeat('0', o_len)||o_bit;  
    o_bit := '1'||o_bit;  
  end if;  
  raise notice '%', o_bit;  
  return o_bit::varbit;  
end;  
$$ language plpgsql;  

测试 :

digoal=# select si32tob(1);  
NOTICE:  1  
 si32tob   
---------  
 1  
(1 row)  
Time: 1.715 ms  
digoal=# select si32tob(2);  
NOTICE:  10  
 si32tob   
---------  
 10  
(1 row)  
Time: 1.049 ms  
digoal=# select si32tob(-2);  
NOTICE:  10000000000000000000000000000010  
             si32tob                
----------------------------------  
 10000000000000000000000000000010  
(1 row)  
Time: 1.520 ms  
digoal=# select si32tob(234);  
NOTICE:  11101010  
 si32tob    
----------  
 11101010  
(1 row)  
Time: 1.094 ms  
digoal=# select si32tob(-234);  
NOTICE:  10000000000000000000000011101010  
             si32tob                
----------------------------------  
 10000000000000000000000011101010  
(1 row)  
Time: 1.124 ms  

以上函数参考wikihow提供的算法编写.

其他转换可参考本文末尾的函数进行修改.

实际上现在的版本不需要这么复杂

已经内置了转换方法,直接转换即可:

postgres=# select 123::bit(64);  
                               bit                                  
------------------------------------------------------------------  
 0000000000000000000000000000000000000000000000000000000001111011  
(1 row)  
  
postgres=# select 12322222222::bit(64);  
                               bit                                  
------------------------------------------------------------------  
 0000000000000000000000000000001011011110011101100011000010001110  
(1 row)  
  
postgres=# select 1::bit(64);  
                               bit                                  
------------------------------------------------------------------  
 0000000000000000000000000000000000000000000000000000000000000001  
(1 row)  
  
postgres=# select (-1)::bit(64);  
                               bit                                  
------------------------------------------------------------------  
 1111111111111111111111111111111111111111111111111111111111111111  
(1 row)  
  
postgres=# select (0)::bit(64);  
                               bit                                  
------------------------------------------------------------------  
 0000000000000000000000000000000000000000000000000000000000000000  
(1 row)  
  

参考

1. http://www.wikihow.com/Convert-from-Decimal-to-Binary

2. http://psoug.org/snippet/Convert-between-Decimal-Binary-Octal-and-Hex_536.htm

3. https://stackoverflow.com/questions/8316164/convert-hex-in-text-representation-to-decimal-number/8335376#8335376

4. https://stackoverflow.com/questions/25020950/postgresql-convert-bit-varying-to-integer

5. src/backend/optimizer/path/costsize.c

00096 #define LOG2(x)  (log(x) / 0.693147180559945)  
  1. oracle pl/sql 相关转换函数 :
SET serveroutput ON  
   
CREATE OR REPLACE PACKAGE dbms_numsystem AS  
   FUNCTION bin2dec (binval IN CHAR  ) RETURN NUMBER;  
   FUNCTION dec2bin (N      IN NUMBER) RETURN VARCHAR2;   
   FUNCTION oct2dec (octval IN CHAR  ) RETURN NUMBER;  
   FUNCTION dec2oct (N      IN NUMBER) RETURN VARCHAR2;   
   FUNCTION hex2dec (hexval IN CHAR  ) RETURN NUMBER;  
   FUNCTION dec2hex (N      IN NUMBER) RETURN VARCHAR2;   
END dbms_numsystem;  
/  
show errors  
   
CREATE OR REPLACE PACKAGE BODY dbms_numsystem AS  
   
FUNCTION bin2dec (binval IN CHAR) RETURN NUMBER IS  
  i                 NUMBER;  
  digits            NUMBER;  
  result            NUMBER := 0;  
  current_digit     CHAR(1);  
  current_digit_dec NUMBER;  
BEGIN  
  digits := LENGTH(binval);  
  FOR i IN 1..digits LOOP  
     current_digit := SUBSTR(binval, i, 1);  
     current_digit_dec := TO_NUMBER(current_digit);  
     result := (result * 2) + current_digit_dec;  
  END LOOP;  
  RETURN result;  
END bin2dec;  
   
FUNCTION dec2bin (N IN NUMBER) RETURN VARCHAR2 IS  
  binval VARCHAR2(64);  
  N2     NUMBER := N;  
BEGIN  
  WHILE ( N2 > 0 ) LOOP  
     binval := MOD(N2, 2) || binval;  
     N2 := TRUNC( N2 / 2 );  
  END LOOP;  
  RETURN binval;  
END dec2bin;  
   
FUNCTION oct2dec (octval IN CHAR) RETURN NUMBER IS  
  i                 NUMBER;  
  digits            NUMBER;  
  result            NUMBER := 0;  
  current_digit     CHAR(1);  
  current_digit_dec NUMBER;  
BEGIN  
  digits := LENGTH(octval);  
  FOR i IN 1..digits LOOP  
     current_digit := SUBSTR(octval, i, 1);  
     current_digit_dec := TO_NUMBER(current_digit);  
     result := (result * 8) + current_digit_dec;  
  END LOOP;  
  RETURN result;  
END oct2dec;  
   
FUNCTION dec2oct (N IN NUMBER) RETURN VARCHAR2 IS  
  octval VARCHAR2(64);  
  N2     NUMBER := N;  
BEGIN  
  WHILE ( N2 > 0 ) LOOP  
     octval := MOD(N2, 8) || octval;  
     N2 := TRUNC( N2 / 8 );  
  END LOOP;  
  RETURN octval;  
END dec2oct;  
   
FUNCTION hex2dec (hexval IN CHAR) RETURN NUMBER IS  
  i                 NUMBER;  
  digits            NUMBER;  
  result            NUMBER := 0;  
  current_digit     CHAR(1);  
  current_digit_dec NUMBER;  
BEGIN  
  digits := LENGTH(hexval);  
  FOR i IN 1..digits LOOP  
     current_digit := SUBSTR(hexval, i, 1);  
     IF current_digit IN ('A','B','C','D','E','F') THEN  
        current_digit_dec := ASCII(current_digit) - ASCII('A') + 10;  
     ELSE  
        current_digit_dec := TO_NUMBER(current_digit);  
     END IF;  
     result := (result * 16) + current_digit_dec;  
  END LOOP;  
  RETURN result;  
END hex2dec;  
   
FUNCTION dec2hex (N IN NUMBER) RETURN VARCHAR2 IS  
  hexval VARCHAR2(64);  
  N2     NUMBER := N;  
  digit  NUMBER;  
  hexdigit  CHAR;  
BEGIN  
  WHILE ( N2 > 0 ) LOOP  
     digit := MOD(N2, 16);  
     IF digit > 9 THEN   
        hexdigit := CHR(ASCII('A') + digit - 10);  
     ELSE  
        hexdigit := TO_CHAR(digit);  
     END IF;  
     hexval := hexdigit || hexval;  
     N2 := TRUNC( N2 / 16 );  
  END LOOP;  
  RETURN hexval;  
END dec2hex;  
   
END dbms_numsystem;  
/  
show errors  

Flag Counter

digoal’s 大量PostgreSQL文章入口