我需要一个函数来比较两个数字,以便10.12432 = 10.124
和10.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,并不是最令人满意的方法。有没有更好的解决方案,特别是没有诉诸字符串的解决方案?
答案 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。