Mercurial > octave
changeset 33481:4d037eddd28f
rat.m: Fix various corner cases and make output compatible with eval (bug #55198)
Various corner cases with Inf or NaN inputs were failing with the previous
code. E.g. `[n, d] = rat (complex (0, inf))` would give `n = Nan - Nani` and
`d = 0`. Passing complex inputs with NaN would give an error about tolerance
instead, though real inputs with NaN were handled properly. This patch fixes
those various corner cases.
Further, the string output for the above corner cases were failing to give back
the original input when passed to `eval`, due to e.g. `0 + (Inf)*i` giving back
`NaN + Infi`. (This is bug #31974, closed as Won't Fix).
The output format of `rat()` for complex inputs has therefore been changed
to return `complex (a, b)` instead of `(a) + (b) * i`. This new output string
can be safely passed to `eval` in all cases.
* rat.m: Fix various complex corner cases with Inf or NaN in them.
Cosmetic change to return `complex (a, b)` instead of `(a) + (b) * i`, for
compatibility with `eval`.
Add and update BISTs for the above changes. Update documentation.
author | Arun Giridhar <arungiridhar@gmail.com> |
---|---|
date | Mon, 29 Apr 2024 10:08:44 -0400 |
parents | 29282fcb0f2a |
children | 787937817a64 31ced69982f1 |
files | scripts/general/rat.m |
diffstat | 1 files changed, 82 insertions(+), 25 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/general/rat.m Sun Apr 28 21:27:05 2024 -0700 +++ b/scripts/general/rat.m Mon Apr 29 10:08:44 2024 -0400 @@ -61,7 +61,7 @@ ## @example ## @group ## s = rat (0.5 + i * pi) -## @result{} s = (1 + 1/(-2)) + (3 + 1/(7 + 1/16)) * i +## @result{} s = complex (1 + 1/(-2), 3 + 1/(7 + 1/16)) ## ## [n, d] = rat (0.5 + i * pi) ## @result{} n = 113 + 710i @@ -72,9 +72,14 @@ ## @end group ## @end example ## -## 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}. +## Programming Notes: +## +## 1. 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}. +## +## 2. The string output produced by @code{rat} can be passed to @code{eval} +## to get back the original input up to the tolerance used. ## ## @seealso{rats, format} ## @end deftypefn @@ -89,6 +94,43 @@ error ("rat: X must be a single or double array"); endif + if (iscomplex (x)) + if (nargout == 2) # return numerator and denominator + if (nargin == 2) + [nr, dr] = rat (real (x), tol); + [ni, di] = rat (imag (x), tol); + else + [nr, dr] = rat (real (x)); + [ni, di] = rat (imag (x)); + endif + + ## For inputs with inf, the output is set to 1/0 or -1/0. + ## Override that to +inf/1 or -inf/1. + ii = (dr == 0 & nr > 0); dr(ii) = 1; nr(ii) = +inf; + ii = (dr == 0 & nr < 0); dr(ii) = 1; nr(ii) = -inf; + ii = (di == 0 & ni > 0); di(ii) = 1; ni(ii) = +inf; + ii = (di == 0 & ni < 0); di(ii) = 1; ni(ii) = -inf; + + d = lcm (dr, di); # now this should always be nonzero + n = complex (nr .* (d ./ dr), ni .* (d ./ di)); + elseif (nargout <= 1) # string output + if (nargin == 2) + realstr = rat (real (x), tol); + imagstr = rat (imag (x), tol); + else + realstr = rat (real (x)); + imagstr = rat (imag (x)); + endif + + nn = rows (realstr); + start = repmat ("complex (", nn, 1); + mid = repmat (", ", nn, 1); + finish = repmat (")", nn, 1); + n = [start, realstr, mid, imagstr, finish]; + endif + return + endif + y = x(:); ## Replace Inf with 0 while calculating ratios. @@ -104,20 +146,6 @@ endif endif - if (iscomplex (x)) - if (nargout == 2) # return numerator and denominator - [nr, dr] = rat (real (x), tol); - [ni, di] = rat (imag (x), tol); - d = lcm (dr, di); - n = nr .* (d ./ dr) + ni .* (d ./ di) * i; - elseif (nargout <= 1) # string output - realstr = rat (real (x), tol); - imagstr = rat (imag (x), tol); - n = [repmat("(", rows (realstr), 1), realstr, repmat(") + (", rows (realstr), 1), imagstr, repmat(") * i", rows (imagstr), 1)]; - endif - return - endif - ## First step in the approximation is the integer portion ## First element in the continued fraction. @@ -246,7 +274,7 @@ ## Test complex scalar input %!test <*55198> -%! assert (rat (complex (0.5, pi)), "(1 + 1/(-2)) + (3 + 1/(7 + 1/16)) * i"); +%! assert (rat (complex (0.5, pi)), "complex (1 + 1/(-2), 3 + 1/(7 + 1/16))"); %! [n, d] = rat (complex (0.5, pi)); %! assert (n, 113 + 710*i); %! assert (d, 226); @@ -260,21 +288,50 @@ %! assert (d, [887313, 49387, 49387, 887313]); %! assert (all (abs (n ./ d - x) <= 2e-6)); %! str = rat (x); -%! assert (str(1, :), "(0 + 1/(3 + 1/(4 + 1/(4 + 1/(4 + 1/4))))) + (1 + 1/(-20 + 1/(-2 + 1/(-3 + 1/(-6))))) * i"); -%! assert (str(2, :), "(-1 + 1/(5 + 1/(4 + 1/(4 + 1/4))) ) + (1 + 1/(-2 + 1/(-2 + 1/(-3 + 1/8))) ) * i"); -%! assert (str(3, :), "(-1 + 1/(5 + 1/(4 + 1/(4 + 1/4))) ) + (-1 + 1/(2 + 1/(2 + 1/(3 + 1/(-8)))) ) * i"); -%! assert (str(4, :), "(0 + 1/(3 + 1/(4 + 1/(4 + 1/(4 + 1/4))))) + (-1 + 1/(20 + 1/(2 + 1/(3 + 1/6))) ) * i"); +%! assert (str(1, :), "complex (0 + 1/(3 + 1/(4 + 1/(4 + 1/(4 + 1/4)))), 1 + 1/(-20 + 1/(-2 + 1/(-3 + 1/(-6)))))"); +%! assert (str(2, :), "complex (-1 + 1/(5 + 1/(4 + 1/(4 + 1/4))) , 1 + 1/(-2 + 1/(-2 + 1/(-3 + 1/8))) )"); +%! assert (str(3, :), "complex (-1 + 1/(5 + 1/(4 + 1/(4 + 1/4))) , -1 + 1/(2 + 1/(2 + 1/(3 + 1/(-8)))) )"); +%! assert (str(4, :), "complex (0 + 1/(3 + 1/(4 + 1/(4 + 1/(4 + 1/4)))), -1 + 1/(20 + 1/(2 + 1/(3 + 1/6))) )"); ## Test complex exceptional inputs %!test <*55198> -%! assert (rat (complex (inf, 0)), "(Inf) + (0) * i"); -%! assert (rat (complex (0, inf)), "(0) + (Inf) * i"); +%! assert (rat (complex (inf, 0)), "complex (Inf, 0)"); +%! assert (rat (complex (0, inf)), "complex (0, Inf)"); +%! assert (rat (complex (-inf, 0)), "complex (-Inf, 0)"); +%! assert (rat (complex (0, -inf)), "complex (0, -Inf)"); +%! assert (rat (complex (nan, 0)), "complex (NaN , 0)"); +%! assert (rat (complex (0, nan)), "complex (0, NaN )"); + +%!test <*55198> +%! [n, d] = rat (complex (inf, 0)); +%! assert (n, complex (inf, 0)); +%! assert (d, 1); +%! [n, d] = rat (complex (0, inf)); +%! assert (n, complex (0, inf)); +%! assert (d, 1); +%! [n, d] = rat (complex (-inf, 0)); +%! assert (n, complex (-inf, 0)); +%! assert (d, 1); +%! [n, d] = rat (complex (0, -inf)); +%! assert (n, complex (0, -inf)); +%! assert (d, 1); +%! [n, d] = rat (complex (nan, 0)); +%! assert (n, complex (nan, 0)); +%! assert (d, 1); +%! [n, d] = rat (complex (0, nan)); +%! assert (n, complex (0, nan)); +%! assert (d, 1); ## Test eval with complex inputs %!test <*55198> %! x = complex (0.5, pi); %! assert (eval (rat (x)), x, 1e-6 * norm (x, 1)) +## Test eval with inf*i +%!test <*55198> +%! x = complex (0, inf); +%! assert (eval (rat (x)), x, 1e-6 * norm (x, 1)) + %!assert <*43374> (eval (rat (0.75)), [0.75]) ## Test input validation