1
$\begingroup$

Consider the function

f[a_] := NIntegrate[Sqrt[a + Log[x]], {x, 1, 10}, WorkingPrecision -> 30].

With this definition, I cannot call f[0.1] (for example) without Mathematica throwing an error, since "0.1" is interpreted automatically as a machine number with MachinePrecision (about 16), whereas the integration requires 30 digits of precision.

But when I enter 0.1, of course I really mean 1/10. One solution is to enter f[1/10], but this is very inconvenient to do every time, especially because my real functions involve many parameters with long strings of decimal numbers. What's the best way around this issue? Ideally, I'd like Mathematica to interpret 0.1 as 1/10 automatically, as mentioned in the title.

EDIT

To clarify, I would like any decimal number input be treated as the exact mathematical number with the same name. For example, 0.### should become ###/1000. As pointed out in the comments, Rationalize converts 0.33333333 to 1/3, which I would consider a problem for my case. I would like Mathematica to treat decimal number input as exact numbers without rounding to any finite precision, nor assuming that I intended a "close" but different rational number.


Another option would be to change the function definition:

f[a_] := NIntegrate[Sqrt[Rationalize[a] + Log[x]], {x, 1, 10}, WorkingPrecision -> 30]

or

f[a_] := NIntegrate[Sqrt[SetPrecision[a, \[Infinity]] + Log[x]], {x, 1, 10}, WorkingPrecision -> 30]

But this too would be quite inconvenient to do for every function I use. I also worry about the fact that SetPrecision[0.1, \[Infinity]] does not give 1/10. So all this makes me wonder what the "right" method is.

$\endgroup$
9
  • $\begingroup$ You could use $PreRead to replace "0.1" with RowBox[{"1", "/", "10"}], I guess. For my money, I would personally go with entering 1/10. Your comment about it suggests you might not just be worried about 0.1 but other decimal numbers. It's unclear to me which ones I should worry about, though. Perhaps instead of $PreRead$, you could use $Pre to replace real numbers by their rationalizations. $\endgroup$
    – Michael E2
    Commented Aug 20, 2020 at 23:42
  • $\begingroup$ @MichaelE2 Yes, I was using 0.1 as an example, but in reality there are many arbitrary decimal values I may want to enter. $\endgroup$
    – WillG
    Commented Aug 20, 2020 at 23:45
  • $\begingroup$ Do you want all decimal input to be converted to decimal fractions? $\endgroup$
    – Michael E2
    Commented Aug 20, 2020 at 23:48
  • $\begingroup$ Yes, so for example, 0.### would be converted to ###/1000. $\endgroup$
    – WillG
    Commented Aug 20, 2020 at 23:49
  • $\begingroup$ Even 0.3333333333333 should not become 1/3 as happens when you feed it to Rationalize? (Maybe this should be clarified in the question, and not just left in the comments.) $\endgroup$
    – Michael E2
    Commented Aug 20, 2020 at 23:50

4 Answers 4

2
$\begingroup$
Clear["Global`*"]

Since f makes use of a numeric technique, the argument a should be restricted to being numeric. Set the precision of a to at least the WorkingPrecision used in NIntegrate.

f[a_?NumericQ, wp : _Integer : 30] := Module[{ap = SetPrecision[a, 
  Max[wp, Precision[a]]]},
  NIntegrate[Sqrt[ap + Log[x]], {x, 1, 10}, WorkingPrecision -> wp]]

f[1/10]

(* 11.3397714471112499234083518037 *)

f[0.1]

(* 11.3397714471112499447483664367 *)

f[0.1, 20]

