4
$\begingroup$

I have a number with a long decimal tail, like

x=1.23456789789456123

and I would like to (i) keep the first $n$ significant figures, and (ii) completely discard the rest. I must confess, however, that I'm struggling to do the latter; I roughly understand why Mathematica is reticent to do this but I still want it to happen.

To be a bit more specific, I have a number which I get from a randomized getter,

x = RandomReal[{0, Pi}]

and I really, really don't care about what comes after the ~fourth significant figure. Moreover, this number will (can) eventually make its way to the user via an error message such as

sample::message = "Sample message with an input number, x=`1`.";

and in this case it comes out with the entirety of its tail: calling Message[sample::message, x] returns

sample::message: Sample message with an input number, x=0.7265630732332595`.

with a big ugly decimal tail I really don't care about.

Usually, to deal with this, you either

  • use the second argument of N, which doesn't really do much as x is already in numerical form with a trailing `, or
  • run it through SetPrecision[#, 4] (say), which appends a `4 at the end of the decimal expression, which then goes on to affect how x gets displayed and how it interacts with other numbers, but which only makes its FullForm and its message form longer. Alternatively, you could also
  • run it through Round, but the second argument is an absolute number instead of a number of significant figures.

So: is there a simple, clean-cut, in-built way to disregard all the decimals after a certain point? I understand why SetPrecision does what it does (adding information instead of deleting it, and then enforcing the correct behaviour on that number) but here I'm perfectly happy with 'changing' x, much the same way that Round does.

$\endgroup$
  • 1
    $\begingroup$ I don't quite understand your objection to using Round[]: Round[1.23456789789456123, 1.*^-4] looks clean to me. You could write a wrapper that takes significant figures instead. $\endgroup$ – J. M. will be back soon May 17 '16 at 14:47
  • $\begingroup$ The problem is that I don't have a guarantee that x is between 1 and 10, and I would like to cut off at significant figures instead of at a specific decimal. If Round[#, 10^(Log10[#]-4)]& really is the best and only way then that's fair enough, but it really weirds me out that there's no way to tell N or its closer cousins to do this. $\endgroup$ – Emilio Pisanty May 17 '16 at 15:08
  • $\begingroup$ Ah, MantissaExponent[] might help you with that; this can then be combined with Round[] as seen fit. $\endgroup$ – J. M. will be back soon May 17 '16 at 15:10
  • $\begingroup$ Hmmm. Even that turns out surprisingly tricky, both using MantissaExponent and the (actually non-functional) Round hack from my comment above. The problem is that using Round[#, 0.0001]& has a tendency to return e.g. FullForm[Round[0.1234567, 0.00001]]==0.12346000000000001` . You end up needing things like Apply[N[Round[#1, 10^-4] 10^#2] &]@*MantissaExponent, which is definitely enough of a mouthful that it feels like surely there's a cleaner in-built way to do it. $\endgroup$ – Emilio Pisanty May 17 '16 at 15:34
  • $\begingroup$ If you are just dealing with a display issue use NumberForm: Off[NumberForm::sigz]; NumberForm[#, 4] & /@ (x 10^Range[0, 6]) $\endgroup$ – Bob Hanlon May 17 '16 at 16:06
2
$\begingroup$

I don't know of a built in method but the method below uses only a few lines of code to achieve this.

toPrecision[x_?NumericQ, sigFigs_Integer?Positive] :=
 Module[{y, sign, magnitudeShift},
  sign = Sign@x;
  y = x sign;
  magnitudeShift = sigFigs - Ceiling@Log10@y;
  sign Round[y 10^magnitudeShift, 1] 10^-magnitudeShift
  ]

This shifts the number so that the integer part contains the number of significant figures requested. Then rounds the shifted number to remove the decimal. Finally, it shifts the number back to its original magnitude. Of course the Sign bit is needed because of Log10.

Depending on what you pass in a rational can be returned. It is of the requested significant figures in decimal form. In these cases you can use N if you like.

toPrecision[Log[5], 2] // N
(* 1.6 *)

toPrecision[-1248768.9868, 4]
(* -1249000 *)

toPrecision[0.9384794839, 4] // N
(* 0.9385 *)

And so on.

Hope this helps.

$\endgroup$
  • $\begingroup$ That turns something with a Real head into something with a Rational head, which is not ideal, so that final N should go in the function itself. The code is pretty verbose but some reasonable golfing puts it at N[Round[10^(#2 - Ceiling[Log10[Abs[#1]]]) #1, 1] 10^(-#2 + Ceiling[Log10[ Abs[#1]]])] &, which I guess is close enough in absence of a built-in solution - but surely there's a cleaner way! $\endgroup$ – Emilio Pisanty May 17 '16 at 23:54
  • $\begingroup$ Try this one for size: toPrecision[x_?NumericQ, sigFigs_Integer?Positive] := Module[{prec = Internal`PrecAccur[x]}, N[#1 10^#2 & @@ MapAt[Round[#, 10^-sigFigs] &, MantissaExponent[x], 1], prec]] $\endgroup$ – J. M. will be back soon May 18 '16 at 0:02
  • $\begingroup$ @EmilioPisanty I find it is better to have an extra line or two of code to assist with the reader's comprehension of the algorithm than to go super sleek because I can. My aim is communication, not elegance. $\endgroup$ – Edmund May 18 '16 at 8:33

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.