9

I'm working in Mathematica 13.0 and I type

a=0.00001;
IntegerPart[a/a]

and well, the output is 0.

Try with almost any other value of a, say a=0.01, and the result is correctly 1. I did some trials and I found that it fails again with a=0.000000001. I also found that a possible fix is

a=0.00001 // Rationalize;

(then I got tired). What is going on? Can this have to do with the MachinePrecision?

More in general, how do I fix it once and for all for the entire Notebook?

sonarventu
  • 465
  • 2
  • 9
  • 4
    RealDigits[a/a] => {{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, 0} In the memory this is a number less than 1. Yes it has to do with machine epsilon – Kirill Belov Aug 08 '22 at 14:31
  • 2
    Also interesting example:

    arr = RandomReal[1, 100000]; Tally[RealDigits /@ (arr/arr)] => {{{{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}, 0}, 15338}, {{{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 1}, 84662}}. ~15% numbers giving not 1 when you try to divide number on itself

    – Kirill Belov Aug 08 '22 at 14:38
  • 4
    Try IntegerPart[Divide[a, a]]. It has been noted before on this site that "a / a" is parsed as Times[a, Power[a, -1]]. The roundoff error in 1 / a = 99999.99999999999 means a * (1/a) does not cancel (and sometimes doesn't for other values of a). – Michael E2 Aug 08 '22 at 14:42
  • Try to change precision by hand: a = 0.00001`16; IntegerPart[a/a] => 1. Also you can use SetPrecision – Kirill Belov Aug 08 '22 at 14:44
  • 4
    @KirillBelov You might be interested in this: Table[a = 10.`16^-k; IntegerPart[a/a], {k, 100}]. The use of arbitrary-precision means more bits than in machine precision are used to represent the floating-point numbers (plus precision tracking, which is unimportant here). All the extra precision does is change where the problem occurs. – Michael E2 Aug 08 '22 at 14:58
  • Yes @MichaelE2, I had exactly the same feeling – sonarventu Aug 08 '22 at 15:47
  • 2

1 Answers1

11

[H]ow do I fix it once and for all for the entire Notebook?

I suppose it depends on how you want to treat results that contain round-off error. You cannot really get rid of the problem, only shift where the problem arises. For instance, in IntegerPart[x], IntegerPart (nor usually the program/programmer) knows at that point whether the roundoff error that led to x is positive, negative, or zero. If you fix IntegerPart[x] when x comes from a/a and is one bit less than 1., then it will be broken when another x has a roundoff error that increases it to one bit less than 1.. One may be willing to live with it being broken in this way. Here's a way based on Equal comparing with tolerance:

intPart // ClearAll;
intPart[x_?NumericQ] /; x == Round[x] := Round[x];
intPart[x_?NumericQ] := IntegerPart[x];

intPart[a/a] (* 1 *)

Make it Listable if your code relies on IntegerPart being listable. You can change the tolerance used by Equal with Internal`$EqualTolerance.

Another approach is to "correct" real numbers that are close to integers:

snapToInteger // ClearAll;
snapToInteger[expr_] := 
 expr /. {x_Real /; x == Round[x] :> 
    SetPrecision[Round[x], Precision[x]]};

IntegerPart[snapToInteger[a/a]] (* 1 *)

Either approach is likely to incur a performance hit, which may or may not be important.

For what it's worth, this works on x = a/a, because of the nature of the rounding error and how MatchQ works on machine reals and bignums, and it's a bit faster:

intPart // ClearAll;
intPart[x_Real] /; MatchQ[x, N[Round[x], Precision[x]] := 
  Round[x];
intPart[x_?NumericQ] := IntegerPart[x];
Michael E2
  • 235,386
  • 17
  • 334
  • 747
  • 1
    I have to say that using Divide[a/a], as you suggested under my question, is also (often) a quick and valid fix. – sonarventu Aug 10 '22 at 09:10
  • 1
    @DavideVenturelli You may have noticed that if x is a symbol (without a numeric or other value), Divide[x, x] evaluates to x/x, that is, Times[x, Power[x, -1]]. Sometimes it's not so easy to replace / by Divide[], and since there was no response to my comment, I thought it might not work in all your use-cases. – Michael E2 Aug 10 '22 at 13:18