优雅的PL / SQL比较两个不同小数位数的数字

时间:2013-05-30 07:19:00

标签: plsql numbers precision

我需要一个函数来比较两个数字,以便10.12432 = 10.12410.12432 != 10.123。也就是说,需要根据不太准确的小数点来舍入更准确的数字。

以下功能似乎可以满足我的需求(根据Noel的评论编辑):

function eq(number_1 in number, number_2 in number) return boolean is
    string_1 varchar2(100); 
    string_2 varchar2(100); 

    num_1    number;
    num_2    number;

    len      number;
begin
    string_1 := to_char(number_1, 'FM99999999999999999999999999999.99999999999999999999999999999');
    string_2 := to_char(number_2, 'FM99999999999999999999999999999.99999999999999999999999999999');

    string_1 := regexp_replace(string_1, '.*(\..*)', '\1');
    string_2 := regexp_replace(string_2, '.*(\..*)', '\1');

    len   := least(length(string_1), length(string_2)) - 1;

    num_1 := round(number_1, len);
    num_2 := round(number_2, len);

    return num_1 = num_2;

end eq;

但是,imho,并不是最令人满意的方法。有没有更好的解决方案,特别是没有诉诸字符串的解决方案?

4 个答案:

答案 0 :(得分:0)

如果只知道有多少小数位,你总是会有一点使用字符串。使用最小字符串的一种解决方案是:

FUNCTION eq(number_1 in number, number_2 in number) return boolean is
    dot1 NUMBER;
    dot2 NUMBER;
    min_places  NUMBER;
BEGIN
    dot1 := INSTR( number_1, '.' );
    dot2 := INSTR( number_2, '.' );
    IF( dot1 = 0 OR dot2 = 0 )
    THEN
        min_places := 0;
    ELSE
        min_places := NVL( LEAST( LENGTH( SUBSTR(number_1, dot1+1) )
                                , LENGTH( SUBSTR(number_2, dot2+1) )
                                ), 0 );
    END IF;
    RETURN ROUND( number_1, min_places ) = ROUND( number_2, min_places );
END eq;

编辑:刚刚从您的示例中了解了第二个参数。我之前使用的是*POWER(10,min_places)。感谢。

答案 1 :(得分:0)

试试这个:

CREATE OR REPLACE function are_equal(i_num1 number, i_num2 number) return number deterministic is
  l_rv number := 0;
  l_places number := 0;
begin
  l_places := least(length(trim(regexp_replace(i_num1, '[^.]+\.(.*)$', '\1'))),length(trim(regexp_replace(i_num2, '[^.]+\.(.*)$', '\1'))));
  l_rv := case when (round(i_num1,l_places) = round(i_num2,l_places))  then 1 
  else 0 end;
  return l_rv;
end;

请注意,我正在舍入(根据你的帖子),并返回1或0而不是boolean(更有用的imo,在pl / sql之外的上下文中)。

答案 2 :(得分:0)

好的,我想我找到了没有字符串转换的解决方案。拍!

declare
  function find_precision(
    p_input in number
  ) return number
  is
    l_check number;
  begin
    for l_i in 0 .. 39
    loop
      l_check := round(p_input, l_i);
      -- as soon as l_check is the same number as p_input we have reached the right number of decimals
      if l_check = p_input
      then
        return l_i;
      end if;
    end loop;
    -- should never get here
    raise value_error;
  end find_precision;

  function lossy_compare(
    p_number1 in number
  , p_number2 in number
  ) return boolean
  is
    l_precision number;
  begin
    l_precision := least(find_precision(p_number1), find_precision(p_number2));
    return round(p_number1, l_precision) = round(p_number2, l_precision);
  end lossy_compare;
begin

  if lossy_compare(10.12432, 10.124)
  then
    dbms_output.put_line('equal');
  else
    dbms_output.put_line('not equal');
  end if;

  if lossy_compare(10.12432, 10.123)
  then
    dbms_output.put_line('equal');
  else
    dbms_output.put_line('not equal');
  end if;
end;
/

