Mercurial > octave
changeset 26176:b5418132423f
rat.m: Overhaul function.
* rat.m: Add new copyright holder. Rewrite docstring. Don't validate nargout.
Add input validation tests that input is a floating point array. Validate
tolerance is a numeric scalar greater than 0. Change code to detect extreme
values 0 and Inf. Remove unused variable nd. Add more BIST tests for function
and for input validation.
author | Rik <rik@octave.org> |
---|---|
date | Thu, 06 Dec 2018 21:47:35 -0800 |
parents | 6e1a800dd365 |
children | f2d795f07c84 |
files | scripts/general/rat.m |
diffstat | 1 files changed, 122 insertions(+), 68 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/general/rat.m Thu Dec 06 23:16:16 2018 -0500 +++ b/scripts/general/rat.m Thu Dec 06 21:47:35 2018 -0800 @@ -1,3 +1,4 @@ +## Copyright (C) 2018 Rik Wehbring ## Copyright (C) 2001-2018 Paul Kienzle ## ## This file is part of Octave. @@ -17,41 +18,68 @@ ## <https://www.gnu.org/licenses/>. ## -*- texinfo -*- -## @deftypefn {} {@var{s} =} rat (@var{x}, @var{tol}) -## @deftypefnx {} {[@var{n}, @var{d}] =} rat (@var{x}, @var{tol}) +## @deftypefn {} {@var{s} =} rat (@var{x}) +## @deftypefnx {} {@var{s} =} rat (@var{x}, @var{tol}) +## @deftypefnx {} {[@var{n}, @var{d}] =} rat (@dots{}) +## +## Find a rational approximation of @var{x} to within the tolerance defined by +## @var{tol}. ## -## Find a rational approximation to @var{x} within the tolerance defined by -## @var{tol} using a continued fraction expansion. +## If unspecified, the default tolerance is @code{1e-6 * norm (@var{x}(:), 1)}. +## +## When called with one output argument, return a string containing a +## continued fraction expansion (multiple terms). +## +## When called with two output arguments, return numeric matrices for the +## numerator and denominator of a fractional representation of @var{x} such +## that @code{@var{x} = @var{n} ./ @var{d}}. ## ## For example: ## ## @example ## @group -## rat (pi) = 3 + 1/(7 + 1/16) = 355/113 -## rat (e) = 3 + 1/(-4 + 1/(2 + 1/(5 + 1/(-2 + 1/(-7))))) -## = 1457/536 +## @var{s} = rat (pi) +## @result{} s = 3 + 1/(7 + 1/16) +## +## [@var{n}, @var{d}] = rat (pi) +## @result{} @var{n} = 355 +## @result{} @var{d} = 113 +## +## @var{n}/@var{d} - pi +## @result{} 0.00000026676 ## @end group ## @end example ## -## When called with two output arguments return the numerator and denominator -## separately as two matrices. -## @seealso{rats} +## Programming Note: With one output @code{rat} produces a string which is a +## continued fraction expansion. To produce a string which is a simple +## fraction (one numerator, one denominator) use @code{rats}. +## +## @seealso{rats, format} ## @end deftypefn -function [n,d] = rat (x,tol) +function [n, d] = rat (x, tol) - if (nargin != [1,2] || nargout > 2) + if (nargin < 1 || nargin > 2) print_usage (); endif + if (! isfloat (x)) + error ("rat: X must be a single or double array"); + endif + y = x(:); ## Replace Inf with 0 while calculating ratios. - y(isinf(y)) = 0; + inf_idx = isinf (x); + y(inf_idx(:)) = 0; - ## default norm - if (nargin < 2) - tol = 1e-6 * norm (y,1); + if (nargin == 1) + ## default norm + tol = 1e-6 * norm (y, 1); + else + if (! (isscalar (tol) && isnumeric (tol) && tol > 0)) + error ("rat: TOL must be a numeric scalar > 0"); + endif endif ## First step in the approximation is the integer portion @@ -59,18 +87,17 @@ ## First element in the continued fraction. n = round (y); d = ones (size (y)); - frac = y-n; + frac = y - n; lastn = ones (size (y)); lastd = zeros (size (y)); - nd = ndims (y); nsz = numel (y); steps = zeros ([nsz, 0]); ## Grab new factors until all continued fractions converge. while (1) ## Determine which fractions have not yet converged. - idx = find (abs (y-n./d) >= tol); + idx = find (y != 0 & abs (y - n./d) >= tol); if (isempty (idx)) if (isempty (steps)) steps = NaN (nsz, 1); @@ -79,87 +106,114 @@ endif ## Grab the next step in the continued fraction. - flip = 1./frac(idx); + flip = 1 ./ frac(idx); ## Next element in the continued fraction. step = round (flip); if (nargout < 2) tsteps = NaN (nsz, 1); - tsteps (idx) = step; + tsteps(idx) = step; steps = [steps, tsteps]; endif - frac(idx) = flip-step; + frac(idx) = flip - step; ## Update the numerator/denominator. - nextn = n; - nextd = d; + savedn = n; + savedd = d; n(idx) = n(idx).*step + lastn(idx); d(idx) = d(idx).*step + lastd(idx); - lastn = nextn; - lastd = nextd; + lastn = savedn; + lastd = savedd; endwhile - if (nargout == 2) - ## Move the minus sign to the top. + if (nargout <= 1) + ## string output + n = ""; + nsteps = columns (steps); + ## Loop over all values in array + for i = 1:nsz + + if (inf_idx(i)) + s = ifelse (x(i) > 0, "Inf", "-Inf"); + elseif (y(i) == 0) + s = "0"; + else + ## Create partial fraction expansion of one value + s = [int2str(y(i)), " "]; + j = 1; + + while (true) + step = steps(i, j++); + if (isnan (step)) + break; + endif + if (j > nsteps || isnan (steps(i, j))) + if (step < 0) + s = [s(1:end-1), " + 1/(", int2str(step), ")"]; + else + s = [s(1:end-1), " + 1/", int2str(step)]; + endif + break; + else + s = [s(1:end-1), " + 1/(", int2str(step), ")"]; + endif + endwhile + s = [s, repmat(")", 1, j-2)]; + endif + + ## Append result to output + n_nc = columns (n); + s_nc = columns (s); + if (n_nc > s_nc) + s(:, s_nc+1:n_nc) = " "; + elseif (s_nc > n_nc && n_nc != 0) + n(:, n_nc+1:s_nc) = " "; + endif + n = cat (1, n, s); + endfor + else + ## numerator, denominator output + + ## Move the minus sign to the numerator. n .*= sign (d); d = abs (d); - ## Return the same shape as you receive. + ## Return the same shape as the input. n = reshape (n, size (x)); d = reshape (d, size (x)); ## Use 1/0 for Inf. - n(isinf (x)) = sign (x(isinf (x))); - d(isinf (x)) = 0; - - ## Reshape the output. - n = reshape (n, size (x)); - d = reshape (d, size (x)); - else - n = ""; - nsteps = columns (steps); - for i = 1: nsz - s = [int2str(y(i))," "]; - j = 1; - - while (true) - step = steps(i, j++); - if (isnan (step)) - break; - endif - if (j > nsteps || isnan (steps(i, j))) - if (step < 0) - s = [s(1:end-1), " + 1/(", int2str(step), ")"]; - else - s = [s(1:end-1), " + 1/", int2str(step)]; - endif - break; - else - s = [s(1:end-1), " + 1/(", int2str(step), ")"]; - endif - endwhile - s = [s, repmat(")", 1, j-2)]; - n_nc = columns (n); - s_nc = columns (s); - if (n_nc > s_nc) - s(:,s_nc+1:n_nc) = " "; - elseif (s_nc > n_nc && n_nc != 0) - n(:,n_nc+1:s_nc) = " "; - endif - n = cat (1, n, s); - endfor + n(inf_idx) = sign (x(inf_idx)); + d(inf_idx) = 0; endif endfunction +%!assert (rat (pi), "3 + 1/(7 + 1/16)") +%!assert (rat (pi, 1e-2), "3 + 1/7") +## Test exceptional values +%!assert (rat (0), "0") +%!assert (rat (Inf), "Inf") +%!assert (rat (-Inf), "-Inf") + %!test %! [n, d] = rat ([0.5, 0.3, 1/3]); %! assert (n, [1, 3, 1]); %! assert (d, [2, 10, 3]); +## Test exceptional values +%!test +%! [n, d] = rat ([Inf, 0, -Inf]); +%! assert (n, [1, 0, -1]); +%! assert (d, [0, 1, 0]); %!assert <*43374> (eval (rat (0.75)), [0.75]) +## Test input validation %!error rat () %!error rat (1, 2, 3) +%!error <X must be a single or double array> rat (int8 (3)) +%!error <TOL must be a numeric scalar> rat (1, "a") +%!error <TOL must be a numeric scalar> rat (1, [1 2]) +%!error <TOL must be a numeric scalar . 0> rat (1, -1)