(* 11.339771447111249944 *)
$\endgroup$
6
  • $\begingroup$ This has the unfortunate problem that SetPrecision[0.1,30] does not give 0.1 (with 29 zeros), but rather 0.100000000000000005551115123126. Whereas I need 0.1 to be 0.1 $\endgroup$
    – WillG
    Commented Aug 21, 2020 at 3:15
  • $\begingroup$ Then use Rationalize[#, 0] & $\endgroup$
    – Bob Hanlon
    Commented Aug 21, 2020 at 3:21
  • $\begingroup$ The issue with Rationalize, I'm finding, is that it refuses to work on decimals that are too "messy." For example Rationalize[0.362861] just returns the input, valid only to MachinePrecision. Try Rationalize[0.362861]~SetPrecision~50 and you'll see what I mean. $\endgroup$
    – WillG
    Commented Aug 21, 2020 at 4:55
  • 1
    $\begingroup$ That is what the 0 is for in my earlier comment, i.e., Rationalize[0.362861, 0] or 0.362861//Rationalize[#, 0] & $\endgroup$
    – Bob Hanlon
    Commented Aug 21, 2020 at 4:58
  • $\begingroup$ Ah, now I see. So this method will definitely work. I'm still curious to know whether this could be done automatically for all numerical inputs, using $Pre, as Michael E2 suggested in a comments. $\endgroup$
    – WillG
    Commented Aug 21, 2020 at 5:01
2
$\begingroup$

This will do what you're after:

rd = With[{t = RealDigits[#][[1]]},,FromDigits[t]/10^Length@t] &;

E.g.:

rd[0.12345675785894491727276262524416723451]

12345675785894491727276262524416723451/100000000000000000000000000000000000000

Compare:

Rationalize[0.12345675785894491727276262524416723451, 0]

2986272931814910240/24188817069267814669

Specifying precision as part of the input can address this:

Rationalize[0.12345675785894491727276262524416723451`100, 0]

12345675785894491727276262524416723451/100000000000000000000000000000000000000

I suppose a simple wrapper could be used to automate the conversion when calling your functions.

$\endgroup$
2
  • 1
    $\begingroup$ Oh. I didn't see this before I added my lame alternative (now rolled back)... +1 -- This is shorter: rd = FromDigits@*RealDigits -- @WillG, this will work with $Pre. E.g. $Pre = rd; $\endgroup$
    – Michael E2
    Commented Aug 21, 2020 at 19:06
  • $\begingroup$ @MichaelE2 - ah, well done - I'd forgotten that the output of realdgits can directly be used with fromdigits. $\endgroup$
    – ciao
    Commented Aug 22, 2020 at 1:17
1
$\begingroup$

Here's a $PreRead approach:

$PreRead = # /. 
    s_String /; StringMatchQ[s, NumberString] :> 
     With[{pos = StringPosition[s, "."]},
      RowBox[{StringDrop[s, First[pos]], "/", 
         10^(StringLength[s] - pos[[1, 1]])}]
       /; Length[pos] === 1] &;

(* new Input cell *)
0.231 + 27 Exp[10.856 x]
(*  231/1000 + 27 E^(1357 x/125)  *)
(* new Input cell *)
$PreRead =.    (* reset *)

%% // N
(*  0.231 + 27. 2.71828^(10.856 x)  *)

Gratuitous remark: I never use $PreRead (anymore). I think the way it can mess with the input is a potential headache and, in this case, I'd rather put up with the inconvenience of typing 10856/1000.

$\endgroup$
1
  • $\begingroup$ Note that this works because you parse the string. One cannot recover exact decimal input after conversion to approximate binary, so Rationalize-based approaches cannot do exactly what the OP asks for. $\endgroup$
    – John Doty
    Commented Aug 21, 2020 at 14:24
0
$\begingroup$

Exact conversion to the underlying IEEE-754 machine-precision number can be done with SetPrecision[x,∞]:

x = 1/3 // N
(*    0.333333    *)

SetPrecision[x, ∞]
(*    6004799503160661/18014398509481984    *)

So you see that $1/3$ is represented internally as $6\,004\,799\,503\,160\,661\times2^{-54}$.

$\endgroup$
2
  • $\begingroup$ The OP want the denominator to be a power of ten, not a power of two. $\endgroup$
    – Michael E2
    Commented Aug 21, 2020 at 12:46
  • $\begingroup$ @MichaelE2 I couldn't quite figure out what the OP wants. Rounding to the nearest decimal is a tricky operation and has been discussed a lot in the past. $\endgroup$
    – Roman
    Commented Aug 21, 2020 at 16:53

Your Answer

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

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