web-dev-qa-db-ja.com

テキスト表現の16進数を10進数に変換します

PostgreSQL 9.1を使用して16進数を10進数に変換しようとしています

このクエリでは:

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

次のエラーが表示されます。

ERROR:  invalid input syntax for type numeric: " "

私は何を間違えていますか?

33
Hartwig

2つの差し迫った問題があります。

  1. to_number は16進数を理解しません。
  2. Xto_number形式の文字列には意味がなく、意味のないものは明らかに「文字をスキップする」ことを意味します。

(2)の正式な正当性はなく、経験的証拠のみです。

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

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

ドキュメントには、二重引用符で囲まれたパターンの動作方法が記載されています。

to_dateto_number、およびto_timestampでは、二重引用符で囲まれた文字列は、文字列に含まれる入力文字の数をスキップします。 "XX"は2つの入力文字をスキップします。

ただし、フォーマット文字ではない引用符で囲まれていない文字の動作は指定されていないようです。

いずれにせよ、to_numberは16進数を数値に変換するための適切なツールではありません。次のように言います。

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)

**整数オーバーフローエラーによるこのような負の数を回避するには、intではなくbigintを使用して、より大きな16進数(IPアドレスなど)に対応します。

19
mu is too short

動的SQLなしの方法があります。

最大8桁の16進数

text 表現の16進数から数値型へのキャストはありませんが、ウェイポイントとして bit(n) を使用できます。 4ビットビット文字列で1桁の16進数をエンコードします。 bit(32)(最大8桁の16進数)までのビット文字列から integer (標準4バイトまでの文書化されていないキャストがあります。整数)-内部表現はバイナリ互換です。

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

結果:

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

all 8桁までの16進数をエンコードするには4バイトで十分ですが、Postgresのintegerは符号付きタイプなので、16進数上記の_'7fffffff'_負の整数数値へのオーバーフロー。これはまだ一意の表現ですが、の意味は異なります。それがbigintへの切り替えが重要な場合は、以下を参照してください。

未知数の16進数可変長 leading zeros _0_をbit(32)にキャストするように埋め込む必要があります。既知の長さの数については、長さ指定子を調整するだけです。 7桁の16進数とintまたは8桁とbigintの例:

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

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

最大16桁の16進数

bigint (_int8_、8バイト整数)を16桁までの16進数で使用-上半分の負の数にオーバーフロー:

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

結果:

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

16桁を超える16進数の場合、最下位文字(右端を超える)は truncated になります。

このキャストは文書化されていない動作に依存しています。引用する Tom Lane here

これは、ビット型入力コンバーターのいくつかの文書化されていない動作に依存していますが、それが壊れると予想する理由はありません。おそらくより大きい問題は、PG> = 8.3を必要とすることです。その前にビットキャストするテキストがなかったためです。

最大のUUID 32桁の16進数

Postgres uuid データ型は数値型ではないであるため、これは質問とは異なります。ただし、標準のPostgresで最大32桁の16進数を格納するのが最も効率的なタイプで、16バイトのストレージしか占有しません。 直接キャストがありますが、 exactly 32桁の16進数が必要です。

_SELECT lpad(hex, 32, '0')::uuid 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
_

ご覧のように、標準出力は16進数の文字列で、UUIDの一般的な区切り文字が付いています。

md5ハッシュ

これは、md5ハッシュを保存するのに特に便利です。

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

結果:

_           md5_hash
--------------------------------------
 02e10e94-e895-616e-8e23-bb7f8025da42
_
68

他の誰かが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;
3
Pierre D

_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
Evan Carroll

以下はnumericを使用するバージョンです。したがって、任意の大きな16進文字列を処理できます。

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
1
David Wolever