Преобразовать шестнадцатеричное в текстовом представлении в десятичное число - PullRequest
32 голосов
/ 29 ноября 2011

Я пытаюсь преобразовать шестнадцатеричное в десятичное с использованием PostgreSQL 9.1

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

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

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

ERROR:  invalid input syntax for type numeric: " "

Что я делаю не так?

Ответы [ 5 ]

66 голосов
/ 01 декабря 2011

Есть способы без динамического SQL .

Макс.8 шестнадцатеричных цифр

В представлении text к числовому типу нет преобразования из шестнадцатеричных чисел, но мы можем использовать bit(n) в качестве путевой точки. 4 бита в битовой строке кодируют 1 шестнадцатеричную цифру.Существует недокументированное приведение от битовых строк до bit(32) (макс. 8 шестнадцатеричных цифр) до integer (стандартное 4-байтовое целое число) - внутреннее представление двоично-совместимо.

SELECT <b>('x' || lpad(hex, 8, '0'))::bit(32)::int</b> 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' переполняются до отрицательного целого числа .Это все еще уникальное представление, но означает другое.Если это имеет значение, переключитесь на 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 <b>('x' || lpad(hex, 16, '0'))::bit(64)::bigint</b> 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 шестнадцатеричных цифр младшие значащие символы (слева направо) получают усеченные .

Это приведение основано на недокументированном поведении , цитирую Тома Лейна здесь :

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

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

Тип данных Postgres uuid равен , а не числовому типу , поэтому это отличается от заданного вопроса.Но это самый эффективный тип в стандартных Postgres для хранения до 32 шестнадцатеричных цифр, занимающих всего 16 байтов.Существует прямое приведение , но требуется точно 32 шестнадцатеричные цифры.

SELECT <b>lpad(hex, 32, '0')::uuid</b> 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
19 голосов
/ 29 ноября 2011

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

  1. to_number не понимает шестнадцатеричное.
  2. 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 не подходит для преобразования шестнадцатеричных чисел в цифры, вы хотите сказать что-то вроде этого:

select x'deadbeef'::int;

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

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-адресов).

3 голосов
/ 09 декабря 2017

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.

3 голосов
/ 12 июля 2014

Если кто-то еще застрял с 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 версия:

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;
1 голос
/ 19 октября 2018

Вот версия, которая использует 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
...