%mathpiper,def="NearRational"

//Retract("NearRational",*);

//////////////////////////////////////////////////
/// NearRational, GuessRational
//////////////////////////////////////////////////

/// find rational number with smallest num./denom. near a given number x
/// See: HAKMEM, MIT AI Memo 239, 02/29/1972, Item 101C

10 # NearRational(_x) <-- NearRational(x, Floor(1/2*BuiltinPrecisionGet()));

15 # NearRational(x_IsRationalOrNumber, prec_IsInteger) <-- 
[
	Local(x1, x2, i,  old'prec);
	old'prec := BuiltinPrecisionGet();
    BuiltinPrecisionSet(prec + 8);	// 8 guard digits (?)
	x1 := ContFracList(N(Eval(x+10^(-prec))));
	x2 := ContFracList(N(Eval(x-10^(-prec))));

    /*
    If(InVerboseMode(),
      [
         Echo("NearRational: x      = ", N(Eval(x           ))));
         Echo("NearRational: xplus  = ", N(Eval(x+10^(-prec)))));
         Echo("NearRational: xmin   = ", N(Eval(x-10^(-prec)))));
         Echo("NearRational: Length(x1) = ", Length(x1)," ",x1));
         Echo("NearRational: Length(x2) = ", Length(x2)," ",x1));
      ]
    );
    */
    
	// find where the continued fractions for "x1" and "x2" differ
	// prepare result in "x1" and length of result in "i"
	For (i:=1, i<=Length(x1) And i<=Length(x2) And x1[i]=x2[i], i++ ) True;
	If(
		i>Length(x1),
		// "x1" ended but matched, so use "x2" as "x1"
		x1:=x2,
		If(
			i>Length(x2),
		// "x2" ended but matched, so use "x1"
			True,
		// neither "x1" nor "x2" ended and there is a mismatch at "i"
		// apply recipe: select the smalest of the differing terms
			x1[i]:=Minimum(x1[i],x2[i])
		)
	);
	// recipe: x1dd 1 to the lx1st term unless it's the lx1st in the originx1l sequence
	//Ayal added this line, i could become bigger than Length(x1)!
	//If(InVerboseMode(), Echo({"NearRational: using ", i, "terms of the continued fraction"}));
	If(i>Length(x1),i:=Length(x1));
	x1[i] := x1[i] + If(i=Length(x1), 0, 1);
	BuiltinPrecisionSet(old'prec);
	ContFracEval(Take(x1, i));
];


20 # NearRational(_z, prec_IsInteger)_
      (And(Im(z)!=0,IsRationalOrNumber(Im(z)),IsRationalOrNumber(Re(z)))) <--
[
    Local(rr,ii);
    rr := Re(z);
    ii := Im(z);
    Complex( NearRational(rr,prec), NearRational(ii,prec) );
];

%/mathpiper

    %output,preserve="false"
      Result: True
.   %/output



%mathpiper_docs,name="NearRational",categories="User Functions;Numbers (Operations)"
*CMD NearRational --- find optimal rational approximations
*STD
*CALL
	NearRational(x)
	NearRational(x, digits)
    NearRational(z)
    NearRational(z, digits)

*PARMS

{x} -- a number to be approximated (must be already evaluated to floating-point)
{z} -- a complex number to be approximated (Re and Im as above)

{digits} -- desired number of decimal digits (integer)

*DESC

The functions {GuessRational(x)} and {NearRational(x)} attempt to find "optimal"
rational approximations to a given value {x}. The approximations are "optimal"
in the sense of having smallest numerators and denominators among all rational
numbers close to {x}. This is done by computing a continued fraction
representation of {x} and truncating it at a suitably chosen term.  Both
functions return a rational number which is an approximation of {x}.

Unlike the function {Rationalize()} which converts floating-point numbers to
rationals without loss of precision, the functions {GuessRational()} and
{NearRational()} are intended to find the best rational that is <i>approximately</i>
equal to a given value.

The function {NearRational(x)} is useful if one needs to
approximate a given value, i.e. to find an "optimal" rational number
that lies in a certain small interval around a certain value {x}. This
function takes an optional second parameter {digits} which has slightly
different meaning: it specifies the number of digits of precision of
the approximation; in other words, the difference between {x} and the
resulting rational number should be at most one digit of that
precision. The parameter {digits} also defaults to half of the current
precision.

*E.G.

Start with a rational number and obtain a floating-point approximation:
In> x:=N(956/1013)
Result: 0.9437314906
In> Rationalize(x)
Result: 4718657453/5000000000;
The first 10 terms of this continued fraction correspond to the correct continued fraction for the original rational number.
In> NearRational(x)
Result: 218/231;
This function found a different rational number closeby because the precision was not high enough.
In> NearRational(x, 10)
Result: 956/1013;

*SEE BracketRational, GuessRational, ContFrac, ContFracList, Rationalize
%/mathpiper_docs

    %output,preserve="false"
      
.   %/output


