将文本表示中的十六进制转换为十进制数

时间:2011-11-29 18:55:31

标签: postgresql types casting hex

我正在尝试使用PostgreSQL 9.1

将十六进制转换为十进制

使用此查询:

SELECT to_number('DEADBEEF', 'FMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');

我收到以下错误:

ERROR:  invalid input syntax for type numeric: " "

我做错了什么?

5 个答案:

答案 0 :(得分:66)

有些方法没有动态SQL

最大。 8个十六进制数字

text表示中没有从十六进制数转换为数字类型,但我们可以使用bit(n)作为航点。位串中的 4位编码1个十六进制数字。从位字符串到bit(32)(最多8个十六进制数字)到integer(标准的4字节整数)有一个未记录的强制转换 - 内部表示是二进制兼容的。

SELECT ('x' || lpad(hex, 8, '0'))::bit(32)::int AS int_val
FROM   (
   VALUES ('1'::text)
         ,('f')
         ,('100')
         ,('7fffffff')
         ,('80000000')
         ,('deadbeef')
         ,('ffffffff')
   ) AS t(hex);

结果:

   int_val
------------
          1
         15
        256
 2147483647
-2147483648
 -559038737
         -1

4个字节足以编码所有十六进制数字,最多8位数,但Postgres中的integer是有符号类型,因此十六进制数高于'7fffffff' 溢出到负数数字。这仍然是一种独特的表示,但含义是不同的。如果重要,请转到bigint,请参阅下文。

对于未知不同长度的十六进制数字,我们需要填充前导0,如演示要强制转换为bit(32)。对于已知长度的数字,我们可以调整长度说明符。示例包含7个十六进制数字和int或8位数字bigint

SELECT ('x'|| 'deafbee')::bit(28)::int
     , ('x'|| 'deadbeef')::bit(32)::bigint;

  int4     | int8
-----------+------------
 233503726 | 3735928559

最大。 16位十六进制数字

使用bigintint8,8字节整数)最多16个十六进制数字 - 溢出到上半部分的负数:

SELECT ('x' || lpad(hex, 16, '0'))::bit(64)::bigint AS int8_val
FROM   (
   VALUES ('ff'::text)
        , ('7fffffff')
        , ('80000000')
        , ('deadbeef')
        , ('7fffffffffffffff')
        , ('8000000000000000')
        , ('ffffffffffffffff')
        , ('ffffffffffffffff123') -- too long
   ) t(hex);

结果:

       int8_val
---------------------
                 255
          2147483647
          2147483648
          3735928559
 9223372036854775807
-9223372036854775808
                  -1
                  -1

对于超过16个十六进制数字,最不重要的字符(超出右侧)得到截断

此演员依赖无证件行为,我引用Tom Lane here

  

这依赖于位类型输入的一些未记录的行为   转换器,但我认为没有理由期望会破坏。可能   更大的问题是它需要PG> = 8.3,因为没有文本   在那之前投点。

最大UUID 32位十六进制数字

Postgres uuid数据类型不是数字类型,因此这与提出的问题不同。但它是标准Postgres中最有效的类型,可存储多达32个十六进制数字,仅占用16个字节的存储空间。有一个直接投射,但正好 32个十六进制数字是必需的。

SELECT lpad(hex, 32, '0')::uuid AS uuid_val
FROM  (
   VALUES ('ff'::text)
        , ('deadbeef')
        , ('ffffffffffffffff')
        , ('ffffffffffffffffffffffffffffffff')
        , ('ffffffffffffffffffffffffffffffff123') -- too long
   ) t(hex);

结果:

              uuid_val
--------------------------------------
 00000000-0000-0000-0000-0000000000ff
 00000000-0000-0000-0000-0000deadbeef
 00000000-0000-0000-ffff-ffffffffffff
 ffffffff-ffff-ffff-ffff-ffffffffffff
 ffffffff-ffff-ffff-ffff-ffffffffffff

如您所见,标准输出是一个十六进制数字字符串,其中包含UUID的典型分隔符。

md5哈希

这对于存储 md5哈希

特别有用
SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash

结果:

           md5_hash
--------------------------------------
 02e10e94-e895-616e-8e23-bb7f8025da42

答案 1 :(得分:19)

