4
$\begingroup$

I'm running a program that evaluates equalities after I substitute all symbols for numbers. The equalities on floats are giving me trouble. My program is reaching a point where something like the following is being evaluated: 2.304793200735844`*^-8 == 2.30479302310016`*^-8 . This gives False, despite the difference being 1.77636*10^-15, which is below the Chop threshold of 10^-10. Running Chop on both numbers obviously doesn't fix the problem, since neither number is individually below the threshold.

Chop[2.304793200735844`*^-8 - 2.30479302310016`*^-8 ]==0 gives the correct result, but I can't easily make this change as not all comparisons are between floats or even lists of floats (Chop[a-b] == 0 or {0,0,0,...} is not always a valid way to test equality between a and b).

I want to avoid overcomplicating my program: I don't want to do a ReplaceAll on a_==b_ /; (a and b are numbers) -> Chop[a-b]==0 as this is very complicated, requires something like Hold, and doesn't resolve the same exact issues which arise in the case of float inequality comparisons. There's gotta be a better way! If there's not, what's the best solution?


EDIT: My solution was to modify Equal (==) for float comparison, and to update List equality to use this new float equality. It's extremely simple, but I think it might slightly slow things down:

Unprotect[Equal]; (* this lets us edit Equal (==) *)
Equal[a_Real,b_Real]:=Chop[a-b]==0  (* change comparison for reals *)
Equal[a_List?RealQ,b_List?RealQ]:=
  Dimensions@a==Dimensions@b&&AllTrue[MapThread[#1==#2&,{a,b}],TrueQ] (* update comparison for real lists to use the new real comparison *)
Protect[Equal];

(* True iff r is a real or any nested List containing only reals *)
RealQ[r_]:=Developer`MachineRealQ[r]||Developer`RealQ[r]||(MatchQ[r,_List]&&AllTrue[r,RealQ]);
$\endgroup$
3
  • 2
    $\begingroup$ From the documentation for Equal, "Approximate numbers with machine precision or higher are considered equal if they differ in at most their last seven binary digits (roughly their last two decimal digits)." If you want a different tolerance, use Abs[x1 - x2] < tolerance $\endgroup$
    – Bob Hanlon
    Mar 8, 2022 at 4:05
  • 3
    $\begingroup$ @Bob's rec is a standard floating-point programming approach for absolute error. For relative error, use something like Abs[x1 - x2]/Max@Abs[{x1,x2}] < relTolerance. This last may be programmed in Mathematica by setting Block[{Internal`$EqualTolerance = Log10[2*relTolerance/$MachineEpsilon]}, x1 == x2]. Or Block[{Internal`$EqualTolerance = Log10[2*relTolerance/$MachineEpsilon]}, code] in which each == between real numbers will be compared with relTolerance relative tolerance. For your example numbers, relTolerance should be about 7.70723 * 10^-8 or greater. $\endgroup$
    – Michael E2
    Mar 8, 2022 at 5:03
  • 3
    $\begingroup$ See mathematica.stackexchange.com/a/35094/4999 and related Q&A $\endgroup$
    – Michael E2
    Mar 8, 2022 at 5:19

1 Answer 1

5
$\begingroup$

I put this in a comment, but then noticed the OP had answered in the question. The relative error of the example numbers is 7.707229332944118`*^-8, which is pretty high, imo. Nonetheless, there is a way to handle relative error: Internal`$EqualTolerance. To set it to how many bits to ignore -- it's really Log10 of 2^bits -- compare it the relative tolerance to one ulp = $MachineEpsilon/2 (roughly).

Block[{Internal`$EqualTolerance = 
   Log10[2*7.707229332944118`*^-8/$MachineEpsilon]},
 2.304793200735844`*^-8 == 2.30479302310016`*^-8]

(*  True  *)

Block[{Internal`$EqualTolerance = 
   Log10[1.999999*7.707229332944118`*^-8/$MachineEpsilon]},
 2.304793200735844`*^-8 == 2.30479302310016`*^-8]

(*  False  *)
$\endgroup$
3
  • $\begingroup$ Great! So the answer is Block[{Internal`$EqualTolerance=Log10[2^(number of ignored bits)/$MachineEpsilon]}, ...]? $\endgroup$ Mar 8, 2022 at 6:07
  • 2
    $\begingroup$ @AndrewP. Rather, Block[{Internal`$EqualTolerance=Log10[2^(number of ignored bits)]}, ...]. I coded Block[{Internal`$EqualTolerance=Log10[2*relTolerance/$MachineEpsilon]}, ...]. The $EqualTolerance (= number of ignored digits) is set equal to the difference between the logs of the relative tolerance and one ulp. If you know how many bits or digits to ignore, you don't need to bother with $MachineEpsilon. (And if you do happen to use arbitrary-precision numbers then the epsilon depends on the Precision[] of the numbers.) $\endgroup$
    – Michael E2
    Mar 8, 2022 at 6:45
  • $\begingroup$ Thanks so much - this is really helpful. Is there a place to find additional definitions of this stuff? I can't find any on the Mathematica website. $\endgroup$ Mar 8, 2022 at 6:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.