web-dev-qa-db-fra.com

Convertir un hexagone dans une représentation textuelle en nombre décimal

J'essaye de convertir hex en décimal à l'aide de PostgreSQL 9.1

avec cette requête:

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

Je reçois l'erreur suivante:

ERROR:  invalid input syntax for type numeric: " "

Qu'est-ce que je fais mal?

31
Hartwig

Vous avez deux problèmes immédiats:

  1. to_number ne comprend pas l'hexadécimal.
  2. X n'a aucune signification dans une chaîne de formatage to_number et toute chose sans signification signifie apparemment "ignorer un caractère".

Je n'ai pas de justification faisant autorité pour (2), juste des preuves empiriques:

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

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

La documentation mentionne le comportement supposé des motifs cités:

Dans to_date, to_number et to_timestamp, les chaînes entre guillemets doubles ignorent le nombre de caractères saisis contenus dans la chaîne, par exemple. "XX" ignore deux caractères saisis.

mais le comportement des caractères non cités qui ne sont pas des caractères de formatage semble être non spécifié.

Dans tous les cas, to_number n'est pas le bon outil pour convertir un hex en nombres, vous voulez dire quelque chose comme ceci:

select x'deadbeef'::int;

alors peut-être cette fonction fonctionnera mieux pour vous:

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;

Ensuite:

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

** Pour éviter des nombres négatifs comme ceux-ci dus à une erreur de dépassement d'entier, utilisez bigint au lieu de int pour gérer des nombres hexadécimaux plus grands (comme les adresses IP).

18
mu is too short

Il existe des moyens sans SQL dynamique.

Max. 8 chiffres hexadécimaux

Il n'y a pas d'incantation à partir de nombres hexadécimaux dans text représentation en un type numérique, mais nous pouvons utiliser bit(n) comme point de cheminement. 4 bits dans une chaîne de bits encoder 1 chiffre hexadécimal. Il existe une conversion non documentée de chaînes binaires jusqu'à bit(32) (max. 8 chiffres hexadécimaux) vers integer (entier standard à 4 octets) - la représentation interne est compatible binaire.

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);

Résultat:

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

4 octets suffisent pour encoder tous nombres hexadécimaux jusqu'à 8 chiffres mais integer dans Postgres est un type signé, ainsi les nombres hexadécimaux supérieurs '7fffffff' débordent dans un nombre négatif int. Ceci est toujours une représentation unique, mais le signification est différent. Si cela est important, passez à bigint, voir ci-dessous.

Pour les nombres hexadécimaux inconnus de longueur variable, nous devons ajouter interligne zéros 0, comme indiqué pour le transtypage en bit(32). Pour les nombres de longueur connue, nous pouvons simplement adapter le spécificateur de longueur. Exemple avec 7 chiffres hexadécimaux et int ou 8 chiffres et bigint:

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

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

Max. 16 chiffres hexadécimaux

Utilisez bigint (int8, entier sur 8 octets) pour un maximum de 16 chiffres hexadécimaux - dépassement des nombres négatifs dans la moitié supérieure:

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);

Résultat:

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

Pour plus de 16 chiffres hexadécimaux, les caractères les moins significatifs (dépassement à droite) obtiennent tronqué.

Cette distribution s'appuie sur un comportement non documenté, je cite Tom Lane ici :

Cela repose sur un comportement non documenté de l'entrée de type bit convertisseur, mais je ne vois aucune raison de s’attendre à ce que cela casse. Un possible Le plus gros problème est qu'il nécessite un PG> = 8.3 puisqu'il n'y avait pas de texte mordre avant cela.

UUID pour max. 32 chiffres hexadécimaux

Le type de données Postgres uuid est n'est pas un type numérique, ce qui diffère de la question posée. Mais c'est le type le plus efficace dans Postgres standard pour stocker jusqu'à 32 chiffres hexadécimaux, n'occupant que 16 octets de stockage. Il y a un diffusion directe, mais exactement 32 chiffres hexadécimaux sont requis.

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

Résultat:

              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

Comme vous pouvez le constater, la sortie standard est une chaîne de chiffres hexadécimaux avec des séparateurs typiques pour l’UUID.

hash md5

Ceci est particulièrement utile pour stocker md5 hashes:

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

Résultat:

           md5_hash
--------------------------------------
 02e10e94-e895-616e-8e23-bb7f8025da42
62
Erwin Brandstetter

Si quelqu'un d'autre est coincé avec PG8.2, voici une autre façon de le faire.

version 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;

version 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;
3
Pierre D

pg-bignum

En interne, pg-bignum utilise la bibliothèque SSL pour les grands nombres. Cette méthode ne présente aucun des inconvénients mentionnés dans les autres réponses avec numérique. Il n'est pas non plus ralenti par plpgsql. C'est rapide et cela fonctionne avec un nombre de n'importe quelle taille. Cas de test tiré de la réponse d'Erwin pour comparaison,

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)

Vous pouvez obtenir le type numérique en utilisant bn_in_hex('deadbeef')::text::numeric.

3
Evan Carroll

Voici une version qui utilise numeric, afin de pouvoir gérer des chaînes hexagonales de taille arbitraire:

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$;

Par exemple:

=# select hex_to_decimal('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff');
32592575621351777380295131014550050576823494298654980010178247189670100796213387298934358015
0
David Wolever