你有两个直接的问题:

  1. to_number无法理解十六进制。
  2. Xto_number格式字符串中没有任何意义,任何没有含义的内容显然都意味着“跳过一个字符”。
  3. 我没有(2)的权威理由,只是经验证据:

    => SELECT to_number('123', 'X999');
     to_number 
    -----------
            23
    (1 row)
    
    => SELECT to_number('123', 'XX999');
     to_number 
    -----------
             3
    

    文档提到双引号模式应该如何表现:

      

    to_dateto_numberto_timestamp中,双引号字符串会跳过字符串中包含的输入字符数,例如"XX"跳过两个输入字符。

    但是非格式化字符的非引用字符的行为似乎未指定。

    在任何情况下,to_number都不是将十六进制转换为数字的正确工具,您想要这样说:

    select x'deadbeef'::int;
    

    所以也许this function会更适合你:

    CREATE OR REPLACE FUNCTION hex_to_int(hexval varchar) RETURNS integer AS $$
    DECLARE
        result  int;
    BEGIN
        EXECUTE 'SELECT x' || quote_literal(hexval) || '::int' INTO result;
        RETURN result;
    END;
    $$ LANGUAGE plpgsql IMMUTABLE STRICT;
    

    然后:

    => select hex_to_int('DEADBEEF');
     hex_to_int 
    ------------
     -559038737 **
    (1 row)
    

    **为了避免整数溢出错误这样的负数,请使用bigint而不是int来容纳更大的十六进制数(如IP地址)。

答案 2 :(得分:3)

如果有其他人坚持PG8.2,这是另一种方法。

bigint版本:

create or replace function hex_to_bigint(hexval text) returns bigint as $$
select
  (get_byte(x,0)::int8<<(7*8)) |
  (get_byte(x,1)::int8<<(6*8)) |
  (get_byte(x,2)::int8<<(5*8)) |
  (get_byte(x,3)::int8<<(4*8)) |
  (get_byte(x,4)::int8<<(3*8)) |
  (get_byte(x,5)::int8<<(2*8)) |
  (get_byte(x,6)::int8<<(1*8)) |
  (get_byte(x,7)::int8)
from (
  select decode(lpad($1, 16, '0'), 'hex') as x
) as a;
$$
language sql strict immutable;

int version:

create or replace function hex_to_int(hexval text) returns int as $$
select
  (get_byte(x,0)::int<<(3*8)) |
  (get_byte(x,1)::int<<(2*8)) |
  (get_byte(x,2)::int<<(1*8)) |
  (get_byte(x,3)::int)
from (
  select decode(lpad($1, 8, '0'), 'hex') as x
) as a;
$$
language sql strict immutable;

答案 3 :(得分:3)

pg-bignum

在内部,pg-bignum将SSL库用于大数字。这种方法没有在数字的其他答案中提到的缺点。它也不会被plpgsql放慢速度。速度很快,适用于任何规模。测试案例取自Erwin的答案进行比较,

CREATE EXTENSION bignum;

SELECT hex, bn_in_hex(hex::cstring) 
FROM   (
   VALUES ('ff'::text)
        , ('7fffffff')
        , ('80000000')
        , ('deadbeef')
        , ('7fffffffffffffff')
        , ('8000000000000000')
        , ('ffffffffffffffff')
        , ('ffffffffffffffff123')
   ) t(hex);

         hex         |        bn_in_hex        
---------------------+-------------------------
 ff                  | 255
 7fffffff            | 2147483647
 80000000            | 2147483648
 deadbeef            | 3735928559
 7fffffffffffffff    | 9223372036854775807
 8000000000000000    | 9223372036854775808
 ffffffffffffffff    | 18446744073709551615
 ffffffffffffffff123 | 75557863725914323415331
(8 rows)

您可以使用bn_in_hex('deadbeef')::text::numeric将数据类型化为数字。

答案 4 :(得分:1)

这里是使用numeric的版本,因此它可以处理任意大的十六进制字符串:

create function hex_to_decimal(hex_string text)
returns text
language plpgsql immutable as $pgsql$
declare
    bits bit varying;
    result numeric := 0;
    exponent numeric := 0;
    chunk_size integer := 31;
    start integer;
begin
    execute 'SELECT x' || quote_literal(hex_string) INTO bits;
    while length(bits) > 0 loop
        start := greatest(1, length(bits) - chunk_size);
        result := result + (substring(bits from start for chunk_size)::bigint)::numeric * pow(2::numeric, exponent);
        exponent := exponent + chunk_size;
        bits := substring(bits from 1 for greatest(0, length(bits) - chunk_size));
    end loop;
    return trunc(result, 0);
end
$pgsql$;

例如:

=# select hex_to_decimal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
32592575621351777380295131014550050576823494298654980010178247189670100796213387298934358015