我正在尝试创建一个返回最低分数值的函数。示例代码在这里:
create or replace function fraction_sh(x number) return varchar2
is
fra1 number;
pwr number;
intprt number;
v4 number;
numer number;
denom number;
gcdval number;
frac varchar2(50);
begin
if x <> 0 then
fra1 := mod(x,1);
pwr := length(mod(x,1))-1;
intprt := trunc(x);
numer :=mod(x,1)*power(10,length(mod(x,1))-1);
denom :=power(10,length(mod(x,1))-1);
gcdval := gcdnew(power(10,length(mod(x,1))-1),mod(x,1)*power(10,length(mod(x,1))-1));
if intprt = 0 then
frac := to_char(trunc(numer/gcdval))||'/'||to_char(trunc(denom/gcdval));
DBMS_OUTPUT.put_line(1||' '||denom||' '||gcdval||' '||numer);
else
frac := (intprt*to_char(trunc(denom/gcdval)))+to_char(trunc(numer/gcdval))||'/'||to_char(trunc(denom/gcdval));
DBMS_OUTPUT.put_line(2||' '||denom||' '||gcdval||' '||numer);
end if;
end if;
return frac;
end;
create or replace function gcdnew (a number, b number, p_precision number default null, orig_larger_num number default null) return number is
v_orig_larger_num number := greatest(nvl(orig_larger_num,-1),a,b);
v_precision_level number := p_precision;
begin
if a is null or b is null or (a = 0 and b = 0) then return 1; end if;
if p_precision is null or p_precision <= 0 then
v_precision_level := 4;
end if;
if b is null or b = 0 or (b/v_orig_larger_num <= power(10,-1*v_precision_level) and greatest(a,b) <> v_orig_larger_num) then
return a;
else
return (gcdnew(b,mod(a,b),v_precision_level,v_orig_larger_num));
end if;
端;
在大多数情况下它起作用,但是当我尝试通过2/11时它返回2/10。
任何帮助表示感谢。
答案 0 :(得分:0)
你可以像这样使用:
create or replace function fraction_sh(dividing number,divided number) return varchar2
is
dividing2 number;
divided2 number;
frac varchar2(100 char);
temp number;
loop_value boolean;
begin
loop_value:=true;
dividing2:=dividing;
divided2 :=divided;
if dividing <> 0 then
while loop_value
loop
if gcd(dividing2,divided2)<> 1 then
temp:=gcd(dividing2,divided2);
dividing2:=dividing2/temp;
divided2 :=divided2/temp;
frac:=dividing2||'/'||divided2;
else
loop_value:=false;
frac:=dividing2||'/'||divided2;
end if;
end loop;
else
frac:='0';
end if;
return frac;
end;
gcd func:
create or replace function gcd(a number, b number)
return number is
begin
if b = 0 then
return a;
else
return gcd(b,mod(a,b));
end if;
end;
答案 1 :(得分:0)
您目前正在做的事情的问题是精确度。对于2/11,结果数字为0.1818181 ...重复出现,并且其长度 - 因此pwr
值 - 最终为40,这会破坏以后的计算。
通过修改来限制精度(并且稍微整理一下,主要是为了在已经有方便的变量时删除重复计算):
create or replace function fraction_sh(p_float number) return varchar2
is
l_precision pls_integer := 10;
l_int_part pls_integer;
l_frac_part number;
l_power pls_integer;
l_numer number;
l_denom number;
l_gcdval number;
l_result varchar2(99);
begin
if p_float is null or p_float = 0 then
return null;
end if;
l_int_part := trunc(p_float);
l_frac_part := round(mod(p_float, 1), l_precision);
l_power := length(l_frac_part);
l_denom := power(10, l_power);
l_numer := l_frac_part * l_denom;
l_gcdval := gcdnew(l_denom, l_numer, ceil(l_precision/2));
if l_int_part = 0 then
l_result := trunc(l_numer/l_gcdval) ||'/'|| trunc(l_denom/l_gcdval);
else
l_result := l_int_part * (trunc(l_denom/l_gcdval) + trunc(l_numer/l_gcdval))
||'/'|| trunc(l_denom/l_gcdval);
end if;
return l_result;
end;
/
获得:
with t(n) as (
select 9/12 from dual
union all select 2/11 from dual
union all select 1/2 from dual
union all select 1/3 from dual
union all select 1/4 from dual
union all select 1/5 from dual
union all select 1/6 from dual
union all select 1/7 from dual
union all select 1/8 from dual
union all select 1/9 from dual
union all select 1/10 from dual
union all select 4/3 from dual
union all select 0 from dual
union all select 1 from dual
)
select n, fraction_sh(n) as fraction
from t;
N FRACTION
---------- ------------------------------
.75 3/4
.181818182 2/11
.5 1/2
.333333333 1/3
.25 1/4
.2 1/5
.166666667 1/6
.142857143 1/7
.125 1/8
.111111111 1/9
.1 1/10
1.33333333 4/3
0
1 1/1
所以你可能想要为传入1添加一些处理,或者在舍入结束后为1/1添加近似值 - 可能只是为了在任何一种情况下返回普通'1'
。
我已将l_precision
设置为10而非任意,你可以将其设置得更大,但会在某些时候遇到问题,所以请仔细测试你选择的任何值。
(我根本没看过gdcnew
;这可能会有点简化。)