【问题标题】:PostgreSQL: convert hex string of a very large number to a NUMERICPostgreSQL:将一个非常大的数字的十六进制字符串转换为数字
【发布时间】:2016-02-02 20:47:22
【问题描述】:

我正在尝试转换一个非常大的数字的十六进制字符串 到 NUMERIC 列

CREATE OR REPLACE FUNCTION hex_to_int(hexval varchar) RETURNS NUMERIC AS $$
DECLARE
   result  NUMERIC;
BEGIN
 EXECUTE 'SELECT x''' || hexval || '''::NUMERIC(40,0)' INTO result;
 RETURN result;
END;
$$
LANGUAGE 'plpgsql' IMMUTABLE STRICT;

我正在尝试这样做:

select hex_to_int(tx.value) from internal_transaction tx

我得到的错误是:

[42846] ERROR: cannot cast type bit to numeric Where: PL/pgSQL function hex_to_int(character varying) line 5 at EXECUTE statement

【问题讨论】:

标签: postgresql types casting hex


【解决方案1】:

又一个转换函数。这个想法是减少循环中的步骤数,从而减少算术运算的数量。

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

一些测试:

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)

该函数比其他答案中的 plpgsql 函数快 5-10 倍(取决于十六进制值的长度)。

【讨论】:

    【解决方案2】:

    这是一种蛮力,一点也不防弹:

    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
    

    或者,如果您安装了 PL/Perl,您可以让它完成繁重的工作:

    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
    

    我认为非 Perl 不适用于负数,而且我很确定如果输入非十六进制值,两者都会给你带来不好的结果,但这些场景很容易陷入陷阱和句柄,取决于你想让函数做什么。

    【讨论】:

    • 非 perl 函数不能很好地处理超过 16 个字符的输入。
    【解决方案3】:

    我的用例是将十六进制 SHA-1 哈希值转换为整数。如果不注意整个函数的数值精度,这个用例很容易暴露缺点;但当然,具有更大“非常大数字”的用例很容易识别。

    给定一些 SHA-1 哈希,第一个接受的答案的解决方案产生以下结果:

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

    当然,每个结果右填充了这么多零是用例精度不足的标志。

    这是我最终获得所需精度的地方:

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-19
      • 2018-11-02
      • 2021-12-26
      • 1970-01-01
      • 2021-02-27
      相关资源
      最近更新 更多