PostgreSQL:将非常大的十六进制字符串转换为 NUMERIC
PostgreSQL: convert hex string of a very large number to a NUMERIC
我正在尝试转换非常大的十六进制字符串
到 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
这有点蛮力,一点也不防弹:
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 函数适用于负数,而且我很确定如果你输入非十六进制值,两者都会给你带来不好的结果,但这些场景很容易陷阱和处理,具体取决于您希望函数执行的操作。
我的用例是将十六进制 SHA-1 哈希值转换为整数。如果不注意整个函数的数字精度,这个用例很容易暴露缺点;但当然 "very large number" 更大的用例很容易识别。
给定一些 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
又一个转换函数。这个想法是减少循环中的步骤数,从而减少算术运算的数量。
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(, (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 倍(取决于十六进制值的长度)。
我正在尝试转换非常大的十六进制字符串 到 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
这有点蛮力,一点也不防弹:
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 函数适用于负数,而且我很确定如果你输入非十六进制值,两者都会给你带来不好的结果,但这些场景很容易陷阱和处理,具体取决于您希望函数执行的操作。
我的用例是将十六进制 SHA-1 哈希值转换为整数。如果不注意整个函数的数字精度,这个用例很容易暴露缺点;但当然 "very large number" 更大的用例很容易识别。
给定一些 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
又一个转换函数。这个想法是减少循环中的步骤数,从而减少算术运算的数量。
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(, (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 倍(取决于十六进制值的长度)。