4
$\begingroup$

I generally prefer to do all my intermediate calculations with exact numbers, and then round my result at the end. Thus I will typically convert each arbitrary-precision number to its exact equivalent; e.g., 1.7 becomes 17/10. However, the only method I've found that ensures numerically exact conversion is the manual one: delete the decimal point, and then divide by 10^z, where z is the number of digits to the right of the decimal.

For instance, consider a = 399847593.00000068. If I use the above method, I get a fraction that is exactly equal to a. Is there a command that achieves this?

Edit added for clarity: I'm not looking for exact rational representations of the computer's internal binary representation of base-10 floating point numbers; I understand the latter typically can't be exactly equal to the floating point. Rather, I'm looking for exact rational representation of the floating point numbers themselves (which the software can do, if I enter them manually), which will then effectively be carried through the entire calculation. At the end of the calculation, final cancellations, and numerical conversion to a floating point, if desired, can then be done.

I tried SetPrecision[a,Infinity] and SetAccuracy[a, Infinity], but both give fractions that are not exactly equal to a (see screenshot below). Why is this? [Edit: I've removed my speculations here, since Szabloc's answer explains the behavior of these commands.]

From the name, Rationalize might seem to be a good candidate, but Rationalize[a, 0] likewise doesn't give a numerically exact conversion, as expected from the documentation: "Rationalize[x,0] gives a rational number equivalent to x up to the precision of x." Finally, multiplying by 10^z (see first paragraph), applying Floor, and then dividing by 10^z likewise doesn't achieve the desired result:

enter image description here

