8

Something very strange is happening as I evaluate the Floor function:

(9.2 - 8)/1.2
Floor[(9.2 - 8)/1.2]

returns:

1.

0

so I did the following:

Floor[1]
Floor[1.0]
Floor[1.2/1.2]

returning

1

1

1

Does anybody know what is happening? Am I doing something wrong here? I also tried the following and got the same confusing result:

x = (9.2 - 8)/1.2
Floor[x]
1.

0

and

In[57]:= Floor[(10.2 - 9.0)/1.2]

Out[57]= 0

but

In[59]:= Floor[(10.4 - 9.0)/1.4]

Out[59]= 1

I'm getting so confused that sometimes I doubt myself when doing this simple operations in my head... Any information about this will be highly appreciated.

lericr
  • 27,668
  • 1
  • 18
  • 64
Sofi Ixchel
  • 161
  • 3
  • 4
    Examine the output of (9.2 - 8)/1.2 // FullForm. – Carl Woll Mar 11 '21 at 21:48
  • 2
    You might find (9.2 - 8)/1.2 // FullForm illuminating—Mathematica does this occasionally annoying thing where it rounds numerical output in output cells by default. As to why (9.2 - 8)/1.2 is less than one, I'm not totally sure beyond "implementation details", and that's still worth getting an explanation for. EDIT: I see @CarlWoll beat me to it by 15 seconds... :) – thorimur Mar 11 '21 at 21:49
  • Oh, so it's related to the order of evaluation... Thank you very very much @CarlWoll and @thorimur, I think Round[x,0.1] will do the trick for me :D – Sofi Ixchel Mar 11 '21 at 21:58
  • Welcome to Mathematica.SE! I suggest the following: 1) As you receive help, try to give it too, by answering questions in your area of expertise. 2) Take the tour! 3) When you see good questions and answers, vote them up by clicking the gray triangles, because the credibility of the system is based on the reputation gained by users sharing their knowledge. Also, please remember to accept the answer, if any, that solves your problem, by clicking the checkmark sign! – Michael E2 Mar 12 '21 at 05:28

3 Answers3

8

As it was suggested by @CarlWoll and @thorimur in the comments, I evaluated:

(9.2 - 8)/1.2 // FullForm

which returns:

