Преобразование hex в текстовое представление в десятичное число

Я пытаюсь преобразовать hex в десятичный, используя PostgreSQL 9.1

с этим запросом:

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

Я получаю следующую ошибку:

ERROR:  invalid input syntax for type numeric: " "

Что я делаю неправильно?

Ответ 1

У вас есть две проблемы:

  • to_number не понимает шестнадцатеричный.
  • X не имеет никакого значения в строке формата to_number, и что-либо без значения, по-видимому, означает "пропустить символ".

У меня нет авторитетного обоснования для (2), просто эмпирического доказательства:

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

=> SELECT to_number('123', 'XX999');
 to_number 
-----------
         3

В документации упоминается, как должны вести себя двойные кавычки:

В to_date, to_number и to_timestamp строки с двойными кавычками пропускают количество входных символов, содержащихся в строке, например. "XX" пропускает два входных символа.

но поведение некабельных символов, которые не являются форматирующими символами, представляется неуказанным.

В любом случае to_number не является правильным инструментом для преобразования hex в числа, вы хотите сказать что-то вроде этого:

select x'deadbeef'::int;

возможно, эта функция будет работать лучше для вас:

CREATE OR REPLACE FUNCTION hex_to_int(hexval varchar) RETURNS integer AS $$
DECLARE
    result  int;
BEGIN
    EXECUTE 'SELECT x''' || 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

Существуют способы без динамического 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 цифр, но 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

Макс. 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);

Результат:

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

Для более чем 16 шестнадцатеричных цифр наименее значимые символы (избыток справа) обрезаются.

Этот факт основан на недокументированном поведении, я цитирую Tom Lane здесь:

Это зависит от некоторых недокументированных действий ввода бит-типа конвертер, но я не вижу причин ожидать, что это сломается. Возможно Большая проблема заключается в том, что он требует PG >= 8.3, поскольку не было текста перед выполнением бит.

UUID для макс. 32 шестнадцатеричных цифры

Тип данных Postgres uuid не является числовым типом, поэтому это отклоняется от заданного вопроса. Но это самый эффективный тип стандартных Postgres для хранения до 32 шестнадцатеричных цифр, занимающих только 16 байт памяти. Существует direct cast, но требуется ровно 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 hash

Это особенно полезно для хранения хешей md5:

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

Результат:

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

Ответ 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;

Ответ 4

pg-bignum

Внутри pg-bignum используется библиотека SSL для больших чисел. Этот метод не имеет ни одного из недостатков, упомянутых в других ответах с числовыми. Это также не замедляется plpgsql. Он работает быстро и работает с любым размером. Тестовый случай, взятый из ответа Эрвина для сравнения,

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.