{a = 399847593.00000068, Precision[a] // N}
mExact = 39984759300000068/10^8; (*manual method*)
mNum25 = NumberForm[N[mExact, 25], 
ExponentFunction -> (If[-Infinity < # < Infinity, Null, #] &)];
{mExact, Precision[mExact], mNum25}
pExact = SetPrecision[a, Infinity]; (*SetPrecision method*)
pNum25 = NumberForm[N[pExact, 25], 
ExponentFunction -> (If[-Infinity < # < Infinity, Null, #] &)];
{pExact, Precision[pExact], pNum25}
aExact = SetAccuracy[a, Infinity]; (*SetAccuracy method*)
aNum25 = NumberForm[N[aExact, 25], 
ExponentFunction -> (If[-Infinity < # < Infinity, Null, #] &)];
{aExact, Precision[aExact], aNum25}
rExact = Rationalize[a, 0]; (*Rationalize method*)
rNum25 = NumberForm[N[rExact, 25], 
ExponentFunction -> (If[-Infinity < # < Infinity, Null, #] &)];
{rExact, Precision[rExact], rNum25}
fExact = Floor[a*10^8]/10^8; (*Floor method*)
fNum25 = NumberForm[N[fExact, 25], 
ExponentFunction -> (If[-Infinity < # < Infinity, Null, #] &)];
{fExact, Precision[fExact], fNum25}
$\endgroup$
8
$\begingroup$

However, the only method I've found that ensures numerically exact conversion is the manual one: delete the decimal point, and then divide by 10^z, where z is the number of digits to the right of the decimal.

This does not produce an exact conversion because floating point numbers are represented in binary, not in decimal.

I tried SetPrecision[a,Infinity] and SetAccuracy[a, Infinity], but both give fractions that are not exactly equal to a (see screenshot below).

In fact the result of SetPrecision[..., Infinity] is the one that is exactly equal to the input. Floating point numbers are represented in binary. You can see all their digits using RealDigits[..., 2].

The following will give an exact conversion, and the same result as SetPrecision[..., Infinity] (thanks @george2079!)

exactConvert[x_Real] := FromDigits[RealDigits[x, 2], 2]

If you try it on 0.1, you don't get 1/10:

exactConvert[0.1]
(* 3602879701896397/36028797018963968 *)

SetPrecision[0.1, Infinity]
(* 3602879701896397/36028797018963968 *)

We don't get 1/10 because 1/10 is simply not exactly representable in binary. When Mathematica prints it as 0.1, it does some rounding. This already shows that it does not make sense for most use cases to do a truly exact conversion.

Rationalize returns a more reasonable result:

Rationalize[0.1, 0]
(* 1/10 *)

The important thing to keep in mind is that floating point numbers are meant as an approximate representation. For most use cases an exact conversion simply does not make sense. However, if you do want to do it, you can using the functions above.

In my opinion, Rationalize with a reasonable chosen precision makes more sense. For a machine number, a reasonable precision is something just below $MachinePrecision, i.e. about 15 digits. This is why Rationalize[0.1, 0] gives 1/10 instead of the truly exact 3602879701896397/36028797018963968.


To sum up, the key problem here is that your concept of "exactness" as taking all displayed decimal digits is simply flawed; and that true exactness is simply not useful unless you are concerned with floating point representations.

$\endgroup$
  • $\begingroup$ This seems to give the same result as SetPrecision[x,Infinity]. Are there cases where it does not? $\endgroup$ – george2079 Mar 14 '17 at 17:14
  • $\begingroup$ @Szabolics: You wrote "In my opinion, for most uses casses it does not make sense to do an accurate conversion...the proper way ...is to specify an accuracy limit that is reasonable for the problem, i.e., use Rationalize." Perhaps, but shouldn't that be my decision rather than the program's? Further, in cases where it does make sense to do an exact conversion, Rationalize doesn't allow you to specify an accuracy limit that is reasonable for the problem, since its "0" (in Rationalize[a,0] isn't really "0". $\endgroup$ – theorist Mar 14 '17 at 17:20
  • $\begingroup$ @theorist I don't understand your comment. What is it that you want to achieve that you cannot? Also, as George above pointed out, SetPrecision[x, Infinity] does in fact give you the exact result. What is then the problem? $\endgroup$ – Szabolcs Mar 14 '17 at 17:28
  • 1
    $\begingroup$ @theorist I rephrased the answer. The problem is that your concept of exactness is flawed, and true exactness is not useful (or at least I do not see how it can be useful) unless you are concerned with the precise representation of floating point numbers. If you want to study such representations, the ComputerArithmetic package is useful. $\endgroup$ – Szabolcs Mar 14 '17 at 17:49
  • 3
    $\begingroup$ @theorist As I explained in my answer, computers represent floating point numbers in binary, not decimal. At this point people usually recommend reading docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html $\endgroup$ – Szabolcs Mar 14 '17 at 17:51
2
$\begingroup$

Here are a couple ideas:

Round[a, 10^-Round[Accuracy[a]]]
FromDigits @ RealDigits[a]

1999237965000003/5000000

1999237965000003/5000000

$\endgroup$
  • $\begingroup$ These fractions are not exactly equal to 39984759300000068/100000000 = 399847593.00000068 $\endgroup$ – theorist Mar 14 '17 at 17:51
  • 1
    $\begingroup$ you can do this: Round[ 399847593.00000068`20, 10^-8] $\endgroup$ – george2079 Mar 14 '17 at 18:01
  • 2
    $\begingroup$ the fundamental problem is whatever you key in gets "almost" instantly converted to a machine number, so if you key in more digits than a machine number can hold they are just lost. See here mathematica.stackexchange.com/a/46169/2079 for an attempt at manipulating the keyed-in string before conversion. $\endgroup$ – george2079 Mar 14 '17 at 18:48
  • 2
    $\begingroup$ @theorist the backtick notation is part of the number representation and it affects the parsing of the input before evaluation takes place. But SetPrecision is not part of the number representation, it is a function which is applied during evaluation. When you enter SetPrecision[399847593.00000068, 20] the first thing that happens is the string of characters 399847593.00000068 gets interpreted as the machine precision number not quite equal to 39984759300000068/10^8. The SetPrecision function never sees the string of digits you typed in, it sees that machine precision number. $\endgroup$ – Simon Woods Mar 14 '17 at 22:51
  • 2
    $\begingroup$ I think essentially you are asking if you can enter exact numbers (rationals) using the notation for inexact numbers. The answer is no, except by intercepting the input string before it is parsed, as shown by george in the linked answer. $\endgroup$ – Simon Woods Mar 14 '17 at 22:51

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.