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

Har*_*wig 33 postgresql hex types casting

我试图使用PostgreSQL 9.1将十六进制转换为十进制

使用此查询:

SELECT to_number('DEADBEEF', 'FMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX');
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

ERROR:  invalid input syntax for type numeric: " "
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

Erw*_*ter 68

没有动态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);
Run Code Online (Sandbox Code Playgroud)

结果:

   int_val
------------
          1
         15
        256
 2147483647
-2147483648
 -559038737
         -1
Run Code Online (Sandbox Code Playgroud)

4个字节足以编码 最多8位数的所有十六进制数,但integer在Postgres中是有符号类型,因此上面的'7fffffff'十六进制数溢出为负数int.这仍然是一种独特的表现形式,但意义不同.如果重要的话bigint,请参见下文.

对于未知变化长度的十六进制数,我们需要填充前导零,0如演示所示bit(32).对于已知长度的数字,我们可以调整长度说明符.7个十六进制数字和/ int或8位数的示例bigint:

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

  int4     | int8
-----------+------------
 233503726 | 3735928559
Run Code Online (Sandbox Code Playgroud)

最大.16位十六进制数字

使用bigint(int8,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);
Run Code Online (Sandbox Code Playgroud)

结果:

       int8_val
---------------------
                 255
          2147483647
          2147483648
          3735928559
 9223372036854775807
-9223372036854775808
                  -1
                  -1
Run Code Online (Sandbox Code Playgroud)

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

这个演员依赖于无证件的行为,我在这里引用Tom Lane:

这依赖于位型输入转换器的一些未记录的行为,但我认为没有理由期望会破坏.一个可能更大的问题是它需要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);
Run Code Online (Sandbox Code Playgroud)

结果:

              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
Run Code Online (Sandbox Code Playgroud)

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

md5哈希

这对于存储md5哈希特别有用:

SELECT md5('Store hash for long string, maybe for index?')::uuid AS md5_hash
Run Code Online (Sandbox Code Playgroud)

结果:

           md5_hash
--------------------------------------
 02e10e94-e895-616e-8e23-bb7f8025da42
Run Code Online (Sandbox Code Playgroud)

  • 将md5哈希存储为uuid是一个巧妙的技巧! (2认同)

mu *_*ort 20

你有两个直接的问题:

  1. to_number 不懂十六进制.
  2. Xto_number格式字符串中没有任何意义,任何没有意义的东西显然意味着"跳过一个字符".

我没有(2)的权威理由,只是经验证据:

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

=> SELECT to_number('123', 'XX999');
 to_number 
-----------
         3
Run Code Online (Sandbox Code Playgroud)

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

In to_date,to_numberto_timestamp,双引号字符串跳过字符串中包含的输入字符数,例如"XX"跳过两个输入字符.

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

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

select x'deadbeef'::int;
Run Code Online (Sandbox Code Playgroud)

所以也许这个功能对你来说会更好:

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;
Run Code Online (Sandbox Code Playgroud)

然后:

=> select hex_to_int('DEADBEEF');
 hex_to_int 
------------
 -559038737 **
(1 row)
Run Code Online (Sandbox Code Playgroud)

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

  • 哈哈,奇怪的是,“ to_number”支持最简单的东西,例如罗马数字,序数后缀等等-这是最后一次有人需要它的时候。:)但是没有十六进制吗? (3认同)

Dav*_*ver 8

这是一个使用 的版本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$;
Run Code Online (Sandbox Code Playgroud)

例如:

=# select hex_to_decimal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
32592575621351777380295131014550050576823494298654980010178247189670100796213387298934358015
Run Code Online (Sandbox Code Playgroud)


Pie*_*e D 5

如果其他人被 PG8.2 困住了,这里有另一种方法。

大整数版本:

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;
Run Code Online (Sandbox Code Playgroud)

国际版本:

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;
Run Code Online (Sandbox Code Playgroud)


Eva*_*oll 5

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)
Run Code Online (Sandbox Code Playgroud)

您可以使用bn_in_hex('deadbeef')::text::numeric.