0.9999999999999994`

I'm not really sure why, but I suspect that this is related to the order in which Mathematica evaluates each operation. Since 9.2/1.2 and 8/1.2 have no finite decimal expansion, apparently Mathematica truncates this expressions and rounds them when showing the final answer. So I decided to round them myself before plugging them in the Floor function as follows:

Floor[Round[(9.2 - 8)/1.2, 0.1]]

resulting in:

1

I still have no clue why Mathematica would distribute the 1/1.2 into the subtraction apparently ignoring the parenthesis, but I suppose it's usually better when working with large numbers...

Sofi Ixchel
  • 161
  • 3
  • 3
    It's not that it's distributing the 1.2, it's that 9.2 - 8 is slightly less than 1.2 when using machine precision numbers. Try just 9.2 - 8//FullForm and you'll get 1.1999999999999993. If you use Round (as you already did) or Rationalize to get exact numbers, or enter exact numbers yourself (Floor[(92 - 80)/12]), Mathematica will be able to return the correct value. – MassDefect Mar 12 '21 at 04:51
  • The Floor[Round[...]] trick could give wrong results for values which actually are something .999999 ish – მამუკა ჯიბლაძე Apr 04 '23 at 07:27
  • 6
    It's been said many times, but maybe saying it a different way will help. This doesn't really have anything to do with Mathematica. Floating point arithmetic is done with a representation of numbers and associated functions that are not intuitive (details don't matter to make my point). As soon as you enter a finite precision number like 9.2 you have entered this world, and you must understand and accept the limits of precision in your results. Nothing about this is wrong or even particularly mysterious. – lericr Apr 04 '23 at 17:17
  • If you want infinite precision, you need to introduce it early (what "early" means depends on context). For example: Floor[(Rationalize[9.2] - 8)/Rationalize[1.2]]. You can't recover infinite precision later. Well, you can impose it, but it may be too late to get the results that match your intuition. – lericr Apr 04 '23 at 17:17
  • Ah, I see @Roman has provided a nice explanation of the details. – lericr Apr 04 '23 at 17:19
8

Machine-precision numbers are stored internally by a 64-bit pattern encoding the IEEE 754 format. Each bit pattern describes a rational number of the form $\pm m\times 2^{e}$ where $m$ is the mantissa or significand and $e$ is the binary exponent. To see which rational number is encoded in a given machine-precision number, we can set the precision to infinity:

SetPrecision[9.2 - 8, Infinity]
(*    337769972052787/281474976710656    *)

SetPrecision[(9.2 - 8)/1.2, Infinity] (* 9007199254740987/9007199254740992 *)

The latter number is clearly less than 1.

Digging a bit deeper, things clarify when we look at the involved numbers in their hexadecimal representations:

SetPrecision[9.2, Infinity] ==
  2^-48*FromDigits["9333333333333", 16]
(*    True    *)

SetPrecision[9.2 - 8, Infinity] == 2^-48FromDigits["1333333333333", 16] ( True *)

SetPrecision[1.2, Infinity] == 2^-52FromDigits["13333333333333", 16] ( True *)

Note that the representation of 9.2 - 8 has one fewer digits in the periodic expansion than the representation of 1.2. This comes from the cancellation and error amplification when subtracting two large numbers (here, 9.2 and 8) to form a smaller number (here, 1.2).

Another way of seeing IEEE 754 floating-point numbers is as intervals: for example, the machine-precision number 9.2 is actually the interval of size $2^{-48}$ centered at $2589569785738035\times2^{-48}$: it is $9.2=(2589569785738035\times2^{-48})\pm2^{-49}$. The interval's size is given by the number of binary digits that we can store in the mantissa. Subtracting 8 (an exact number) from this number gives $9.2-8=(337769972052787\times2^{-48})\pm2^{-49}$; notice that the interval's size has not changed. This contrasts with the number $1.2$, which can be expressed as the interval $1.2=(5404319552844595\times2^{-52})\pm2^{-53}$ and has a 16-fold smaller interval size.

Roman
  • 47,322
  • 2
  • 55
  • 121
  • 1
    Hmmm I should probably open a separate question for that, but there are more involved cases. The reason I actually revived this question is that I needed ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio]. This is actually 4, but Floor[N[ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio]]] returns 3, FullForm[N[ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio]]] being `3.9999999999999996`` – მამუკა ჯიბლაძე Apr 04 '23 at 17:23
  • 1
    You may have seen the result displayed as 3, but the result "returned" was the FullForm result (which is incredibly close to 4). FWIW, the result displays as 4. on my system. If you didn't want approximate results, then you shouldn't have applied N. If you needed something even closer, you can pass a second argument to N to indicate the desired precision. – lericr Apr 04 '23 at 17:29
  • Oh, sorry, I missed the Floor. Yes, my system also displays 3 after the Floor. – lericr Apr 04 '23 at 17:30
  • (1) Could add numeric fuzz on the order of say 10x machine precision. `In[5]:= Floor[ N[ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio]] + 10^(-MachinePrecision + 1)]

    Out[5]= 4`

    – Daniel Lichtblau Apr 04 '23 at 17:48
  • (2) Might be smarter though to use the fuzz for a relative rather than absolute error, like so. `In[14]:= floorWithFuzz[n_] := Floor[n(1 + 1010^(-MachinePrecision))]

    In[15]:= floorWithFuzz[(9.2 - 8)/1.2]

    Out[15]= 1

    In[16]:= floorWithFuzz[N[ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio]]]

    Out[16]= 4`

    – Daniel Lichtblau Apr 04 '23 at 17:48
  • 2
    Maybe all this is an XY problem. What are you trying to achieve with Floor[N[...]]? If you are trying to prove that the expression is equal to 4, then PossibleZeroQ[ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio] - 4] returns True. Invoking machine precision is a terrible idea for such checks. – Roman Apr 04 '23 at 18:04
  • @DanielLichtblau Could you say this in an answer? – მამუკა ჯიბლაძე Apr 04 '23 at 18:51
  • 1
    @მამუკაჯიბლაძე You can also calculate how far a given expression is from the nearest integer, instead of using Floor and N. For example, with a = ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio] you can compute Mod[a, 1, -1/2] // N giving $-4.44089\times10^{-16}$: the number $a$ is just a tiny bit smaller than the nearest integer. Naturally, for some expressions you'll get "slightly larger" and for others you'll get "slightly smaller", depending on the exact calculation details. But Floor treats these two cases very differently, which makes it an unstable operation. – Roman Apr 04 '23 at 20:20
  • @Roman I literally need Floor of the expression ArcSinh[Sqrt[5] n/2]/Log[GoldenRatio], as a function of natural number n, not nearest integer. This occurred in connection with A072649. Could you explain why invoking MachinePrecision is harmful in such cases? Also, what do you mean by XY problem? – მამუკა ჯიბლაძე Apr 05 '23 at 06:32
  • An XY problem describes the situation where you ask about fixes to your proposed solution instead of asking about how to solve your problem. Quoting from the link: "You are trying to solve problem X, and you think solution Y would work, but instead of asking about X when you run into trouble, you ask about Y." Here you are trying to describe OEIS A072649 (X, "the problem") but you are asking about the interaction between machine-precision numbers and the Floor operator (Y, "the solution"). – Roman Apr 05 '23 at 08:58
  • @მამუკაჯიბლაძე Machine-precision numbers are a surprisingly difficult topic of numerical analysis. It is very easy to get into situations where unexpected results occur. In essence, when using machine-precision numbers, we have to be very careful to use only operations and criteria that are robust to the differences between ideal numbers and their IEEE 754 representations. Machine-precision numbers are a convenient heuristic for fast calculations, not a mathematical tool for proofs (unless you REALLY know what you're doing). – Roman Apr 05 '23 at 09:02
  • 1
    Here's a solution that avoids machine precision and avoids Floor: define a = ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio] and b = Round[a] (noting that $b=4$ is evaluated exactly and accurately), then check that FullSimplify[a == b] returns True. No approximations involved. – Roman Apr 05 '23 at 09:06
  • That FullSimplify might or might not use numeric methods. If it invokes PossibleZeroQ on the difference then the likelihood shoots up considerably. – Daniel Lichtblau Apr 05 '23 at 13:33
  • @DanielLichtblau numerical methods, yes; but probably not something as limiting as machine-precision, I presume? – Roman Apr 05 '23 at 16:20
  • Good point @Roman. Correct, most definitely not machine arithmetic. – Daniel Lichtblau Apr 05 '23 at 16:26
3

One can finesse this for machine numbers by allowing some relative error to handle the fact that (i) decimals do not always have exact finite binary expansions and (ii) finite precision arithmetic can have roundoff and/or cancellation error. I use an order of magnitude above machine precision in the code below, but easy to reconfigure e.g. as a Tolerance option.

floorWithFuzz[n_] := Floor[n*(1 + 10*10^(-MachinePrecision))]

Here are two examples that have appeared in this thread and comments.

floorWithFuzz[(9.2 - 8)/1.2]

(* Out[15]= 1 *)

floorWithFuzz[N[ArcSinh[Sqrt[5] 3/2]/Log[GoldenRatio]]]

(* Out[16]= 4 *)

Daniel Lichtblau
  • 58,970
  • 2
  • 101
  • 199
  • This works until it doesn’t, upon which the user is even more confused. – Roman Apr 05 '23 at 13:04
  • @Roman I doubt anyone would use this without recognizing there have to be discontinuities. Same as with Chop. Stated slightly differently, you can’t have a smooth transition for a discontinuous function. Which is one reason some functions have a Tolerance option. – Daniel Lichtblau Apr 05 '23 at 13:25
  • @Roman Dear contributors, in a day I have to assign the bounty, and I am still perplexed. This answer offers a solution, although your discussion in comments seems to suggest it is not perfect. Another answer contains much more information and it seems to suggest that there is no solution. Am I right? Could you help me to decide? – მამუკა ჯიბლაძე Apr 10 '23 at 10:14
  • 1
    Is there no option simply to not assign the bounty? – Daniel Lichtblau Apr 10 '23 at 15:01
  • 1
    @DanielLichtblau Yes, a bounty can remain unassigned: "If, after the end of the bounty period, a question has no answers, the bounty will expire and the reputation will disappear. Part of what you're 'paying for' with a bounty is for higher question visibility and increased answerer motivation. A bounty does not guarantee a response and is not refunded if none are received." – Roman Apr 10 '23 at 15:05
  • @Roman My problem is that I rather think both of these answers deserve it, I just cannot decide which one to choose! – მამუკა ჯიბლაძე Apr 10 '23 at 17:32
  • OK I made the decision: one of my own comments helped me out. Let me quote myself - "Imo informativeness is still highest priority. Answers are all here, readers can compare them and judge by themselves." So I choose Roman's answer. Sorry Daniel, I wish I could assign it to both. – მამუკა ჯიბლაძე Apr 10 '23 at 17:37
  • No apology needed, it’s a fine choice. – Daniel Lichtblau Apr 10 '23 at 17:50
  • 1
    Thanks @მამუკაჯიბლაძე. I'll pass these points onto another bounty. Cheers! – Roman Apr 10 '23 at 17:50