Rounding numbers given in string representation while controlling their number of significant digits
For a reporting application demanding numeric accuracy we needed a functionality for rounding (positive) numbers given in string representation while controlling their number of significant digits. I found the standard oracle functionality somewhat lacking in this respect since it doesn’t necessarily maintain the number of significant digits, at least when using the standard chain: round(to_number(..)).
The following PL/SQL is our own string rounding function 
, based solely on the string representaion of the number. The method used is rather obvious (and mathematically trivial) so I consider the code to be amply self explanatory. The function is fed with the floating point number in string form (p_numstr) and the number of significant digits to round it to (p_dig).
create or replace FUNCTION Str_Round (p_Numstr IN VARCHAR2, p_Dig NUMBER) RETURN VARCHAR2 IS
v_Numstr VARCHAR2(30) := p_Numstr;<br /> v_Int VARCHAR2(30);<br /> v_Frc VARCHAR2(30);<br /> v_Frc1 VARCHAR2(30);<br /> v_Frc2 VARCHAR2(30);<br /> v_Len NUMBER;<br /> v_Ln1 NUMBER;<br /> v_Ln2 NUMBER;<br /> v_Pt NUMBER;<br /> v_Dig NUMBER := p_Dig;<br /> v_Num NUMBER;<br /> v_sgn VARCHAR2(1);<br /><br />BEGIN<br /> --pos or neg?<br /> v_sgn := SubStr(v_numstr,1,1);<br /> IF v_sgn <> '-' THEN<br /> v_sgn := '';<br /> ELSE<br /> -- use abs value of v_numstr in the rest<br /> v_numstr := SubStr(v_numstr,2);<br /> END IF;<br /><br /> v_Pt := Instr(v_Numstr, '.');<br /> v_Len := Length(v_Numstr);<br /><br /> -- correct pathological representations (eg '.99');<br /> IF v_Pt = 1 THEN<br /> v_Numstr := '0' || v_Numstr;<br /> v_len := v_len + 1;<br /> v_pt := 2;<br /> ELSIF v_Pt = v_Len THEN<br /> v_Numstr := v_Numstr || '0';<br /> v_pt := v_pt;<br /> v_len := v_len + 1;<br /> ELSIF v_Pt = 0 THEN<br /> v_Numstr := v_Numstr || '.0';<br /> v_pt := v_len + 1;<br /> v_len := v_len + 2;<br /> END IF;<br /><br /> v_Int := Substr(v_Numstr, 1, v_Pt - 1);<br /> v_Ln1 := Length(v_Int); -- >= 1<br /><br /> v_Frc := Substr(v_Numstr, v_Pt + 1);<br /> v_Ln2 := Length(v_Frc); -- >= 1<br /><br /> IF v_Dig > v_Ln2 THEN<br /> -- no rounding needed; padding of zero's<br /> v_Frc := Rpad(v_Frc, v_Dig, '0');<br /> ELSIF v_Dig = v_Ln2 THEN<br /> -- no action needed<br /> NULL;<br /> ELSE<br /> -- do the actual rounding <br /> v_Frc1 := Substr(v_Frc, 1, v_Dig);<br /> v_Frc2 := Substr(v_Frc, v_Dig + 1);<br /> v_Num := To_Number(v_Int || v_Frc1);<br /><br /> IF To_Number(Ltrim(v_Frc2)) >= Power(10, v_Ln2 - v_Dig) / 2 THEN<br /> v_Num := v_Num + 1; -- an integer<br /> v_Numstr := To_Char(v_Num);<br /> v_Len := Length(v_Numstr);<br /> v_Int := Nvl(Substr(v_Numstr, 1, v_Len - v_Dig),'0'); -- evt,v_len < v_dig!!<br /> v_Frc := Substr(v_Numstr, v_Len - v_Dig + 1);<br /> ELSE<br /> v_Frc := v_Frc1;<br /> END IF;<br /> END IF;<br /><br /> -- test for fractional parts that became void by rounding<br /> IF v_Frc IS NULL THEN<br /> v_Numstr := v_sgn||v_Int;<br /> ELSE<br /> v_Numstr := v_sgn||v_Int ||'.'|| v_Frc;<br /> END IF;<br /><br /> RETURN(v_Numstr);<br />END;
This function is wrong for integers of x decimals which are rounded to less than x decimals, e.g. 12345 when rounded to 3 significant decimals is 12300 but should be displayed as 123×100 or whichever fancy notation other than the usual scientific notation 123E+02 one has in mind.