2
$\begingroup$

A number like 0.644696875 is represented internally as 0.6446968749999...:

N[FromDigits[RealDigits[0.644696875, 2], 2], $MachinePrecision]
(* 0.6446968749999999 *)

So if I ask NumberForm to print this number with 8 decimals, I would expect it to be 0.64469687 and not 0.64469688 because the digit after the 87 is a 4 which is less than 5. But it is not what we get with NumberForm:

NumberForm[0.644696875, {8, 8}]
(* 0.64469688 *)

So it looks like we have here two round operations when only one was requested:

  • First Rounding: From 0.6446968749999999 to 0.644696875
  • Second Rounding: From 0.644696875 to 0.64469688

I found this while comparing to Python, which doesn't double round. This leads to a result which I believe is correct:

ExternalEvaluate["Python", "'{:.8f}'.format(0.644696875)"]
(* 0.64469687 *)

Also notice that this floating point number is stored in the same way in both systems:

Divide @@ ExternalEvaluate["Python", "0.644696875.as_integer_ratio()"] == FromDigits[RealDigits[0.644696875, 2], 2]
(* True *)

Is Mathematica double rounding? Can this be avoided while still using machine numbers?

Motivation: I'm working on making a hash on an array of floating point numbers. This calculation should be the same from Mathematica and from Python. For this I need to be able to produce the same string representation of numbers in both system. This has proven more challenging than expected.

Update: I think Mathematica is double rounding just like Java does. Please see:

Update2: I asked support about this [CASE:4304365] and they said "It does appear that NumberForm is not behaving properly".

$\endgroup$
  • $\begingroup$ Probably what you want is RealDigits[Round[u, 10^-8]]. $\endgroup$ – Alexey Popkov Sep 4 at 18:47
  • $\begingroup$ Related: "Efficient Round edition with different rounding direction." $\endgroup$ – Alexey Popkov Sep 4 at 18:48
  • $\begingroup$ @AlexeyPopkov Thanks for the info. However, I don't think that the issue is the rounding direction. For example, NumberForm[0.644696874999999947, 8] rounds to 0.64469687 and if we remove the last 7 effectively making the number smaller then the rounded version increases as NumberForm[0.64469687499999994, 8] returns 0.64469688 $\endgroup$ – Gustavo Delfino Sep 4 at 20:51
  • $\begingroup$ I didn't meant that the issue with NumberForm is the rounding direction, but if you need to reproduce the Python result exactly, then Round is probably what you need (but it may have another rounding direction). $\endgroup$ – Alexey Popkov Sep 5 at 6:04
3
$\begingroup$

I think NumberForm, being a formatting wrapper, formats the machine number using the available digits that are given to it. For your example, the available digits are:

.644696875 //FullForm

0.644696875`

So, the double rounding you're observing is due to the following:

  1. Mathematica uses a rounded representation of the machine number for display
  2. NumberForm rounds the displayed number

Misconception

I think you have a misconception about $MachinePrecision. When you do

N[FromDigits[RealDigits[0.644696875, 2], 2], $MachinePrecision]

you are actually creating an extended precision object with $MachinePrecision digits, and not a machine number. Compare:

exact = FromDigits[RealDigits[0.644696875, 2], 2];
N[exact, MachinePrecision] //FullForm
N[exact, $MachinePrecision] //FullForm

0.644696875`

0.6446968749999999470645661858725361526`15.954589770191003

Notice how the latter number has digits following the `. This is the signature of an extended precision number. If you want to create a MachinePrecision object with N, you need to use the single argument version, or you need to specify MachinePrecision and not $MachinePrecision.

Workaround

I think you can take your observations and create a workaround by first converting to an extended precision number and then using NumberForm:

myNumberForm[n_?MachineNumberQ, spec__] := NumberForm[
    SetPrecision[n, $MachinePrecision],
    spec
]

Then:

myNumberForm[0.644696875`, {8,8}]

0.64469687

$\endgroup$
  • 1
    $\begingroup$ It would be valuable to know the algorithm which Mathematica uses for creation of rounded representation of the machine numbers for display (via InputForm or FullForm). I already asked about it here: "The algorithm behind InputForm for machine numbers." $\endgroup$ – Alexey Popkov Sep 8 at 11:21
  • $\begingroup$ I'm puzzled by situations like this: NumberForm[39201.327646149446, {Infinity, 13}] returns 39201.3276461494400. Note that it has dropped the last digit 6 and hasn't rounded the last 4 to 5. Why is that? Is it expected behavior? Note that FullForm correctly returns the original number. I asked about it some time ago: "Is it possible to get the original number from DecimalForm?" $\endgroup$ – Alexey Popkov Sep 8 at 11:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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