请注意,此代码将10.124000的精度设置为3(实际上为6),但这与解决您的问题无关。

修改 重新思考我的上一句话,这可能是不正确的。比较10.124000和10.124001时,您会得到什么结果?我的解决方案会给出“相等”(因为它主要将其视为将10.124与10.124001进行比较),而有人可能会认为“不相等”(因为尾随0会增加精度)。

答案 3 :(得分:0)

我意识到这是一篇很老的文章,但是最近我看到了一些有关浮点值的问题。因此,当我遇到这个问题时,我疏通了我用于进行浮点比较的旧例程。希望这对将来的搜索者有用。顺便说一句,它不需要转换为字符串,因为它会比较相对值。

create or replace function compare_float(
                            float_value_1  double precision
                          , float_value_2  double precision
                          , delta_value    double precision default 1.0e-6
                          )
 return integer
 is
 /* 
    Name:          Compare_Float
    Author:        Belayer
    Date Written:  22-Jan-2009

    Floating point number are ALWAYS estimates as there is no way to precisely a
    decimal base 10 number in binary. Therefore comparing 2 floating points of 
    for equality is a risky proposition at best. 

    While the above is true, we can at least inquire as to the difference being
    enough as to not matter and then consider the values as equal even though
    in the computed since they may not be.

    This routine implements the 'close enough' comparison to give the relative 
    magnitude relationship between 2 floating point variables.

    Parameters:
      Float_Value_1. The first of 2 floating point values to be compared. 
      Float_Value_2. The second of he 2 floating point values to be compared.
      Delta_Value.   The amount of the 2 values can differ and still be considered 
                     to be equal.  Default value 10^-6 (.000001) This value can be
                     overridden on each call to the procedure.
    Return Values:
      -1 Float_Value_1 is less than Float_Value_2.
       0 Float_Value_1 equals Float_Value_2 within specified Datla_Value.
       1 Float_Value_1 is greater than Float_Value_2.

*/ 
     delta_diff           double precision;
     compare_float_result integer;
begin
    delta_diff := float_value_1 - float_value_2;
    if abs(delta_diff) < delta_value
    then
        compare_float_result := 0;
    else 
        compare_float_result := sign(delta_diff);
    end if;    
    return compare_float_result;
end compare_float; 

以下内容将测试相同的值,并为delta_value指定不同的值,但允许的值之间的差仍视为相等。使用值1.0e-6(默认),1.0e-3和1.0e-7进行测试。

-- test 1.3e-6 (default)
with vals (f,s,e) as 
     ( select 10.12432,  10.124,      1  from dual union all
       select 10.124,    10.124001,  -1  from dual union all
       select 1.0000124,  1.0000120,  0  from dual union all
       select 1.000124,   1.000120,   1  from dual union all       
       select 1.11000015, 1.12000015,-1  from dual union all
       select 0.0000010,  0.00000011, 0  from dual
     ) 
select compare_float(f,s) result, e expecting
  from vals;
-- test 1.3e-3 (default)  
with vals (f,s,e) as 
     ( select 10.12432,  10.124,      0  from dual union all
       select 10.124,    10.124001,   0  from dual union all
       select 1.0000124,  1.0000120,  0  from dual union all
       select 1.000124,   1.000120,   0  from dual union all       
       select 1.11000015, 1.12000015,-1  from dual union all
       select 0.0000010,  0.00000011, 0  from dual
     ) 
select compare_float(f,s, 1.0e-3) result, e expecting
  from vals; '
-- test 1.3e-7  
with vals (f,s,e) as 
     ( select 10.12432,  10.124,      1  from dual union all
       select 10.124,    10.124001,  -1  from dual union all
       select 1.0000124,  1.0000120,  1  from dual union all
       select 1.000124,   1.000120,   1  from dual union all       
       select 1.11000015, 1.12000015,-1  from dual union all
       select 0.0000010,  0.00000011,-1  from dual
     ) 
select compare_float(f,s, 1.0e-7) result, e expecting
  from vals;  

有人在那里想要它。注意:此例程来自更老的Fortran。