Here's an approach that counts the involved operations (addition, multiplication) and remembers which ones of them involve floats and which ones involve only integers. This is of course a simplistic model of precision or complexity; but maybe it can be of use for you.
SetAttributes[{myplus, mytimes}, Orderless];
operations[A_] := Module[{B, R},
(* substitute integers by "i" and floats by "f" *)
B = A /. {Plus -> myplus, Times -> mytimes} /.
{i | j -> "i", x | y -> "f", _Integer -> "i", _?NumericQ -> "f"};
(* execute the formula and remember operations *)
R = Reap[B //. {myplus[a : ("i"|"f")..] :>
(Sow[p[a]];If[Union[{a}]==={"i"},"i","f"]),
mytimes[a : ("i"|"f")..] :>
(Sow[t[a]];If[Union[{a}]==={"i"},"i","f"])}][[2, 1]]];
Notice how in the definition of R
the floating-point numbers are "contagious" in that a sum or product involving at least one float results in a float.
As an example, we define the complexity of a formula as the number of multiplications involving at least one floating-point number:
complexity[A_] := Count[operations[A], t["f", __]]
Let's try it out: the three given formulas contain different numbers of floating-point multiplications:
ansA = 2 x i + 3 x j + 5 y i + 7 y j;
ansB = (2 x + 5 y) i + (3 x + 7 y) j;
ansC = (2 i + 3 j) x + (5 i + 7 j) y;
complexity[ansA]
(* 4 *)
complexity[ansB]
(* 6 *)
complexity[ansC]
(* 2 *)
We can use this complexity function in FullSimplify
to discover ansC
automatically:
FullSimplify[ansA, ComplexityFunction -> complexity]
(* (2 i + 3 j) x + (5 i + 7 j) y *)
Different choices for complexity
will give different results here, and some experimentation may be needed.
Update: more detailed complexity function
A more detailed complexity function would count the total number of binary operations that involve at least one floating-point number. For example, $x+y$ would be one such operation; $x+i$ would be one; $x+i+2=x+(i+2)$ would be one (because $i+2$ can be done in the integers); $x+y+2$ would be two floating-point operations.
complexity[A_] :=
Total[operations[A] /. (t | p)[a : ("i" | "f") ..] :>
Count[{a}, "f"] - Boole[FreeQ[{a}, "i"]]]
Try out this complexity function:
complexity[ansA]
(* 7 *)
complexity[ansB]
(* 9 *)
complexity[ansC]
(* 3 *)
FullSimplify[ansA, ComplexityFunction -> complexity]
(* (2 i + 3 j) x + (5 i + 7 j) y *)
{i, j} . {{2, 5}, {3, 7}} . {x, y}
. As usual in such expressions, the order of contraction matters. Contracting from the left allows staying in the integers longer (youransC
); contracting from the right means switching to floats right away (youransB
). So if your expression in general can be written in this form (or similar), then arguing through contraction-order will help. $\endgroup$