Sindbad~EG File Manager
/* menu operators for fractions, compound fractions, common denominators menus.
M. Beeson, for MathXpert
12.4.90 Original date
3.30.99 last modified
3.28.00 corrected commondenomandsimp2
8.27.04 modified polydivop
1.23.06 added SUM to definition of OMITS
*/
#define ALGEBRA_DLL
#include <string.h>
#include <assert.h>
#include <math.h>
#include "globals.h"
#include "ops.h"
#include "order.h"
#include "simpsums.h"
#include "simpprod.h"
#include "factor.h"
#include "cancel.h"
#include "algaux.h"
#include "polynoms.h"
#include "checkarg.h"
#include "ops.h"
#include "fraction.h"
#include "eqn.h"
#include "prover.h"
#include "symbols.h"
#include "deval.h" /*nearint */
#include "mpmem.h" /* save_and_reset*/
#include "errbuf.h"
#include "mstring.h"
#include "mathmode.h" /* get_problemtype */
#include "probtype.h"
#include "autosimp.h" /* set_pathtail */
#include "surdsimp.h" /* canonical */
#include "pvalaux.h"
static int m_aux(term, term, term *, char *, int);
static int contains_fraction(term);
static int local_polyval2(term t, term *ans);
static int multiplyfractions_aux(term t, term *next, char *reason);
/*______________________________________________________________*/
static actualop cancelops[] =
{ cancelop,
cancelgcd,
cancelsqrt,
cancelsqrt2,
cancelsqrt3,
cancelroot,
cancelroot2,
cancelgcd,
cancelsqrtgcd,
cancelrootgcd,
cancelabsgcd,
polydivop,
cancelbypolydiv,
powerstonum,
powerstodenom
};
#define NCANCELOPS (sizeof(cancelops)/sizeof(actualop))
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int zeronum(term in, term arg, term *next, char *reason)
/* 0/a = 0 */
{ int k,j;
char temp[128];
term denom;
if(FUNCTOR(in) != '/' )
return 1; /* inapplicable */
if(!ISZERO(ARG(0,in)))
return 1;
denom = ARG(1,in);
j = contains(denom,LIMIT);
k = contains(denom,INTEGRAL);
if(j || k)
strcpy(temp, english(280)); /* You must first evaluate the */
if(j)
{ strcat(temp, english(281)); /* limit in the denominator. */
errbuf(0,temp);
return 1;
}
if(k)
{ strcat(temp, english(282)); /* integral in the denominator. */
errbuf(0,temp);
return 1;
}
*next = zero;
strcpy(reason,"0/a = 0");
HIGHLIGHT(*next);
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int unitdenom(term in, term arg, term *next, char *reason)
{ if(FUNCTOR(in) != '/' )
return 1; /* inapplicable */
if(ISONE(ARG(1,in)))
{ *next = ARG(0,in);
strcpy(reason,"a/1 = a");
HIGHLIGHT(*next);
return 0;
}
return 1;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int recip(term t, term arg, term *ans, char *reason)
/* a (1/a) = 1 */
{ int i,j,k,flag,p,err;
term a;
unsigned short n = ARITY(t);
if(FUNCTOR(t) != '*' )
return 1; /* inapplicable */
for(i=0;i<n;i++)
{ if(FUNCTOR(ARG(i,t)) != '/')
continue;
if(!ISONE(ARG(0,ARG(i,t))))
continue;
/*Now we've found a factor of 1/a */
a = ARG(1,ARG(i,t));
if(ZERO(a))
continue; /* This can happen! zero denoms are created in infinite limit problems */
if(SOME_INFINITESIMAL(ARG(i,t)))
continue; /* The denom will eventually evaluate to zero */
strcpy(reason,"a(1/a) = 1");
for(j=0;j<n;j++)
{ if(equals(ARG(j,t),a)) /* found factor of a */
{ if(get_polyvaldomainflag())
{ err = infer(nonzero(a));
if(err)
break;
}
if(n==2)
{ *ans = one;
HIGHLIGHT(*ans);
return 0;
}
if(n==3 && status(multbyone)>=KNOWN)
/* return the remaining factor */
{ for(k=0;k<3;k++)
if(i!=k && j!=k)
{ *ans = ARG(k,t);
HIGHLIGHT(*ans);
return 0;
}
}
if(n==3) /* return 1 * remaining factor */
{ for(k=0;k<3;k++)
if(i!=k && j!=k)
{ if(k==0)
{ mt(ARG(0,t),two,ans); /* 'one' in place of 'two' will be dropped */
ARGREP(*ans,ARITY(*ans)-1,one); /* put 1 where 2 was */
}
else
{ mt(two,ARG(k,t),ans);
ARGREP(*ans,0,one); /* see above */
}
HIGHLIGHT(*ans);
return 0;
}
}
/* now n > 3 so we have to return a product */
flag = status(multbyone);
if(flag >= KNOWN)
*ans = make_term('*',(unsigned short)(n-2));
else
*ans = make_term('*',(unsigned short)(n-1));
p=0; /* place the next arg at p-th arg of *ans */
for(k=0;k<n;k++)
{ if(i!=k && j!=k)
{ ARGREP(*ans,p,ARG(k,t));
++p;
}
else if ( flag < KNOWN &&
((i==k && i<j) || (j==k && j<i))
)
{ ARGREP(*ans,p,one);
HIGHLIGHT(ARG(p,*ans));
++p;
}
}
if(i>j)
strcpy(reason, "a(1/a) = 1");
else
strcpy(reason, "(1/a)a = 1");
if(flag >= KNOWN)
HIGHLIGHT(*ans);
return 0;
}
}
if(j < n)
continue; /* there is a 0...(1/0) pair, but there might
be ANOTHER pair to which it legally applies. */
}
return 1; /* not applicable */
}
/*____________________________________________________________________*/
#define OMIT(f,u) ((f) == DIFF || (f) == INTEGRAL || (f) == LIMIT || ((f) == '+' && (contains(u,DIFF) || contains(u,INTEGRAL) || contains(u,LIMIT) || contains(u,SUM) || contains(u,'/'))))
static int multiplyfractions_aux(term t, term *next, char *reason)
/* multiply (a/b)(c/d) = ac/(bd) or a(b/c) = ab/c, even in products
with many factors; do all fractions at once if more than two to multiply */
/* There are certain terms which should not be multiplied
into fractions: limits, derivatives, integrals, sums containing
fractions, limits, derivs, or integrals, and complexi.
This will be done only
if (1) we're in menu mode and (2) otherwise there's nothing to do.
Returns 0 for success, 1 for no result. In the latter
case *next = t rather than garbage, if t is a product.
*/
/* If any of the fractions is marked as having a denom that will eventually
evaluate to zero, this function returns 1, and the fractions will
not be multiplied. */
/* This is NOT the operator, which has some more subtleties. See below. */
{ term temp,sofar;
int flag=0; /* how many fractions there are in t */
int omitflag = 0; /* use to see if there are limits or integrals or derivatives in the product */
int zerodenomflag = 0;
unsigned short n = ARITY(t);
term u,v,w;
unsigned short f;
int i,err;
if(FUNCTOR(t) != '*')
return 1; /* inapplicable */
if(n==2 && ISATOM(ARG(1,t)) &&
equals(ARG(1,t),complexi) &&
!contains(ARG(0,t),'i')
)
{ *next = t;
return 1; /* leave (sqrt(3)/2) i alone for example */
}
if(n==2 && ISATOM(ARG(0,t)) &&
equals(ARG(0,t),complexi) &&
!contains(ARG(1,t),'i')
)
{ *next = t;
return 1; /* leave i (sqrt(3)/2) alone */
}
for(i=0;i<n;i++)
{ w = ARG(i,t);
f = FUNCTOR(w);
if(f == '/')
{ ++flag;
if(SOME_INFINITESIMAL(w))
++zerodenomflag;
}
if(OMIT(f,w))
++omitflag;
if(ARITY(w)==1 && contains(w,'/'))
++omitflag; /* example, 1/�3 arctan(x/�3) */
/* don't multiply this out */
}
if(flag==0 || zerodenomflag)
{ *next = t;
return 1; /* no fractions in t */
}
if(flag==1 && omitflag == n-1 && get_mathmode() == AUTOMODE)
{ *next = t;
return 1; /* one fraction and some terms to omit, leave alone */
}
if(omitflag && omitflag != n-1) /* don't multiply in the terms to be omitted */
{ /* put the limits and integrals in v, the other terms in u */
unsigned short j,k;
u = make_term('*',n);
v = make_term('*',n);
j=k=0;
for(i=0;i<n;i++)
{ w = ARG(i,t);
f = FUNCTOR(w);
if(OMIT(f,w) ||
(ARITY(w)==1 && contains(w,'/'))
)
{ ARGREP(v,k,w);
++k;
}
else
{ ARGREP(u,j,w);
++j;
}
}
assert(j>1);
SETFUNCTOR(u,'*',j);
assert(k>0);
if(k==1)
{ temp = ARG(0,v);
RELEASE(v);
v = temp;
}
else
SETFUNCTOR(v,'*',k);
err = multiplyfractions_aux(u,&temp,reason);
if(err)
return 1;
HIGHLIGHT(temp);
*next = product(temp,v);
return 0;
}
if(n==2)
{ mfracts(ARG(0,t),ARG(1,t),next);
if(equals(t,*next))
return 1;
if(FUNCTOR(ARG(0,t))=='/' && FUNCTOR(ARG(1,t))=='/')
strcpy(reason,"$$(a/b)(c/d) = ac/(bd)$$");
else if(FUNCTOR(ARG(0,t))=='/')
strcpy(reason,"$$a(b/c) = ab/c$$");
else if(FUNCTOR(ARG(1,t))=='/')
strcpy(reason,"$$(a/b)c = ac/b$$");
goto out;
}
/* Now t is a product of 3 or more factors */
temp = ARG(0,t);
if(FUNCTOR(temp)=='/')
flag =1;
else
flag=0;
for(i=1;i<n;i++)
{ mfracts(temp,ARG(i,t),&sofar);
if(FUNCTOR(ARG(i,t))=='/')
++flag;
temp = sofar;
}
*next = sofar;
if(flag >=2)
strcpy(reason,"$$(a/b)(c/d) = ac/(bd)$$");
else if(FUNCTOR(ARG(0,t))=='/')
strcpy(reason,"$$(a/b)c = ac/b$$");
else
strcpy(reason,"a(b/c) = ab/c");
if(equals(t,*next))
return 1; /* can happen if MATRIX terms are involved */
out:
if(status(collectpowers) > LEARNING &&
status(multiplyfractions) > LEARNING &&
FUNCTOR(*next) == '/' &&
get_arithflag().intexp /* on numerical problems, for example leave 10*10 alone,
don't make it 10^2, or else it screws up common denom problems with 100 for
a common denom and 1/10 for one of the fractions. */
)
/* then collect powers in num and denom */
{ for(i=0;i<2;i++)
{ temp = ARG(i,*next);
if(FUNCTOR(temp) == '*')
{ err = rawcollectpowers(temp,&sofar,1);
if(!err)
ARGREP(*next,i,sofar);
}
}
}
HIGHLIGHT(*next);
return 0;
}
/*_________________________________________________________________________*/
MEXPORT_ALGEBRA int multiplyfractions(term t, term arg, term *next, char *reason)
/* This IS the operator. In automode it is controlled by autoproduct,
which uses it under complicated conditions which hopefully get
the following examples right without having the operator
itself behave differently in automode or menumode:
Examples (assuming !comdenomflag && !infractionflag && (ringflag & RATRING))
it won't be called on (1/2)x^2 alone in auto mode
or on (4/3) �r^2,
but if called (in menu mode) it first gives (4�/3)r^2, then 4�r^2/3.
If given (1/3)(x/3) it should get (1/9) x.
If given (2/3)(x/y) it should get 2x/(3y)
If given 1/(2x) 1/(2y) it should get 1/(4xy), not (1/4) (1/xy)
If given (1/c) a it will get a/c; if this is not desired it is up to
autoproduct not to call this operator (e.g. in (1/2) x )
However: it's difficult to avoid loops between this operator and
pulloutrational. Do we want 3x/5 or (3/5)x ? This depends on
(ringflag & RATRING); but this operator isn't context-sensitive, so
will be used to multiply out all fractions in the current line at once;
therefore if you DON'T want to multiply out (3/5)x, the operator itself
had better prevent that. We settle this by having TWO operators,
multiplyfractions and multiplyfractions2; they agree unless
(ringflag & RATRING), but in that case only the latter proceeds with
(3/5)x, and is called only in menu mode.
*/
{ return m_aux(t,arg,next,reason,1);
}
/*__________________________________________________________________*/
MEXPORT_ALGEBRA int multiplyfractions2(term t, term arg, term *next, char *reason)
/* do proceed with (3/5)x, even if (ringflag & RATRING)
This is called by polyval as well as in menu mode. */
{ int err,ringflag;
if(FUNCTOR(t) == '*' &&
ARITY(t) == 2 &&
equals(ARG(0,t),complexi) &&
FRACTION(ARG(1,t)) && get_mathmode() != AUTOMODE
)
/* i(a/b)= (ia)/b
this case has to be handled separately as m_aux doesn't do it,
and shouldn't be done in automode or it throws polyval into a loop.
*/
{ *next = make_fraction(product(ARG(0,t),ARG(0,ARG(1,t))),ARG(1,ARG(1,t)));
strcpy(reason,"a(b/c) = ab/c");
return 0;
}
if(FUNCTOR(t) == '*' &&
ARITY(t) == 2 &&
equals(ARG(1,t),complexi) &&
FRACTION(ARG(0,t)) && get_mathmode() != AUTOMODE
)
{ *next = make_fraction(product(ARG(0,ARG(0,t)),ARG(1,t)),ARG(1,ARG(0,t)));
strcpy(reason,"a(b/c) = ab/c");
return 0;
}
ringflag = get_ringflag();
set_ringflag(INTRING); /* without this, it won't multiply rationals in */
err = m_aux(t,arg,next,reason,0);
set_ringflag(ringflag);
return err;
}
/*__________________________________________________________________*/
static int m_aux(term t, term arg, term *next, char *reason, int stopflag)
/* Do the work of multiplyfractions, using 'stopflag' to determine
whether to proceed with (3/5)x etc; but don't proceed anyway unless
(ringflag & RATRING). If stopflag is nonzero, block such multiplications.
Return 0 for success, 1 for failure.
In automode, fail if one of the products is not a fraction, but contains
fractions polynomially, in order to avoid creating compound fractions. */
{ term n,c,s,temp,temp2,temp3,val;
int count,i,err;
unsigned short k=ARITY(t);
term a,b,p,q;
if(FUNCTOR(t) != '*')
return 1;
count = 0; /* count the fractions */
for(i=0;i<k;i++)
{ if(FRACTION(ARG(i,t)))
++count;
}
if(get_mathmode() == AUTOMODE) /* avoid creating compound fractions */
{ for(i=0;i<k;i++)
{ temp = ARG(i,t);
if(FUNCTOR(temp) == '^')
{ if(FRACTION(ARG(0,temp)))
return 1;
temp = ARG(0,temp);
}
if(FRACTION(temp))
{ if(SOME_INFINITESIMAL(temp))
{ if(contains(ARG(1,temp),LIMIT))
{ errbuf(0, english(1117));
/* First evaluate the limit in the denominator. */
return 1;
}
return 1; /* refuse to multiply fractions with zero denom */
}
continue;
}
if(contains_monomially(temp,'/'))
return 1;
}
}
if(get_ringflag() & RATRING)
{ ratpart2(t,&a,&b);
/* trap the case (1/2) sqrt(3) sin x and make it sqrt(3)/2 sin x */
if(RATIONALP(a) &&
FUNCTOR(b) == '*' && FUNCTOR(ARG(0,b)) == SQRT &&
INTEGERP(ARG(0,ARG(0,b))) && !contains_fraction(b)
)
{ mfracts(a,ARG(0,b),&temp3);
cancel(b,ARG(0,b),&p,&q);
*next = product(temp3,q);
strcpy(reason,"$$(a/b)c = ac/b$$");
HIGHLIGHT(*next);
SetShowStepOperation(multiplyfractions2); /* a(b/c) = ab/c */
return 0;
}
if(RATIONALP(a) &&
(!stopflag || contains_fraction(b))
/* example: (1/3)(x/3) = (1/9)x */
/* example: (1/3)(1/�2)(4/pi) which actually occurs
in one of the 'simple limit' problems! */
/* This will still leave (4/3) pi r^3 alone */
)
{ *next = product(a,b);
/* but this may just be t, or a re-ordering of t; check whether
a is already one of the factors of t */
for(i=0;i<k;i++)
{ if(equals(a,ARG(i,t)))
break;
}
if(i == k) /* *next isn't t or a reordering of t, so we're done:
for example (x/3)(y/3) = (1/9)xy
or (1/3)(x/3) = (1/9) x */
{ strcpy(reason,"$$(a/b)(c/d) = ac/(bd)$$");
goto out;
}
}
if(stopflag && RATIONALP(a) && equals(a,ARG(0,t)) &&
!contains_fraction(b) &&
!OBJECT(b) /* Go ahead and multiply (3/5) 6 ; no loop will
result and we need it in adding numerical fractions */
) /* as in (3/5) x */
{ errbuf(0, english(283)); /* Use a(b/c) = ab/c to multiply */
errbuf(1, english(284)); /* by a rational fraction. */
return 1;
}
}
err = multiplyfractions_aux(t,&temp,reason);
if(err)
return 1;
if(get_arithflag().intexp == 0)
{ *next = temp;
goto out; /* don't multiply numbers. With arithmetic turned so low
we are doing numerical problems and quite possibly still
awaiting a cancellation of one of the numbers that might
be multiplied here. */
}
ncs(temp,&n,&c,&s);
if(FUNCTOR(n) == '/' &&
status(commondenom) <= LEARNING &&
!get_arithflag().comdenom
&& ONE(c) && ONE(s)
)
/* avoid loop on (1/2)(3/3) + (1/3)(2/2),
get 3/6 + 2/6 instead of 1/2 + 1/3 again */
{ value(ARG(0,n),&temp2);
value(ARG(1,n),&temp3);
temp = make_fraction(temp2,temp3);
}
else if(status(multiplyfractions) > LEARNING)
{ value(n,&val);
n = val;
value(temp,&val);
temp = val;
}
if(FUNCTOR(s) == '/' || get_comdenomflag() || ONE(s) || ONE(n))
{ assert(!equals(t,temp)); /* else err would have been nonzero above */
if( (get_ringflag() & RATRING) && FUNCTOR(n) == '/')
{ if(ONE(c))
{ *next = temp;
goto out;
}
if(ONE(s))
{ err = multiplyfractions_aux(product(n,c),next,reason); /* 3 (�/4) = 3�/4 */
if(err || equals(t,*next))
{ *next = temp; /* changed from return 1 on 2.20.99 */
err = 0;
}
goto out;
}
err = multiplyfractions_aux(product(c,s),&temp3,reason);
if(err)
temp3 = product(c,s);
*next = product(n,temp3);
if(equals(t,*next))
return 1;
goto out;
}
*next = temp; /* as in (2/3)(x/y) goes to 2x/(3y) */
goto out;
}
if(ONE(c))
{ temp2 = product(n,s);
if(equals(temp2,t))
{ *next = temp;
if(count == 1)
SetShowStepOperation(multiplyfractions2); /* a(b/c) = ab/c */
return 0;
}
*next = temp2;
goto out;
}
/* Now all three parts are non-trivial */
temp2 = product3(n,c,s);
if(equals(temp2,t))
{ /* as in (4/3) pi r^2 or (1/c) arctan x */
err = multiplyfractions_aux(product(n,c),&temp3,reason);
if(err)
return 1;
*next = product(temp3,s); /* (4�/3)r^2 in menu mode */
if( !equals(*next,t) )
{ HIGHLIGHT(*next);
if(count == 1)
SetShowStepOperation(multiplyfractions2); /* a(b/c) = ab/c */
return 0;
}
*next = temp; /* (4�/3)r^2 comes here and goes to 4�r^2/3 */
goto out;
}
*next = temp;
out:
if(FUNCTOR(*next) == '/' && status(multiplyfractions) > LEARNING)
/* output (3(x-1))/6 instead of ((x-1)�3)/6 */
{ if(FUNCTOR(ARG(0,*next)) == '*')
sortargs(ARG(0,*next));
if(FUNCTOR(ARG(1,*next)) == '*')
sortargs(ARG(1,*next));
}
HIGHLIGHT(*next);
if(count == 1)
SetShowStepOperation(multiplyfractions2); /* a(b/c) = ab/c */
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int cancelminusinquotient(term t, term arg, term *next, char *reason)
/* (-a)/(-b) = a/b */
{ if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(0,t)) != '-')
return 1;
if(FUNCTOR(ARG(1,t)) != '-')
return 1;
*next = make_fraction(ARG(0,ARG(0,t)),ARG(0,ARG(1,t)));
strcpy(reason,"$$(-a)/(-b) = a/b$$");
HIGHLIGHT(*next);
if(SOME_INFINITESIMAL(t))
copy_infinitesimal_markers(t,next);
return 0;
}
/*____________________________________________________________________*/
/* There are a lot of operators for moving minus signs out of quotients. */
/* (-a)/b = -a/b minusoutfromnum */
/* a/(-b) = -a/b minusoutfromdenom */
/* -a/(-b) = a/b " */
/* (-a-b)/c = -(a+b)/c minusoutfromnum2 */
/* -(-a-b)/c = (a+b)/c " */
/* a/(-b-c) = -a/(b+c) minusoutfromdenom2 */
/* -a/(-b-c) = a/(b+c) " */
/* a/(b-c) = -a/(c-b) minusoutfromdenom3 */
/* -a/(b-c) = a/(c-b) " */
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromnum(term t, term arg, term *next, char *reason)
/* (-a)/b = -a/b */
{ if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(0,t)) != '-')
return 1;
*next = tnegate(make_fraction(ARG(0,ARG(0,t)),ARG(1,t)));
strcpy(reason,"$$(-a)/b = -(a/b)$$");
if(SOME_INFINITESIMAL(t))
copy_infinitesimal_markers(t,ARGPTR(*next));
HIGHLIGHT(*next);
return 0;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromdenom(term t, term arg, term *next, char *reason)
/* a/(-b) = -a/b */
{ int err;
term temp;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromdenom(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
return 0;
}
if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(1,t)) != '-')
return 1;
*next = tnegate(make_fraction(ARG(0,t),ARG(0,ARG(1,t))));
strcpy(reason,"$$a/(-b) = -(a/b)$$");
if(SOME_INFINITESIMAL(t))
copy_infinitesimal_markers(t,ARGPTR(*next));
HIGHLIGHT(*next);
return 0;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromnum2(term t, term arg, term *next, char *reason)
/* (-a-b)/c = -(a+b)/c */
/* -(-a-b)/c = (a+b)/c */
{ unsigned short n;
int j,err;
term temp;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromnum2(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
strcpy(reason,"-(-a-b)/c = (a+b)/c");
return 0;
}
if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(0,t)) != '+')
return 1;
n = ARITY(ARG(0,t));
for(j=0;j<n;j++)
{ if(FUNCTOR(ARG(j,ARG(0,t))) != '-')
return 1;
}
*next = tnegate(make_fraction(strongnegate(ARG(0,t)),ARG(1,t)));
HIGHLIGHT(*next);
strcpy(reason,"(-a-b)/c = -(a+b)/c");
if(SOME_INFINITESIMAL(t))
copy_infinitesimal_markers(t,ARGPTR(*next));
return 0;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromnum22(term t, term arg, term *next, char *reason)
/* -(-a-b)/c = (a+b)/c */
{ int err;
term temp;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromnum2(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
strcpy(reason,"-(-a-b)/c = (a+b)/c");
return 0;
}
return 1;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromdenom2(term t, term arg, term *next, char *reason)
/* a/(-b-c) = -a/(b+c) */
/* -a/(-b-c) = a/(b+c) */
{ unsigned short n;
int j,err;
term temp;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromdenom2(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
strcpy(reason, "-a/(-b-c) = a/(b+c)");
return 0;
}
if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(1,t)) != '+')
return 1;
n = ARITY(ARG(1,t));
for(j=0;j<n;j++)
{ if(FUNCTOR(ARG(j,ARG(1,t))) != '-')
return 1;
}
*next = tnegate(make_fraction(ARG(0,t),strongnegate(ARG(1,t))));
HIGHLIGHT(*next);
strcpy(reason,"a/(-b-c) = -a/(b+c)");
copy_infinitesimal_markers(t,ARGPTR(*next));
return 0;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromdenom22(term t, term arg, term *next, char *reason)
/* -a/(-b-c) = a/(b+c) */
{ int err;
term temp;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromdenom2(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
strcpy(reason, "-a/(-b-c) = a/(b+c)");
return 0;
}
return 1;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromdenom3(term t, term arg, term *next, char *reason)
/* a/(b-c) = -a/(c-b) */
/* -a/(b-c) = a/(c-b) */
{ term denom,temp;
int err;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromdenom3(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
strcpy(reason,"-a/(b-c) = a/(c-b)");
return 0;
}
if(FUNCTOR(t) != '/')
return 1;
denom = ARG(1,t);
if(FUNCTOR(denom) != '+' || ARITY(denom) != 2 ||
NEGATIVE(ARG(0,denom)) || !NEGATIVE(ARG(1,denom))
)
return 1;
*next = tnegate(make_fraction(ARG(0,t),sum(ARG(0,ARG(1,denom)),tnegate(ARG(0,denom)))));
strcpy(reason,"a/(b-c) = -a/(c-b)");
HIGHLIGHT(*next);
HIGHLIGHT(ARG(0,*next));
copy_infinitesimal_markers(t,ARGPTR(*next));
return 0;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int minusoutfromdenom33(term t, term arg, term *next, char *reason)
/* -a/(b-c) = a/(c-b) */
{ term temp;
int err;
if(NEGATIVE(t) && FRACTION(ARG(0,t)))
{ err = minusoutfromdenom3(ARG(0,t),arg,&temp,reason);
if(err)
return 1;
tneg(temp,next);
strcpy(reason,"-a/(b-c) = a/(c-b)");
return 0;
}
return 1;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int minusintonum(term t, term arg, term *next, char *reason)
/* -(a/b) =(-a)/b */
/* -((a+b)/c) = (-a-b)/c */
/* Applied in auto mode only for -(a-b)/c = (b-a)/c */
/* Also works on -(a/b)c to produce ((-a)/b)c, example:
-((e^-x-e^x)/2)i = ((e^x-e^-x)/2) i
*/
{ term u,c,newnum,v;
int i,err;
if(FUNCTOR(t) != '-')
return 1;
if(FUNCTOR(ARG(0,t)) == '*' && FRACTION(ARG(0,ARG(0,t))))
{ err = minusintonum(tnegate(ARG(0,ARG(0,t))),arg,&u,reason);
if(err)
return 1;
*next = make_term('*',ARITY(ARG(0,t)));
ARGREP(*next,0,u);
for(i=1;i<ARITY(*next);i++)
ARGREP(*next,i,ARG(i,ARG(0,t)));
return 0;
}
if(!FRACTION(ARG(0,t)))
return 1;
v = ARG(0,t);
u = ARG(0,v);
c = ARG(1,v);
if(FUNCTOR(u) != '+')
{ if (get_mathmode() == AUTOMODE)
return 1;
*next = make_fraction(tnegate(u),c);
HIGHLIGHT(*next);
if(SOME_INFINITESIMAL(v))
copy_infinitesimal_markers(v,next);
strcpy(reason,"$$-(a/b) = (-a)/b$$");
return 0;
}
if(get_mathmode() == AUTOMODE)
{ if(ARITY(u) != 2)
return 1;
if(!NEGATIVE(ARG(1,u)))
return 1;
}
newnum = strongnegate(u);
additive_sortargs(newnum);
*next = make_fraction(newnum,c);
HIGHLIGHT(*next);
if(NEGATIVE(ARG(1,u)))
strcpy(reason,"-(a-b)/c = (b-a)/c");
else
strcpy(reason, "-(a+b)/c) = (-a-b)/c");
if(SOME_INFINITESIMAL(v))
copy_infinitesimal_markers(v,next);
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int minusintodenom(term t, term arg, term *next, char *reason)
/* -(a/b) =a/(-b) */
{ term u,c,v;
if(FUNCTOR(t) != '-')
return 1;
if(!FRACTION(ARG(0,t)))
return 1;
v = ARG(0,t);
u = ARG(1,v);
c = ARG(0,v);
*next = make_fraction(c,tnegate(u));
HIGHLIGHT(*next);
if(SOME_INFINITESIMAL(v))
copy_infinitesimal_markers(v,next);
strcpy(reason,"$$-(a/b) = a/(-b)$$");
return 0;
}
/*________________________________________________________________*/
static int precancel(term t, term *next, term *cancelled)
/* t must be a product containing at least one fraction. Cancel
denominator of a fraction with another factor. (But don't do it
if the denominator itself is a fraction, unless the other factor
is a fraction with the same denominator; thus (1/4) (3/(1/4)) would cancel
the 1/4 but 2 (3/(5/4)) will not cancel 1/4.)
Put the cancelled term in *cancelled. Return 0 for success. */
{ unsigned short n = ARITY(t);
int i,j,k,m,err,nfractions;
term u,v,num,denom,newnum,newdenom,p;
if(FUNCTOR(t) != '*')
return 1;
/* count the fractions */
nfractions = 0;
for(i=0;i<n;i++)
{ if(FRACTION(ARG(i,t)))
++nfractions;
}
if(n > 2 && nfractions == 1 && FRACTION(ARG(n-1,t)))
{ /* example, 3x^2 (1/3x). Without this code it takes
two steps, first to cancel the 3 and then to cancel x */
u = make_term('*',(unsigned short)(n-1));
for(i=0;i<n-1;i++)
ARGREP(u,i,ARG(i,t));
err = cancel(u,ARG(1,ARG(n-1,t)),cancelled,&p);
if(err)
return 1;
if(FRACTION(p))
*next = product(ARG(0,p), make_fraction(ARG(0,ARG(n-1,t)),ARG(1,p)));
else
*next = product(p,ARG(0,ARG(n-1,t)));
HIGHLIGHT(*next);
return 0;
}
for(i=0;i<n;i++)
{ u = ARG(i,t);
if(ATOMIC(u) || FUNCTOR(u) != '/' || SOME_INFINITESIMAL(u))
continue;
denom = ARG(1,u);
for(j=0;j<n;j++)
{ if(j==i)
continue;
v = ARG(j,t);
if(FRACTION(denom) && !(FRACTION(v) && equals(ARG(1,v),ARG(1,denom))))
continue; /* see specs of this function */
num = FRACTION(v) ? ARG(0,v) : v;
err = cancel(num,denom,cancelled,&p);
if(err)
continue;
break; /* something cancelled */
}
if(j<n)
break; /* something cancelled */
}
if(i==n)
return 1; /* there was no cancellation */
cancel(denom,*cancelled,&p,&newdenom); /* newdenom of the i-th arg */
cancel(num, *cancelled,&p,&newnum); /* newnum of the j-th arg */
HIGHLIGHT(newnum);
HIGHLIGHT(newdenom);
if(equals(denom,*cancelled) && ONE(ARG(0,u)) &&
equals(v,num) && equals(v, *cancelled)
)
/* both i-th and j-th factor will disappear */
/* example, (1/4) 4x */
{ if(n==2)
{ *next = one;
HIGHLIGHT(*next);
return 0;
}
else if(n==3)
{ *next = ARG(i && j ? 0 : i!= 1 && j != 1 ? 1 : 2, t);
HIGHLIGHT(*next);
return 0;
}
else /* answer will be product of the remaining terms */
{ *next = make_term('*',(unsigned short)(n-2));
k=0;
for(m=0;m<n;m++)
{ if(m==i || m==j)
continue;
ARGREP(*next,k,ARG(m,t));
++k;
}
HIGHLIGHT(*next);
return 0;
}
}
if(equals(denom,*cancelled) && ONE(ARG(0,u)))
/* i-th factor will disappear */
/* j-th factor will be replaced by newnum/ old denom of j-th arg */
/* example, 1/4 4/pi ; the 1/4 will disappear leaving 1/pi */
{ if(n==2)
{ v = ARG(i ? 0 : 1,t);
if(FRACTION(v))
*next = make_fraction(newnum,ARG(1,v));
else
*next = newnum;
HIGHLIGHT(*next);
return 0;
}
else /* answer will be product of the remaining terms */
{ *next = make_term('*',(unsigned short)(n-1));
k=0;
for(m=0;m<n;m++)
{ if(m==i)
continue;
if(m==j && FRACTION(ARG(j,t)))
ARGREP(*next,k, make_fraction(newnum,ARG(1,ARG(j,t))));
else if(m==j) /* and not FRACTION(ARG(j,t)) */
ARGREP(*next,k, newnum);
else
ARGREP(*next,k,ARG(m,t));
++k;
}
HIGHLIGHT(*next);
return 0;
}
}
if(equals(v,*cancelled))
/* j-th factor will disappear */
/* i-th factor will become (old num of i-th factor)/newdenom */
{ if(n==2)
{ *next = make_fraction(ARG(0,u),newdenom);
HIGHLIGHT(*next);
return 0;
}
else /* answer will be product of the remaining terms */
{ *next = make_term('*',(unsigned short)(n-1));
k=0;
for(m=0;m<n;m++)
{ if(m==j)
continue;
if(m==i)
ARGREP(*next,k, make_fraction(ARG(0,u),newdenom));
else
ARGREP(*next,k,ARG(m,t));
++k;
}
HIGHLIGHT(*next);
return 0;
}
}
/* Now the i-th and j-th factors will both remain after cancellation */
*next = make_term('*',n);
for(k=0;k<n;k++)
{ if(k==i)
ARGREP(*next,k,make_fraction(ARG(0,u),newdenom));
else if(k==j && FRACTION(ARG(j,t)))
ARGREP(*next,k,make_fraction(newnum,ARG(1,ARG(j,t))));
else if(k==j) /* and ARG(j,t) is not a fraction */
ARGREP(*next,k,newnum);
else
ARGREP(*next,k,ARG(k,t));
}
return 0;
}
/*____________________________________________________________________*/
static int contains_fraction(term t)
/* return 1 if t is a fraction, or a sum or product or negation which
contains_fraction, or a power whose base contains_fraction. Otherwise
return 0. */
{ unsigned short n = ARITY(t);
unsigned short f = FUNCTOR(t);
int i;
if(ATOMIC(t))
return 0;
if(f == '/')
return 1;
if(f == '-' || f == '^')
return contains_fraction(ARG(0,t));
if(f == '+' || f == '*' )
{ for(i=0;i<n;i++)
{ if(contains_fraction(ARG(i,t)))
return 1;
}
return 0;
}
return 0;
}
/*____________________________________________________________________*/
static int factorof(term t, term a)
/* return 1 if a==t or t is a product, one of whose factors is a */
{ unsigned short n;
int i;
if(equals(t,a))
return 1;
if(FUNCTOR(t) != '*')
return 0;
n = ARITY(t);
for(i=0;i<n;i++)
{ if(equals(a,ARG(i,t)))
return 1;
}
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int cancelop(term t, term arg, term *next, char *reason)
/* cancel common factors in numerator and denom of t */
/* Also works on products of fractions to cancel BEFORE the
fractions are multiplied */
/* In auto mode, this won't work on compound fractions. */
{ term num,denom,cancelled,temp;
char buffer[81];
int k,err;
if(FUNCTOR(t) == '*')
{ err = precancel(t,next,&cancelled);
if(err)
return 1;
}
else if(FUNCTOR(t) != '/')
return 1;
else
{ num = ARG(0,t);
denom = ARG(1,t);
if(get_mathmode() == AUTOMODE)
{ /* in p^(1/2)/q^(1/2), don't cancel x^1/2 for example
if x cancels from p and q. Change to (p/q)^(1/2) first.
*/
if(FUNCTOR(num) == '^' && FRACTION(ARG(1,num)) &&
FUNCTOR(denom) == '^' && FRACTION(ARG(1,denom)) &&
equals(ARG(1,denom),ARG(1,num))
)
{ if(!cancel(ARG(0,num),ARG(0,denom),&cancelled,&temp))
{ *next = make_power(make_fraction(ARG(0,num),ARG(0,denom)),ARG(1,num));
HIGHLIGHT(*next);
strcpy(reason,"$$a^n/b^n = (a/b)^n$$");
SetShowStepOperation(poweroutoffraction);
return 0;
}
return 1;
}
}
if( (OBJECT(num) && TYPE(num) == DOUBLE && OBJECT(denom)) ||
(OBJECT(denom) && TYPE(denom) == DOUBLE && OBJECT(num))
)
return 1; /* use decimal arithmetic instead. It looks
silly to see 'cancel 0.5875' as a reason
when it's just decimal arithmetic. */
err = cancel(num,denom,&cancelled,next);
if(err)
return 1;
/* Don't let this operation cancel a negative power */
if(FUNCTOR(cancelled) == '^' && NEGATIVE(ARG(1,cancelled)))
return 1;
/* And don't let it cancel a product of negative powers */
if(FUNCTOR(cancelled) == '^')
{ for(k=0;k<ARITY(cancelled);k++)
{ if(FUNCTOR(ARG(k,cancelled)) != '^')
break;
if(!NEGATIVE(ARG(1,ARG(k,cancelled))))
break;
}
if(k==ARITY(cancelled))
return 1; /* all factors were negative powers */
}
/* But this still allows it to cancel for example xy^(-2) */
HIGHLIGHT(*next);
/* some fractions can have zero (infinitesimal) denominators.
Preserve the 'tag' bits that so indicate. */
if(NEGATIVE(*next) && FRACTION(ARG(0,*next)) && SOME_INFINITESIMAL(t))
copy_infinitesimal_markers(t,ARGPTR(*next));
else if(FRACTION(*next) && SOME_INFINITESIMAL(t))
copy_infinitesimal_markers(t,next);
}
if(get_mathmode() == AUTOMODE && FRACTION(cancelled) &&
( FUNCTOR(t) != '/' ||
! factorof(num,cancelled) ||
! factorof(denom,cancelled)
)
)
return 1; /* only proceed if cancelled is actually a factor
of both num and denom */
mstring(cancelled,buffer);
k = strlen(buffer);
if( k < 15)
{ strcpy(reason, english(2203)); /* cancel */
strcat(reason," ");
strcat(reason,buffer);
return 0;
}
else /* cancelled is too long to print in justification column */
{ strcpy(reason,english(2203)); /* cancel */
return 0;
}
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA term numerator(term t)
/* return signed numerator of signed fraction */
/* if t is not a signed fraction, returns t itself */
{ if(ATOMIC(t))
return t;
switch(FUNCTOR(t))
{ case '-' : return tnegate(numerator(ARG(0,t)));
case '/' : return ARG(0,t);
}
return t;
}
/*_____________________________________*/
MEXPORT_ALGEBRA term denom(term t)
/* return denom of a signed fraction */
{ if(ATOMIC(t))
return one; /* in case somebody calls it on an atom or int */
switch(FUNCTOR(t))
{ case '-' : return denom(ARG(0,t));
case '/' : return ARG(1,t);
default: assert(0);
}
return zero; /* avoid an error message */
}
/*______________________________________________________*/
static int algnum_aux(term t)
/* t is a sum. Return 1 if it has the form a/c + sqrt(b)/c
for integers a,b,c, or with the two summands in the other order.
*/
{ term u,v;
if(FUNCTOR(t) != '+' || ARITY(t) != 2)
return 0;
u = ARG(0,t);
v = ARG(1,t);
if(NEGATIVE(u))
u = ARG(0,u);
if(NEGATIVE(v))
v = ARG(0,v);
if(!FRACTION(u) || !FRACTION(v) || !equals(ARG(1,u),ARG(1,v)) || !INTEGERP(ARG(1,u)))
return 0;
u = ARG(0,u);
v = ARG(0,v); /* replace u and v by their numerators */
if(!INTEGERP(u) && !INTEGERP(v))
return 0;
if(INTEGERP(u))
u = v;
if(FUNCTOR(u) != SQRT)
return 0;
return INTEGERP(ARG(0,u));
}
/*______________________________________________________*/
MEXPORT_ALGEBRA int addfractions(term t, term arg, term *next, char *reason)
/* t must be a sum. Adds any ADJACENT fractions with the same denominator in t.
Only adds one group of fractions with the same denominator */
/* Uses DEBUG_MathXpert to test equality of denominators, so e.g. a+b+c and a+c+b will
count as the same. */
/* But, in automode when ringflag & RATRING && !infractionflag,
don't add fractions whose
denom is an integer; thus u/2 + v/2 will not go to (u+v)/2 but by some
other operator to (1/2)u + (1/2)v. */
{ int i,j,flag,err;
term u,denominator,num,num1,sum;
unsigned short k;
int stopflag;
int signflag = 0;
unsigned short n = ARITY(t);
if(FUNCTOR(t) != '+')
return 1;
stopflag = get_mathmode() == AUTOMODE && (get_ringflag() & RATRING) && !get_infractionflag() && (!get_comdenomflag() || algnum_aux(t));
for(i=0;i< n-1;i++)
{ if (
SIGNEDFRACTION(ARG(i,t)) &&
SIGNEDFRACTION(ARG(i+1,t)) &&
eqtest(denom(ARG(i,t)),denom(ARG(i+1,t))) &&
(!stopflag || !INTEGERP(denom(ARG(i,t))))
)
break;
}
if(i==n-1)
return 1; /* no fractions to add */
/* Now we have located a group of at least two adjacent fractions
with the same denominator */
denominator = denom(ARG(i,t));
/* count how many terms have this denominator */
k = 2;
for(j=i+2;j<n;j++)
{ if (!SIGNEDFRACTION(ARG(j,t)))
break;
if (!eqtest(denominator,denom(ARG(j,t))))
break;
++k; /* count the j-th term */
if(FUNCTOR(ARG(j,t))=='-')
signflag = 1;
}
/* conclusion: there are k terms with the same denominator */
num = make_term('+',k);
for(j=0;j<k;j++)
{ u = numerator(ARG(i+j,t));
if(eqtest(denom(ARG(i+j,t)),denominator) == -1)
u = tnegate(u);
ARGREP(num,j,u);
}
flag = status(addfractions);
if(flag <= LEARNING)
num1 = num;
else if (flag >= KNOWN && status(collectall) >= KNOWN)
collectall(num,arg,&num1,reason); /* reason will not be used */
else
{ err = value(num,&num1);
if(err)
num1 = num;
}
sum = make_fraction(num1,denominator);
HIGHLIGHT(sum);
if(k==n)
*next = sum;
else
{ *next = make_term('+',(unsigned short)(n-k+1));
for(j=0;j<i;j++)
ARGREP(*next,j,ARG(j,t));
ARGREP(*next,i,sum);
for(j=i+1;j<n-k+1;j++)
ARGREP(*next,j,ARG(j+k-1,t));
}
if(signflag && k==2)
strcpy(reason, "a/c - b/c = (a-b)/c");
else if (signflag)
strcpy(reason, "$a/c � b/c = (a�b)/c$");
else
strcpy(reason, "a/c + b/c = (a+b)/c");
for(i=0;i<NCANCELOPS;i++)
release(cancelops[i]); /* possibly inhibited by findcommondenom */
release(arithmetic); /* ditto */
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int apart(term t, term arg, term *next, char *reason)
/* apart applies the law (a�b)/c = (a/c)�(b/c). Note that is NOT
partial fractions expansion. It allows any number of summands. */
{ unsigned short n;
int i,flag;
term den,num;
if(FUNCTOR(t) != '/' )
return 1;
num = ARG(0,t);
if(FUNCTOR(num) != '+')
return 1;
n = ARITY(num);
den = ARG(1,t);
*next = make_term('+',n);
for(i=0;i<n;i++)
{ if( FUNCTOR(ARG(i,num)) == '-')
{ tneg(make_fraction(ARG(0,ARG(i,num)),den),ARGPTR(*next)+i);
flag = 1; /* a minus sign was encountered */
}
else
ARGREP(*next,i,make_fraction(ARG(i,num),den));
}
if(flag && n==1) /* one of the 2 terms had a minus sign */
strcpy(reason, "(a-b)/c = a/c - b/c");
if(flag) /* more than 2 terms, one of which is negative */
strcpy(reason, "$(a�b)/c = a/c � b/c$");
else
strcpy(reason, "$(a+b)/c = a/c + b/c$");
HIGHLIGHT(*next);
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int apartandcancel(term t, term arg, term *next, char *reason)
/* apply apart, then perform cancellations in the resulting
fractions, failing if there is no cancellation (to prevent loops
in auto mode) */
/* In auto mode, do not do it if the only thing cancelled is a constant,
unless we're trying to add up a series or an indexed sum. */
{ int i,err,sign;
int flag=0; /* set to 1 when we get a cancellation */
int nonconstflag= 0; /* set to 1 when a non-constant cancels */
term u,temp,cancelled,q;
unsigned short n;
err = apart(t,arg,&temp,reason);
if(err)
return 1;
assert(FUNCTOR(temp)=='+');
n = ARITY(temp);
*next = make_term('+',n);
for(i=0;i<n;i++)
{ if(FUNCTOR(ARG(i,temp))== '-')
{ sign = -1;
u = ARG(0,ARG(i,temp));
}
else
{ sign = 1;
u = ARG(i,temp);
}
if(FUNCTOR(u) != '/')
ARGREP(*next,i,ARG(i,temp));
else
{ err = cancel(ARG(0,u),ARG(1,u),&cancelled,&q);
if(err)
ARGREP(*next,i,ARG(i,temp));
else
{ ARGREP(*next,i,((sign==1) ? q : tnegate(q)));
flag = 1;
if(!constant(cancelled))
nonconstflag = 1;
}
}
}
if( !flag ||
(get_mathmode() == AUTOMODE &&
!nonconstflag &&
get_problemtype()!= ADDSERIES &&
get_problemtype()!= SIGMA_NOTATION
)
)
/* there was no significant cancellation */
{ RELEASE(*next);
return 1;
}
HIGHLIGHT(*next);
strcpy(reason,"$(ac�b)/c = a � b/c$");
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int cancelbypolydiv(term t, term arg, term *next, char *reason)
/* if t= a/b and b divides a exactly, return the quotient in *next */
/* if it doesn't, fail (return 1) */
{ int natoms,err;
term *atomlist=NULL; /* initialize to silence 'use before defn' complaint */
term gcd,num,denom,x,r;
natoms = atomsin(t,&atomlist);
if(natoms > 1)
{ free2(atomlist);
return 1; /* only works if all coeff. are numerical */
/* can't divide e.g. (a^2 - b^2)/(a-b) this way */
}
x = atomlist[0];
free2(atomlist);
if(FUNCTOR(t) != '/')
return 1;
err = polygcd(ARG(0,t),ARG(1,t),&gcd);
if(err || ONE(gcd))
return 1;
err = polydiv(x,ARG(0,t),gcd,&num,&r);
if(err)
return 1;
err = polydiv(x,ARG(1,t),gcd,&denom,&r);
if(err)
return 1;
*next = make_fraction(num,denom);
strcpy(reason, english(2204)); /* cancel gcd */
HIGHLIGHT(*next);
return 0;
}
/*_____________________________________________________________________*/
MEXPORT_ALGEBRA int polydivop(term t, term arg, term *next, char *reason)
/* if t= a/b and b has degree <= a, return *next = q + r/b where
q and r are the quotient and remainder of a on division by b */
{ int natoms,i,err;
term *atomlist;
term x,q,r;
int problemtype;
if(FUNCTOR(t) != '/')
return 1;
natoms = atomsin(t,&atomlist);
if(natoms > 1 || natoms==0)
{ free2(atomlist);
return 1; /* only works if all coeff. are numerical */
/* and there is at least one variable */
/* can't divide e.g. (a^2 - b^2)/(a-b) this way */
}
for(i=0;i<natoms;i++)
{ x = atomlist[i];
if(contains(ARG(1,t),FUNCTOR(x)))
break;
}
if(i==natoms)
{ /* constant denominator */
free2(atomlist);
return 1;
}
free2(atomlist);
err = polydiv(x,ARG(0,t),ARG(1,t),&q,&r); /* polynomial division in variable x */
if(err)
{ errbuf(0,english(816));
/* Polynomial division tried on non-polynomials. */
return 1;
}
if(ZERO(q)) /* degree of denominator exceeds that of numerator */
return 1;
if(ZERO(r))
*next = q;
else if(FUNCTOR(r) == '/')
*next = sum(q, signedfraction(ARG(0,r),signedproduct(ARG(1,r),ARG(1,t))));
else
*next = sum(q,signedfraction(r,ARG(1,t)));
problemtype = get_problemtype();
if(get_mathmode() == AUTOMODE && problemtype < LIMITS)
{ /* don't create non-integer coefficients */
if(contains(q,'/') || contains(r,'/'))
return 1;
if(SOLVETYPE(problemtype) && !ZERO(r))
return 1; /* solving equations, only use this if quotient is exact */
}
HIGHLIGHT(*next);
strcpy(reason, english(2205)); /* polynomial division */
return 0;
}
/* COMPOUND FRACTIONS ________________________________________________*/
MEXPORT_ALGEBRA int compoundfractions1(term t, term arg, term *next, char *reason)
/* (a/c)/(b/c) = a/b */
{ term c;
if (FUNCTOR(t) != '/')
return 1;
if (FUNCTOR(ARG(0,t)) != '/')
return 1;
if (FUNCTOR(ARG(1,t)) != '/')
return 1;
c = ARG(1,ARG(0,t));
if (! equals(c,ARG(1,ARG(1,t))))
return 1;
*next = make_fraction(ARG(0,ARG(0,t)),ARG(0,ARG(1,t)));
strcpy(reason, "$$(a/c)/(b/c) = a/b$$");
HIGHLIGHT(*next);
return 0;
}
/*_____________________________________________________________*/
MEXPORT_ALGEBRA int compoundfractions4(term t, term arg, term *next, char *reason)
/* (a/b)c/d = ac/(bd) */
{ term num,bd,ac,d,u,temp;
int i,j,count=0,flattenflag=0;
unsigned short k,k1=0,k2=0,n;
if (FUNCTOR(t) != '/')
return 1;
num = ARG(0,t);
if (FUNCTOR(num) != '*')
return 1;
n = ARITY(num);
d = ARG(1,t);
/* determine the arity of the new denominator */
k=0;
for(i=0;i<n;i++)
{ u = ARG(i,num);
if(FRACTION(u))
{ if(FUNCTOR(ARG(1,u)) == '*')
k += ARITY(ARG(1,u));
else
++k;
}
}
if(FUNCTOR(d) == '*')
k += ARITY(d);
else
++k;
bd = make_term('*',k);
ac = make_term('*',n);
if(FUNCTOR(d) == '*')
{ for(k2 = 0;k2 < ARITY(d);k2++)
ARGREP(bd,k2,ARG(k2,d));
}
else
{ ARGREP(bd,0,d);
++k2;
}
for(i=0;i<n;i++)
{ u = ARG(i,num);
if(FRACTION(u))
{ ++count;
if(!ONE(ARG(0,u)))
{ ARGREP(ac,k1,ARG(0,u));
++k1;
if(FUNCTOR(ARG(0,u)) == '*')
++flattenflag;
}
if(FUNCTOR(ARG(1,u)) == '*')
{ for(j=0;j<ARITY(ARG(1,u));j++)
{ ARGREP(bd,k2,ARG(j,ARG(1,u)));
++k2;
}
}
else
{ ARGREP(bd,k2,ARG(1,u));
++k2;
}
}
else
{ ARGREP(ac,k1,u);
++k1;
if(FUNCTOR(u) == '*')
++flattenflag;
}
}
if(count == 0) /* no fractions in numerator */
{ RELEASE(ac);
RELEASE(bd);
return 1;
}
assert(k2 > 1); /* there was at least one arg from d, and one from
at least one fraction in the numerator */
/* But k1 need not even be nonzero, if there were 1's in the numerators */
if(k1 == 0)
{ RELEASE(ac);
ac = one;
}
else if(k1 == 1)
{ temp = ARG(0,ac);
RELEASE(ac);
ac = temp;
}
else
SETFUNCTOR(ac,'*',k1);
if(FUNCTOR(ac) == '*')
{ if(flattenflag)
ac = topflatten(ac);
sortargs(ac);
}
SETFUNCTOR(bd,'*',k2);
sortargs(bd);
if(get_mathmode() == AUTOMODE && ONE(ARG(0,bd)))
return 1; /* example, (1/4 ab)/((1/4)cd); cancel will work better */
*next = make_fraction(ac,bd);
strcpy(reason, "$$(a/b)c/d = ac/(bd)$$");
HIGHLIGHT(*next);
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int invertandmultiply2(term t, term arg, term *next, char *reason)
/* 1/(a/b) = b/a */
{ int err;
term c,denom;
if(FUNCTOR(t) != '/')
return 1;
denom = ARG(1,t);
if(!ONE(ARG(0,t)))
return 1;
if(!FRACTION(denom))
return 1;
*next = reciprocal(denom);
if(FUNCTOR(*next) == '*')
sortargs(*next); /* avoid unnecessary re-ordering steps */
if(FRACTION(*next))
{ if(FUNCTOR(ARG(0,*next)) == '*')
sortargs(ARG(0,*next));
if(FUNCTOR(ARG(1,*next)) == '*')
sortargs(ARG(1,*next));
}
if(status(collectpowers) >= KNOWN)
{ if(FRACTION(*next) && FUNCTOR(ARG(0,*next)) == '*')
{ err = rawcollectpowers(ARG(0,*next),&c,1);
if(!err)
ARGREP(*next,0,c);
}
else if(FUNCTOR(*next) == '*')
{ err = rawcollectpowers(*next,&c,1);
if(!err)
*next = c;
}
}
HIGHLIGHT(*next);
strcpy(reason,"$$1/(a/b) = b/a$$");
return 0;
}
/*____________________________________________________________________*/
MEXPORT_ALGEBRA int invertandmultiply(term t, term arg, term *next, char *reason)
/* a/ (b/c) = ac/b ; also a/(d(b/c)) = ac/db */
{ int err;
term c,d,num,denom;
if(FUNCTOR(t) != '/')
return 1;
denom = ARG(1,t);
num = ARG(0,t);
if(ONE(num))
return invertandmultiply2(t,arg,next,reason);
if(FUNCTOR(denom) == '*' && contains(denom,'/'))
{ int st = status(invertandmultiply);
term p,q,r,temp;
int i;
unsigned short k=0,j=0;
unsigned short m = ARITY(denom);
if(st <= LEARNING && get_mathmode() == AUTOMODE)
return 1; /* go slower! multiply fractions in the denom first */
p = make_term('*',m); /* collect the numerators */
q = make_term('*',m); /* collect the denominators */
for(i=0;i<m;i++)
{ r = ARG(i,denom);
if(FRACTION(r))
{ ARGREP(q,k,ARG(1,r));
++k;
if(!ONE(ARG(0,r)))
{ ARGREP(p,j,ARG(0,r));
++j;
}
}
else
{ ARGREP(p,j,r);
++j;
}
}
if(k==0)
{ RELEASE(p);
RELEASE(q);
return 1;
}
if(k==1)
{ temp = ARG(0,q);
RELEASE(q);
q = temp;
}
else
SETFUNCTOR(q,'*',k);
if(j==0)
{ RELEASE(p);
p = one;
}
else if(j==1)
{ temp = ARG(0,p);
RELEASE(p);
p = temp;
}
else
SETFUNCTOR(p,'*',j);
return invertandmultiply(make_fraction(num,make_fraction(p,q)),arg,next,reason);
}
if(FUNCTOR(denom) != '/')
return 1;
d = ARG(1,denom);
mfracts(make_fraction(d,ARG(0,denom)),num,next);
/* so 1/(c/d) = d/c and a/(c/d) = (ac)/d, not a(c/d) */
if(FUNCTOR(*next) == '*')
sortargs(*next); /* avoid unnecessary re-ordering steps */
if(FRACTION(*next))
{ if(FUNCTOR(ARG(0,*next)) == '*')
sortargs(ARG(0,*next));
if(FUNCTOR(ARG(1,*next)) == '*')
sortargs(ARG(1,*next));
}
if(status(collectpowers) >= KNOWN)
{ if(FRACTION(*next) && FUNCTOR(ARG(0,*next)) == '*')
{ err = rawcollectpowers(ARG(0,*next),&c,1);
if(!err)
ARGREP(*next,0,c);
}
else if(FUNCTOR(*next) == '*')
{ err = rawcollectpowers(*next,&c,1);
if(!err)
*next = c;
}
}
HIGHLIGHT(*next);
if(ONE(num))
strcpy(reason,"$$1/(a/b) = b/a$$");
else
strcpy(reason, english(286)); /* invert and multiply */
return 0;
}
/*_________________________________________________________________________*/
MEXPORT_ALGEBRA int compoundfractions2(term t, term arg, term *next, char *reason)
/* (a/b)/c = a/(bc) */
{ term d;
if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(0,t)) != '/' )
return 1;
if(get_mathmode() == AUTOMODE && contains_fraction(ARG(1,t)))
/* when there are fractions in the denom, first simplify the
denom till it IS a fraction, then use invertandmultiply */
return 1;
mt(ARG(1,ARG(0,t)),ARG(1,t),&d);
if(FUNCTOR(d) == '*')
sortargs(d);
*next = make_fraction(ARG(0,ARG(0,t)),d);
HIGHLIGHT(*next);
strcpy(reason,"$$(a/b)/c = a/(bc)$$");
return 0;
}
/*_____________________________________________________________________*/
/* only for menu mode */
MEXPORT_ALGEBRA int compoundfractions3(term t, term arg, term *next, char *reason)
/* (a/b)/c = (a/b)(1/c) */
{ if(FUNCTOR(t) != '/')
return 1;
if(FUNCTOR(ARG(0,t)) != '/')
return 1;
mt(ARG(0,t),make_fraction(one,ARG(1,t)),next);
HIGHLIGHT(*next);
strcpy(reason,"$$a/b = a(1/b)$$");
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int findcommondenom(term t, term arg, term *next, char *reason)
/* example: given a+1/x+1/y this gets a(xy/(xy)) + (1/x)(y/y) + (1/y)(x/x)
prepares for putting all terms of a sum over a common denom. */
{ unsigned short n,k;
int i,j;
term f; /* for the factors c/c */
term q; /* for collecting the denoms */
term r; /* for collecting the denoms without duplication */
term temp, aa, trash, currentdenom, denom, c_over_c;
if(FUNCTOR(t) != '+')
return 1;
n = ARITY(t);
*next = make_term('+',n);
q = make_term('+',n);
r = make_term('+',n);
k=0; /* how many denoms collected so far */
for(i=0;i<n;i++)
{ if(FUNCTOR(ARG(i,t)) == '-' && FUNCTOR(ARG(0,ARG(i,t))) == '/')
currentdenom = ARG(1,ARG(0,ARG(i,t)));
else if(FUNCTOR(ARG(i,t)) == '/')
currentdenom = ARG(1,ARG(i,t));
else
currentdenom = one;
ARGREP(q,i,currentdenom);
if(ONE(currentdenom))
continue;
for(j=0;j<k;j++)
{ if(equals(ARG(j,q), currentdenom))
break;
}
if(j==k) /* add currentdenom to r */
{ ARGREP(r,k,currentdenom);
++k;
}
}
if(k==0)
return 1; /* inapplicable */
if(k==1) /* only one fraction */
denom = ARG(0,r);
else
{ SETFUNCTOR(r,'+',k);
naive_listlcm(r,&denom);
if(FUNCTOR(denom) == '*')
{ temp = denom;
local_polyval2(temp,&denom); /* make sure it's in standard form */
}
}
for(i=0;i<n;i++) /* compute the args of *next */
{ aa = ARG(i,q);
if(FUNCTOR(aa) == '*')
{ temp = aa;
local_polyval2(temp,&aa); /* make sure it too is in standard form */
}
if(equals(aa,denom)) /* both are in standard form now */
ARGREP(*next,i,ARG(i,t)); /* no factor c/c necessary */
else /* there will be a factor c/c */
{ if(ONE(aa)) /* i-th term was not a fraction, so aa = 1 */
{ copy(make_fraction(denom,denom),&c_over_c);
/* copy to avoid making a DAG */
inhibit(polydivop); /* otherwise we loop right away */
inhibit(cancelbypolydiv);
/* these will be released by addfractions */
}
else
{ supercancel(denom,aa,&trash,&f);
copy(make_fraction(f,f),&c_over_c);
/* copy to avoid making a DAG */
}
SET_ALREADYARITH(c_over_c);
HIGHLIGHT(c_over_c);
if(FUNCTOR(ARG(i,t))=='-')
ARGREP(*next,i,tnegate(product(ARG(0,ARG(i,t)),c_over_c)));
else
ARGREP(*next,i,product(ARG(i,t),c_over_c));
SET_ALREADYARITH(ARG(i,*next));
/* prevent arithmetic from undoing things before the fractions
are multiplied together. On sums of an integer and two
fractions, this happens otherwise. E.g. 5 + 1/2 + 1/3,
the first term 5(6/6) will be multiplied together by a
different operator than (1/2)(3/3) and the (1/2)(3/3) will
still be there after the first step, so we must prevent
arithmetic from collapsing it.
*/
}
}
strcpy(reason, english(1370)); /* find common denom */
RELEASE(q);
RELEASE(r);
for(i=0;i<NCANCELOPS;i++)
inhibit(cancelops[i]); /* prevent loops; released by addfractions */
inhibit(arithmetic); /* prevent cancelling fractions; released by addfractions */
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int findcommondenom2(term t, term arg, term *next, char *reason)
/* example: given a+1/x+b+1/y this gets a + (1/x)(y/y) + (1/y)(x/x) + b
prepares for putting all FRACTIONAL terms of a sum over a common denom. */
{ int i,j;
unsigned short n; /* arity of t */
unsigned short m; /* number of fractions among args of t */
int mark; /* which arg of t is the first fraction */
int *scratchpad; /* array of 0's and 1's telling which args of t are fractions */
term temp,q;
if (FUNCTOR(t) != '+')
return 1;
n = ARITY(t);
m=0;
temp=make_term('+',n); /* stash the fractions here */
mark = -1;
scratchpad = (int *) callocate(n,sizeof(int));
for(i=0; i<n; i++)
{if (SIGNEDFRACTION(ARG(i,t)))
{ ARGREP(temp,m,ARG(i,t));
if(mark < 0)
mark = i;
scratchpad[i] = 1;
++m;
}
}
if(m < 2)
return 1; /* zero or one fractions */
SETFUNCTOR(temp,'+',m);
/* Now there are at least two fractions, and they're all in temp */
findcommondenom(temp,arg,&q,reason);
*next = make_term('+',n);
/* first copy the non-fractions preceding the first fraction */
for(i=0;i<mark;i++)
ARGREP(*next,i,ARG(i,t));
/* Now copy all the fractions (multiplied by their factors c/c ) */
for(i=mark;i <mark+m;i++)
ARGREP(*next,i,ARG(i-mark,q));
/* Finally put the rest of the non-fractions in *next */
assert(i==mark+m);
for(j=mark+1; j < n ; ++j)
{ if(scratchpad[j]==0)
{ ARGREP(*next,i,ARG(j,t));
assert(i<n);
++i;
}
}
free2(scratchpad); /* release temporary memory on heap */
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int naivecommondenom(term t, term arg, term *next, char *reason)
/* common denom without factoring, all in one step, all terms of sum */
/* example: given a+1/x+1/y this gets (axy + y + x)/xy
prepares for putting all terms of a sum over a common denom. */
{ int err = naivecomdenom(t,next);
int i;
unsigned short n;
term denom,u;
if(err)
return 1;
HIGHLIGHT(*next);
/* In case all the fractions had the same denom, use a different
reason string */
n = ARITY(t);
for(i=0;i<n;i++)
{ u = ARG(i,t);
if(!FRACTION(u))
break;
if(i==0)
denom = ARG(1,u);
else if(!equals(denom,ARG(1,u)))
break;
}
if(i==n)
strcpy(reason,"a/c + b/c = (a+b)/c");
else
strcpy(reason, english(287)); /* common denominator */
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int commondenomandsimp(term t, term arg, term *next, char *reason)
/* common denom (with factoring) and simplify the numerator */
/* When simplifying the numerator, we want content-factoring ON,
because there may well be a sizable common factor to come out.
Example: 8(2x-5)^3/(8x^2-5)^3 - 48x(2x--5)^4/(8x^2-5)^4
*/
{ term temp,num,temp2,cancelled;
int err;
int saveit = get_polyvalfactorflag();
void *savenode = heapmax();
err = commondenom(t,arg,&temp,reason);
if(err)
{ reset_heap(savenode);
return 1;
}
assert(FUNCTOR(temp) == '/');
set_polyvalfactorflag(1);
save_and_reset(temp,savenode,&temp);
err = local_polyval2(ARG(0,temp),&num);
/* but num may still need expanding--local_polyval2 doesn't do that,
and if we leave it labelled ALREADY, autosimp won't go into it. */
set_polyvalfactorflag(saveit);
if(err)
temp2 = temp;
else
temp2 = make_fraction(num,ARG(1,temp));
save_and_reset(temp2,savenode,&temp2);
err = cancel(ARG(0,temp2),ARG(1,temp2),&cancelled,next);
if(err)
*next = temp2;
HIGHLIGHT(*next);
strcpy(reason, english(758)); /* common denom and simp */
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int commondenomandsimp2(term t, term arg, term *next, char *reason)
/* common denom (with factoring) of fractions only and simplify the numerator */
{ term temp,num,denom,temp2,cancelled,temp3;
int i,err;
unsigned short n;
int saveit = get_polyvalfactorflag();
void *savenode = heapmax();
err = commondenom2(t,arg,&temp,reason);
if(err)
return 1;
if(FUNCTOR(temp) == '/')
{ err = local_polyval2(ARG(0,temp),&num);
if(err)
*next = temp;
else
{ denom = ARG(1,temp);
err = cancel(num,denom,&cancelled,next);
if(err)
*next = make_fraction(num,denom);
}
return 0;
}
assert(FUNCTOR(temp) == '+');
n = ARITY(temp);
for(i=0;i<n;i++) /* locate the one (and only one) fraction in args of temp */
{ if(FUNCTOR(ARG(i,temp)) == '/')
break;
}
if(i == n)
/* assert(0) */
return 1;
set_polyvalfactorflag(1);
save_and_reset(temp,savenode,&temp);
denom = ARG(1,ARG(i,temp));
num = ARG(0,ARG(i,temp));
err = local_polyval2(ARG(0,ARG(i,temp)),&num);
/* but num may still need expanding--local_polyval2 doesn't do that,
and if we leave it labelled ALREADY, autosimp won't go into it. */
set_polyvalfactorflag(saveit);
if(err)
temp2 = ARG(i,temp);
else
temp2 = make_fraction(num,denom);
err = cancel(ARG(0,temp2),ARG(1,temp2),&cancelled,&temp3);
if(err)
temp3 = temp2;
*next = temp;
ARGREP(*next,i,temp3);
HIGHLIGHT(ARG(i,*next));
return 0;
}
/*__________________________________________________________________________*/
static int naivecommondenom2(term t, term arg, term *next, char *reason)
/* common denom without factoring, all in one step, fractional terms of sum only */
{ int i,j;
unsigned short n; /* arity of t */
unsigned short m; /* number of fractions among args of t */
int mark; /* which arg of t is the first fraction */
int *scratchpad; /* array of 0's and 1's telling which args of t are fractions */
term temp,q;
if (FUNCTOR(t) != '+')
return 1;
n = ARITY(t);
m=0;
temp = make_term('+',n); /* stash the fractions here */
mark = -1;
scratchpad = (int *) callocate(n,sizeof(int));
for(i=0; i<n; i++)
{ if (SIGNEDFRACTION(ARG(i,t)))
{ ARGREP(temp,m,ARG(i,t));
if(mark < 0)
mark = i;
scratchpad[i] = 1;
++m;
}
}
if(m<2)
return 1; /* no fractions */
SETFUNCTOR(temp,'+',m);
/* Now there are at least two fractions, and they're all in temp */
if(m==n) /* all the terms were fractions */
{ naivecommondenom(temp,arg,next,reason);
RELEASE(temp);
free2(scratchpad);
return 0;
}
/* At least one term was NOT a fraction */
naivecommondenom(temp,arg,&q,reason);
/* q is the sum of the fractions */
*next = make_term('+',(unsigned short)(n-m+1));
/* first copy the non-fractions preceding the first fraction */
for(i=0;i<mark;i++) ARGREP(*next,i,ARG(i,t));
ARGREP(*next,mark,q); /* put the sum of the fractions where the first fraction was */
/* Finally put the rest of the non-fractions in *next */
++i;
assert(i==mark+1);
for(j=mark; j < n ; ++j)
{ if(scratchpad[j]==0)
{ ARGREP(*next,i,ARG(j,t));
++i;
}
}
free2(scratchpad); /* release temporary memory on heap */
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int commondenom(term t, term arg, term *next, char *reason)
/* common denom with factoring, all in one step, all terms of sum */
{ int i,err,sign;
unsigned short n = ARITY(t);
unsigned short g;
term temp,temp2,qq,denom,num,summand;
void *savenode;
if(FUNCTOR(t) != '+')
return 1;
savenode = heapmax();
temp = make_term('+',n);
for(i=0;i<n;i++) /* factor the denominators if possible */
{ summand = ARG(i,t);
g = FUNCTOR(summand);
if(g == '-')
{ sign = -1;
summand = ARG(0,summand);
g = FUNCTOR(summand);
}
else
sign = 1;
if(g != '/')
{ ARGREP(temp,i,ARG(i,t));
continue;
}
if(SOME_INFINITESIMAL(summand))
{ errbuf(0, english(1411));
errbuf(1, english(1412));
/* One of your denominators will evaluate to zero.
You can't use common denominators on such a fraction. */
return 1;
}
denom = ARG(1,summand);
num = ARG(0,summand);
if(!irreducible(denom)) /* for speed, don't call overlaid function 'factor'
on known-irreducible args */
{ err = factor(denom,&temp2);
if(!err)
{ ResetShowStep(); /* factor called SetShowStepOperation */
local_polyval2(temp2,&qq);
}
else
qq = denom;
}
else
qq = denom;
if(sign == -1)
ARGREP(temp,i,tnegate(make_fraction(num,qq)));
else
ARGREP(temp,i,make_fraction(num,qq));
}
save_and_reset(temp,savenode,&temp);
err = naivecommondenom(temp,arg,next,reason);
RELEASE(temp);
return err;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int commondenom2(term t, term arg, term *next, char *reason)
/* common denom with factoring, all in one step, fractional terms of sum only */
{ int i,err;
unsigned short n = ARITY(t);
term temp,temp2,qq,denom,w;
if(FUNCTOR(t) != '+')
return 1;
temp = make_term('+',n);
for(i=0;i<n;i++) /* factor the denominators if possible */
{ w = ARG(i,t);
if(FUNCTOR(w) == '/')
{ if(SOME_INFINITESIMAL(w))
{ errbuf(0, english(1411));
errbuf(1, english(1412));
return 1;
}
denom = ARG(1,w);
err = factor(denom,&temp2);
if(!err)
{ ResetShowStep(); /* factor called SetShowStepOperation */
local_polyval2(temp2,&qq);
}
else
qq = denom;
}
if(FUNCTOR(w) == '-' && FUNCTOR(ARG(0,w)) == '/')
{ denom = ARG(1,ARG(0,w));
if(SOME_INFINITESIMAL(ARG(0,w)))
{ errbuf(0, english(1411));
errbuf(1, english(1412));
return 1;
}
err = factor(denom,&temp2);
if(!err)
{ ResetShowStep(); /* factor called SetShowStepOperation */
local_polyval2(temp2,&qq);
}
else
tneg(denom,&qq);
}
else
err = 1;
if(!err)
ARGREP(temp,i,signedfraction(ARG(0,w),qq));
else
ARGREP(temp,i,w);
}
err = naivecommondenom2(temp,arg,next,reason);
RELEASE(temp);
return err;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int commondenominfraction(term t, term arg, term *next, char *reason)
{ int err;
if( !get_infractionflag())
return 1;
err = commondenom(t,arg,next,reason);
if(err)
return 1;
SetShowStepOperation(commondenom);
return 0;
}
/*_______________________________________________________________________*/
MEXPORT_ALGEBRA int apartandfactor(term t, term arg, term *next, char *reason)
/* (ab+ac+d)/q = a(b+c)/q + d/q */
/* Although this has the prototype of an operator, it is not on the
menus and is never called in auto mode */
{ term num,q,summand,a,d,temp,cancelled,bplusc;
unsigned short n,m,k;
int i,j,err;
if(FUNCTOR(t) != '/')
return 1;
num = ARG(0,t);
q = ARG(1,t);
if(FUNCTOR(num) != '+')
return 1;
n = ARITY(num);
if(n < 3)
return 1;
if(FUNCTOR(arg) != ILLEGAL)
{ a = arg; /* user has supplied it */
if(ONE(a))
{ errbuf(0, english(534));
/* You can't factor out 1. */
return 1;
}
}
else /* in automode we must find a ourselves */
/* Look for a term that isn't a product; then that term must be
the 'a' of the formula */
{ for(i=0;i<n;i++)
{ summand = ARG(i,num);
if(NEGATIVE(summand))
summand = ARG(0,summand);
if(FUNCTOR(summand) != '*' )
break;
}
if(i<n) /* found it! */
a = ARG(i,num);
else
return 1; /* no stand-alone term */
if(ONE(a))
return 1; /* reject 1 */
}
if(NEGATIVE(a))
a = ARG(0,a);
bplusc = make_term('+',n); /* later we'll reset the arity */
d = make_term('+',n);
k=m=0;
for(j=0;j<n;j++)
{ summand = ARG(j,num);
if(NEGATIVE(summand))
summand = ARG(0,summand);
err = cancel(summand,a,&cancelled,&temp);
if(err || (!FRACTION(summand) && FRACTION(temp)))
/* don't create new fractions, e.g. x^2 + xh + c => x^2(1+x/h) + c */
{ if(NEGATIVE(ARG(j,num)))
summand = ARG(j,num);
ARGREP(d,m,summand);
++m;
}
else
{ if(NEGATIVE(ARG(j,num)))
temp = tnegate(temp);
ARGREP(bplusc,k,temp);
++k;
}
}
if(k==0 && FUNCTOR(arg) != ILLEGAL) /* in menu mode */
/* no factor is divisible by the user's a */
errbuf(0, english(530));
/* Can't find the term you entered as a common factor. */
if(k==1 && FUNCTOR(arg) != ILLEGAL) /* in menu mode */
errbuf(0,english(532));
/* Only one factor is divisible by that term. */
if(m==0 && FUNCTOR(arg) != ILLEGAL) /* in menu mode */
errbuf(0, english(531));
/* Use factoring instead to take that term out. */
if(m==0 || k<2) /* whether in menu or auto mode */
/* the whole numerator has a factor of a, or
no other factor is divisible by a */
{ RELEASE(bplusc);
RELEASE(d);
return 1;
}
if(m==1)
{ temp = ARG(0,d);
RELEASE(d);
d = temp;
}
else
SETFUNCTOR(d,'+',m);
SETFUNCTOR(bplusc,'+',k);
temp = product(a,bplusc);
*next = sum(make_fraction(temp,q), signedfraction(d,q));
HIGHLIGHT(*next);
strcpy(reason,"(ac+bc+d)/q = a(b+c)/q + d/q");
PROTECT(*next); /* so common denom doesn't undo it and cause a loop;
limsum will take it apart */
return 0;
}
/*______________________________________________________________*/
static int local_polyval2(term t, term *ans)
/* don't use sqrtexp */
{ int err;
int saveit = get_polyvalfractexpflag();
int saveit2 = get_polyvalfunctionflag();
set_polyvalfractexpflag(0);
set_polyvalfunctionflag(0);
err = polyval(t,ans);
set_polyvalfractexpflag(saveit);
set_polyvalfunctionflag(saveit2);
return err;
}
/*_________________________________________________________________*/
MEXPORT_ALGEBRA int ratsimpop(term t, term arg, term *next, char *reason)
/* "eliminate compound fractions" on the menus */
/* Not called in auto mode */
{ int err;
int savecomdenomflag = get_polyvalcomdenomflag();
int savegcdflag = get_polyvalgcdflag();
if(!contains_compound_fractions(t))
return 1;
set_polyvalcomdenomflag(1);
err = polyval(t,next);
set_polyvalcomdenomflag(savecomdenomflag);
set_polyvalgcdflag(savegcdflag);
if(err)
return 1;
strcpy(reason,english(1394)); /* elim compound fracts */
HIGHLIGHT(*next);
return 0;
}
/*__________________________________________________________________________*/
MEXPORT_ALGEBRA int decimaltofraction(term t, term arg, term *next, char *reason)
/* convert a decimal number to a fraction */
/* If the decimal number is an integer, it fails, because
such numbers look the same as integers to the user.
*/
{ double z,x,intpart,scale;
int n,i,sign=1;
long num,denom;
long kk,a;
if(!OBJECT(t) || !TYPE(t) == DOUBLE)
return 1;
z = DOUBLEDATA(t);
if(z == 0.0)
return 1; /* the change would be invisible anyway */
if(z < 0)
{ z = -z;
sign = -1;
}
if(nearint(z,&kk))
return 1;
intpart = floor(z);
x = z-intpart;
if(x==0.0)
return 1; /* even though nearint failed, this could still
happen if z is an integer greater than the
largest long. */
if(intpart > 100000L)
{ errbuf(0, english(734));
/* Decimal numbers over 100,000 are assumed to be approximate, and cannot be converted to fractions. */
return 1;
}
nearint(intpart,&a);
/* Now determine the denominator */
/* First find the n such that 1/1000^(n+1) <= x < 1/1000^n */
for(n=1;n<3;n++)
{ scale = n==1 ? 1000.0 : 1000000.0; /* scale =1000^n */
if(1/scale <= x)
{ if(n == 2)
x *= 1000.0;
break;
}
}
if(n == 3)
{ errbuf(0, english(735));
/* Decimal numbers less than 0.000001 are assumed to be
approximate, and cannot be converted to fractions. */
return 1;
}
assert(0.001 <= x);
/* Check for a denominator <= 1000 */
for(i=2;i<=1000;i++)
{ if(nearint(x*i,&kk))
break;
}
if(i <= 1000)
{ denom = n == 2 ? 1000 * i : i;
num = a*denom + kk;
if(sign == 1)
*next = make_fraction(make_int(num), make_int(denom));
else
*next = tnegate(make_fraction(make_int(num), make_int(denom)));
HIGHLIGHT(*next);
strcpy(reason, english(733));
/* decimal to fraction */
return 0;
}
/* for example 1/1001 can't be converted */
errbuf(0, english(736));
errbuf(1, english(737));
errbuf(2, english(738));
/* This decimal number is not equal to any simple fraction.
MathXpert treats most decimal numbers as inexact, approximate numbers.
The manual, or Help, explains exactly which decimals can be converted to fractions.
*/
return 1;
}
/*___________________________________________________________________*/
MEXPORT_ALGEBRA int multnumanddenom(term t, term arg, term *next, char *reason)
/* mult num and denom by ? */
{ term num, denom,a,b;
if(!FRACTION(t))
return 1;
if(FUNCTOR(arg) == ILLEGAL)
return 1; /* not used in auto mode */
HIGHLIGHT(arg);
num = ARG(0,t);
denom = ARG(1,t);
a = product(arg,num);
b = product(arg,denom);
if(FUNCTOR(a) == '*')
sortargs(a);
if(FUNCTOR(b) == '*')
sortargs(b);
*next = make_fraction(a,b);
strcpy(reason, english(753)); /* mult num, denom by ? */
return 0;
}
/*_______________________________________________________________________*/
MEXPORT_ALGEBRA int pulloutdenom(term t, term arg, term *next, char *reason)
/* a/b = (1/b) a */
{ term a,b;
if(!FRACTION(t))
return 1;
a = ARG(0,t);
b = ARG(1,t);
*next = product(reciprocal(b),a);
sortargs(*next);
HIGHLIGHT(*next);
if(FRACTION(ARG(0,*next)))
strcpy(reason,"$$a/b = (1/b) a$$");
else
strcpy(reason,"$$a/b = a (1/b)$$");
return 0;
}
/*_______________________________________________________________________*/
MEXPORT_ALGEBRA int breakfraction1(term t, term arg, term *next, char *reason)
/* ab/c = a(b/c) */
{ term a,b,c,num;
unsigned short n;
int i;
if(!FRACTION(t))
return 1;
num = ARG(0,t);
if(FUNCTOR(num) != '*')
return 1;
a = ARG(0,num);
if(ARITY(num) == 2)
b = ARG(1,num);
else
{ n = (unsigned short)(ARITY(num)-1);
b = make_term('*',n);
for(i=0;i<n;i++)
ARGREP(b,i,ARG(i+1,num));
}
c = ARG(1,t);
*next = product(a,make_fraction(b,c));
strcpy(reason,"$$ab/c = a(b/c)$$");
return 0;
}
/*_______________________________________________________________________*/
MEXPORT_ALGEBRA int pulloutreal(term t, term arg, term *next, char *reason)
/* au/b = (a/b) u (real numbers a,b) */
{ term a,b,u,num,temp;
int i,k,j;
unsigned short n;
if(!FRACTION(t))
return 1;
num = ARG(0,t);
b = ARG(1,t);
if(iscomplex(b))
{ errbuf(0, english(1823));
/* Denominator is not real. */
return 1;
}
if(!iscomplex(num))
return 1;
if(FUNCTOR(num) != '*')
{ *next = product(reciprocal(b),num);
HIGHLIGHT(*next);
strcpy(reason,"$$a/b = (1/b) a$$");
SetShowStepOperation(pulloutdenom);
return 0;
}
n = ARITY(num);
a = make_term('*',n);
u = make_term('*',n);
k = j = 0;
for(i=0;i<n;i++)
{ if(!iscomplex(ARG(i,num)))
{ ARGREP(a,k,ARG(i,num));
++k;
}
else
{ ARGREP(u,j,ARG(i,num));
++j;
}
}
if(k==0)
{ RELEASE(a);
return 1;
}
if(k==1)
{ temp = ARG(0,a);
RELEASE(a);
a = temp;
}
else
SETFUNCTOR(a,'*',k);
if(j==0)
{ RELEASE(u);
return 1;
}
if(j==1)
{ temp = ARG(0,u);
RELEASE(u);
u = temp;
}
else
SETFUNCTOR(u,'*',j);
*next = product(make_fraction(a,b),u);
HIGHLIGHT(*next);
strcpy(reason,"$$au/b = (a/b)u$$");
return 0;
}
/*______________________________________________________________*/
MEXPORT_ALGEBRA int breakfraction(term t, term arg, term *next, char *reason)
/* ab/cd = (a/c)(b/d) */
/* If num or denom has arity more than 2, just pulls out the
first factors of each */
{ term num,denom,a,b,c,d;
unsigned short n;
int i;
if(!FRACTION(t))
return 1;
num = ARG(0,t);
denom = ARG(1,t);
if(FUNCTOR(num) != '*' || FUNCTOR(denom) != '*')
return 1;
a = ARG(0,num);
c = ARG(0,denom);
if(ARITY(num) == 2)
b = ARG(1,num);
else
{ n = ARITY(num)-1;
b = make_term('*',n);
for(i=0;i<n;i++)
ARGREP(b,i,ARG(i+1,num));
}
if(ARITY(denom) == 2)
d = ARG(1,denom);
else
{ n = ARITY(denom)-1;
d = make_term('*',n);
for(i=0;i<n;i++)
ARGREP(d,i,ARG(i+1,denom));
}
*next = product(make_fraction(a,c),make_fraction(b,d));
HIGHLIGHT(*next);
strcpy(reason, "$$(ab)/(cd) = (a/c)(b/d)$$");
return 0;
}
/*______________________________________________________________*/
MEXPORT_ALGEBRA int breakfraction2(term t, term arg, term *next, char *reason)
/* ab/c = (a/c)b */
/* Used in automode on e.g. sqrt(3)x/2 to get (sqrt(3)/2) x */
{ term num,a,b,c;
unsigned short n;
int i;
if(!FRACTION(t))
return 1;
num = ARG(0,t);
c = ARG(1,t);
if(FUNCTOR(num) != '*')
return 1;
if(INTEGERP(ARG(0,num)) && numerical(ARG(1,num)) &&
(FUNCTOR(ARG(1,num)) == SQRT || FUNCTOR(ARG(1,num)) == ROOT) &&
(get_ringflag() & RATRING) &&
canonical(0,ARG(1,num)) &&
ARITY(num) > 2
)
{ a = product(ARG(0,num),ARG(1,num));
if(ARITY(num) == 3)
b = ARG(2,num);
else
{ n = ARITY(num)-2;
b = make_term('*',n);
for(i=0;i<n;i++)
ARGREP(b,i,ARG(i+2,num));
}
}
else
{ a = ARG(0,num);
if(ARITY(num) == 2)
b = ARG(1,num);
else
{ n = ARITY(num)-1;
b = make_term('*',n);
for(i=0;i<n;i++)
ARGREP(b,i,ARG(i+1,num));
}
}
*next = product(make_fraction(a,c),b);
HIGHLIGHT(*next);
strcpy(reason, "$$(ab)/c = (a/c)b$$");
return 0;
}
/*______________________________________________________________*/
MEXPORT_ALGEBRA int multiplycoefs(term t, term arg, term *next, char *reason)
/* (a/b)cd = (ac/b)d */
/* Used in automode on e.g. (1/2)sqrt(3)x to get (sqrt(3)/2) x */
{ term a,b,c,d;
unsigned short n;
unsigned short path[5];
int i;
if(FUNCTOR(t) != '*')
return 1;
if(ARITY(t) < 3)
return 1;
if(!FRACTION(ARG(0,t)))
return 1;
a = ARG(0,ARG(0,t));
b = ARG(1,ARG(0,t));
c = ARG(1,t);
if(ARITY(t)==3)
d = ARG(2,t);
else
{ n = ARITY(t)-2;
d = make_term('*', n);
for(i=0;i<n;i++)
ARGREP(d,i,ARG(i+2,t));
}
*next = product(make_fraction(product(a,c),b),d);
HIGHLIGHT(ARG(0,*next));
strcpy(reason,"$$(a/b)c = (ac/b)$$");
SetShowStepOperation(multiplyfractions2);
path[0] = '*';
path[1] = 1;
path[2] = SUBRANGE;
path[3] = 2;
set_pathtail(path);
return 0;
}
/*________________________________________________________________*/
MEXPORT_ALGEBRA int negatenumdenom(term t, term arg, term *next, char *reason)
/* (a-b)/(c-d) = (b-a)/(d-c) */
/* Without this some hyperbolic trig identities that can be solved
with it in one step go on MUCH longer, introducing exponentials. */
/* In automode it is applied to an identity, so it must work on that
too, using set_pathtail. */
{ term num, denom, a,b,c,d,u,v,temp;
unsigned short path[3];
if(FUNCTOR(t) == '=')
{ u = ARG(0,t);
v = ARG(1,t);
if(!negatenumdenom(v,arg,&temp,reason))
{ *next = equation(u,temp);
path[0] = '=';
path[1] = 2;
path[2] = 0;
set_pathtail(path);
return 0;
}
return 1;
}
if(!FRACTION(t))
return 1;
num = ARG(0,t);
denom = ARG(1,t);
if(FUNCTOR(num) != '+' || ARITY(num) != 2)
return 1;
if(FUNCTOR(denom) != '+' || ARITY(denom) != 2)
return 1;
a = ARG(0,num);
b = ARG(1,num);
if(!NEGATIVE(b))
return 1;
b = ARG(0,b);
c = ARG(0,denom);
d = ARG(1,denom);
if(!NEGATIVE(d))
return 1;
d = ARG(0,d);
*next = make_fraction(sum(b,tnegate(a)), sum(d,tnegate(c)));
HIGHLIGHT(*next);
strcpy(reason,"$$(a-b)/(c-d)=(b-a)/(d-c)$$");
return 0;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists