Saltar al contenido

PostgreSQL: convierte una cadena hexadecimal de un número muy grande en un NUMÉRICO

Solución:

Esto es una especie de fuerza bruta y nada a prueba de balas:

CREATE OR REPLACE FUNCTION hex_to_int(hexval varchar) RETURNS numeric AS $$
DECLARE
  result  NUMERIC;
  i integer;
  len integer;
  hexchar varchar;
BEGIN

  result := 0;
  len := length(hexval);

  for i in 1..len loop
    hexchar := substr(hexval, len - i + 1, 1);
    result := result + round(16 ^ (i - 1)::dec * case
      when hexchar between '0' and '9' then cast (hexchar as int)
      when upper (hexchar) between 'A' and 'F' then ascii(upper(hexchar)) - 55
    end);
  end loop;

 RETURN result;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE STRICT;

select hex_to_int('12AE34F');  -- returns 19587919

O, si tiene PL / Perl instalado, puede dejar que haga el trabajo pesado:

CREATE OR REPLACE FUNCTION hex_to_int_perl(varchar)
  RETURNS numeric AS
$BODY$
  my ($hex) = @_;
  return sprintf "%d", hex($hex);
$BODY$
  LANGUAGE plperl VOLATILE
  COST 100;

select hex_to_int_perl('12AE34F');  -- returns 19587919

No creo que el que no sea de Perl funcione con números negativos, y estoy bastante seguro de que ambos te darán malos resultados si pones un valor que no sea hexadecimal, pero esos serían escenarios bastante fáciles de atrapar y manejar, dependiendo sobre lo que desea que haga la función.

Mi caso de uso para esto fue convertir valores hash SHA-1 hexadecimales en números enteros. Sin prestar atención a la precisión numérica en toda la función, este caso de uso revela fácilmente las deficiencias; pero ciertamente los casos de uso con “números muy grandes” aún mayores son fáciles de identificar.

Dados algunos hash SHA-1, la primera de las soluciones de respuesta aceptadas arrojó lo siguiente:

SELECT hex_to_int('356e90d2a2d414ba8757ec2ab91f2f19c481d4c3');
-- returns 305042208670409000000000000000000000000000000000

SELECT hex_to_int('aaa9f7193cc8efe7e98145b0f8d9ae5f1712c25b');
-- returns 974318782301086000000000000000000000000000000000

Por supuesto, cada resultado que se rellena a la derecha con tantos ceros es un signo de precisión inadecuada para el caso de uso.

Aquí es donde terminé para obtener la precisión que necesitaba:

CREATE OR REPLACE FUNCTION hex_to_int(hexVal varchar) RETURNS numeric(1000) AS $$
DECLARE
    intVal numeric(1000) := 0;
    hexLength integer;
    i integer;
    hexDigit varchar;
BEGIN
    hexLength := length(hexVal);

    FOR i IN 1..hexLength
    LOOP
        hexDigit := substr(hexVal, hexLength - i + 1, 1);
        intVal :=
            intVal +
            CASE
                WHEN hexDigit BETWEEN '0' AND '9' THEN CAST(hexDigit AS numeric(1000))
                WHEN upper(hexDigit) BETWEEN 'A' AND 'F' THEN CAST(ascii(upper(hexDigit)) - 55 AS numeric(1000))
            END *
            CAST(16 AS numeric(1000)) ^ CAST(i - 1 AS numeric(1000));
    END LOOP;

    RETURN intVal;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE STRICT;


SELECT hex_to_int('356e90d2a2d414ba8757ec2ab91f2f19c481d4c3');
-- returns 305042208670409212880202819376501392142011323587

SELECT hex_to_int('aaa9f7193cc8efe7e98145b0f8d9ae5f1712c25b');
-- returns 974318782301085717223606317572453925850501530203

Otra función de conversión más. La idea es reducir el número de pasos en el ciclo y, por tanto, el número de operaciones aritméticas.

create or replace function hex_to_numeric(str text)
returns numeric language plpgsql immutable strict as $$
declare
    i int;
    n int = length(str)/ 8;
    res dec = 0;
begin
    str := lpad($1, (n+ 1)* 8, '0');
    for i in 0..n loop
        if i > 0 then 
            res:= res * 4294967296; 
        end if;
        res:= res + concat('x', substr(str, i* 8+ 1, 8))::bit(32)::bigint::dec;
    end loop;
    return res;
end $$;

Algunas pruebas:

select hex, hex_to_numeric(hex) 
from   (
   values ('ff'::text),
        ('7fffffff'),
        ('80000000'),
        ('deadbeef'),
        ('7fffffffffffffff'),
        ('8000000000000000'),
        ('ffffffffffffffff'),
        ('ffffffffffffffff123'),
        ('4540a085e7334d6494dd6a7378c579f6')
   ) t(hex);

               hex                |             hex_to_numeric             
----------------------------------+----------------------------------------
 ff                               |                                    255
 7fffffff                         |                             2147483647
 80000000                         |                             2147483648
 deadbeef                         |                             3735928559
 7fffffffffffffff                 |                    9223372036854775807
 8000000000000000                 |                    9223372036854775808
 ffffffffffffffff                 |                   18446744073709551615
 ffffffffffffffff123              |                75557863725914323415331
 4540a085e7334d6494dd6a7378c579f6 | 92052294502540680826164862654582454774
(9 rows)
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *