【问题标题】:How can wrap-on-overflow math be done in Postgres?如何在 Postgres 中完成溢出数学运算?
【发布时间】:2020-09-17 12:21:00
【问题描述】:
  • 给定以下示例函数:
CREATE OR REPLACE FUNCTION add_max_value(_x BIGINT)
    RETURNS BIGINT
    LANGUAGE sql
    AS $$
        SELECT 9223372036854775807 + _x;
    $$;
  • 如果使用任何正值调用此函数,则返回以下错误:
SELECT add_max_value(1); -- Expecting -9223372036854775808 if math wrapped

-- SQL Error [22003]: ERROR: bigint out of range
  • 如何在 Postgres 中进行溢出整数数学运算?
    • 请注意:
      1. 我想在数据库中执行此操作,而不是在应用程序中
      2. 我不希望它提升为任意精度整数 (NUMERIC)
      3. 虽然这个例子只做加法,但实际上我对其他操作也很感兴趣

【问题讨论】:

  • 即使是负输入,该函数是否真的会引发错误?
  • @a_horse_with_no_name 第二个代码块中有预期的结果。重申一下,许多主流语言(C、Java)提供的相同包装行为。
  • @jarlh 你是对的,好电话;正整数是示例中的问题。问题已更新。
  • 除了在 PL/pgSQL 中使用中间 numeric 变量并手动“包装”之外,我没有看到任何其他方法

标签: sql postgresql plpgsql


【解决方案1】:

作为一个 SQL 函数,没有办法。 SQL 函数不能处理异常。但是plpgsql 函数可以:

CREATE OR REPLACE FUNCTION add_max_value(_x BIGINT)
    RETURNS BIGINT
    LANGUAGE plpgsql
    AS $$
    declare 
        bigx bigint; 
    begin 
        bigx = 9223372036854775807 + _x; 
        return bigx;
    exception
        when sqlstate '22003' then
             return (9223372036854775807::numeric + _x - 2^64)::bigint;
    end;
    $$;

【讨论】:

  • 你会比我清楚得多。但是是的,你也可以只“返回 1”。
  • 没问题。细节无所谓,是处理异常的概念,特别是具体的异常。
  • 先将加法的结果赋值给numeric变量,然后检查它是否大于9223372036854775807会更有效吗?异常处理是相当昂贵的
  • 是的,对于提出的异常是有意引起的人为示例,但我认为不适用于算术溢出的一般情况。我不认为 OP 问题与所提出的具体示例有关,而是针对一般情况。如果问题是针对特定示例,那么效率更高,但只需使用语句“select (9223372036854775807::numeric + _x - 2^64)::bigint;”的 SQL 函数直接进入重置值;
【解决方案2】:

这很容易在我写过的最浪费的 SQL 中排名前五:

create or replace function add_max_value(_x bigint)
  returns bigint
  language sql
as $$
with recursive inputs as (
  select s.rn, r.a::int, s.b::int, (r.a::int + s.b::int) % 2 as sumbit, 
        (r.a::bit & s.b::bit)::int as carry  
    from regexp_split_to_table((9223372036854775807::bit(64))::text, '') with ordinality as r(a, rn)
         join regexp_split_to_table((_x::bit(64))::text, '') with ordinality as s(b, rn)
           on s.rn = r.rn
), addition as (
  select rn, sumbit, sumbit as s2, carry, carry as upcarry
    from inputs
   where rn = 64
  union 
  select i.rn, i.sumbit, (i.sumbit + a.upcarry) % 2, i.carry, 
         (i.carry::bit | a.upcarry::bit)::int
    from addition a
    join inputs i on i.rn = a.rn - 1
)
select (string_agg(s2::text, '' order by rn)::bit(64))::bigint
  from addition
$$;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-03
    • 2011-09-23
    相关资源
    最近更新 更多