3

我需要一个函数来比较两个数字,以便10.12432 = 10.12410.12432 != 10.123。即更准确的数字需要根据不太准确的数字的小数位四舍五入。

以下功能似乎可以满足我的需要(根据诺埃尔的评论编辑):

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;

但是,恕我直言,这不是最令人满意的方法。有没有更好的解决方案,尤其是不使用字符串的解决方案?

4

4 回答 4

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 确实增加了精度)。

于 2013-06-05T10:34:18.123 回答
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 而不是布尔值(更有用的 imo,在 pl/sql 之外的上下文中)。

于 2013-05-30T15:53:40.293 回答
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)以前用过。谢谢。

于 2013-05-30T08:00:53.383 回答
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。

于 2020-02-17T07:39:16.863 回答