Permanently rounding numbers/values to a given number of significant figures

4

5

I am searching for a simple way of rounding and setting an input to a desired number of significant figures. There are a vast number of posts that discuss similar problems, but none address this issue. I turned to writing my own function. Here it is below:

SigFig[x_, n_] :=
  (p := Solve[10^i <= Abs[x] < 10^(i + 1), i, Integers][[1, 1, 2]];
  N[Round[x, 10^(p - n + 1)], n]) // Quiet

SigFig rounds any real number x (of magnitude greater than or equal to 1) to a specified number of significant figures n.

Obviously the biggest flaw in this function is not catering for numbers of magnitude less than 1, and thus also the problem of preceeding insignifcant zeroes such as 0.00123. Admittedly it works for my specific application as all my numbers are between 1 and 10000; so honestly I can't be bothered trying to make it universal due to time constraints.

So, is there a simple pre-defined function or simple way of doing such a simple task that I have completely missed? I ultimately would like to know of a simple significant figure rounding function that works all the time and is much less complicated than what I've given here. I believe that if no such solution exists, then (1) a solution for this task needs to be documented so that people can search for it and (2) the shortcoming in lack of said function needs to be addressed to Wolfram Research. Does anyone have any suggestions on a pre-defined, pre-established or a custom function of their own that improves upon my current suggestion?

George Papadopoulos

Posted 2015-01-25T04:11:53.920

Reputation: 43

Is there anything wrong with using N[#,3]&? http://reference.wolfram.com/language/ref/N.html?q=N

– Cameron Murray – 2015-01-25T04:52:19.963

Sorry I don't understand what you mean, can you explain in more detail? – George Papadopoulos – 2015-01-25T05:14:05.250

In my opinion your question would be much improved if you were to edit out the ranting and just present the numerics problem you want to address. – m_goldberg – 2015-01-25T05:14:58.310

2If you compute N[Pi,3] you'll get 3.14. But copy that output and paste it back into an input field to see that many more digits are retained. This is a perennial annoyance with Mathematica, and documented many places. – David G. Stork – 2015-01-25T05:15:50.230

Just so we are all clear, do you know the difference between the precision of reals used in Mathematica calculations and the print precision used for display? – Mike Honeychurch – 2015-01-25T05:16:10.147

Have you looked at NumberForm?

– m_goldberg – 2015-01-25T05:25:53.057

@m_goldberg sorry, fair point, done. Was that the reason for the negative downvote on my question?

@DavidG.Stork I have been searching but have not found it documented, only people trying to work around it, not blatantly stating that "this is a perennial annoyance with Mathematica". Thanks for letting me know of the persisting issue though.

@MikeHoneychurch Yes I know the difference, and that is why the use of N is useless.

@m_goldberg yes I have, but the output is not something that can be worked with mathematically so that's also useless for computational purposes. – George Papadopoulos – 2015-01-25T05:51:43.903

I down voted because of the ranting. Now that it's gone, I removed my down vote. – m_goldberg – 2015-01-25T06:25:31.187

I must say the way you have staged your question it means that nearly every system has problems, as you would need to use fixed point arithmetic. – joojaa – 2015-01-31T09:58:37.273

@joojaa actually I think that the selected answer works in all cases. – George Papadopoulos – 2015-02-06T02:05:26.007

Answers

6

Please tell me if this simplified function does what you want:

f[x_, n_] := Round[x, 10^(1 - n + ⌊ Log10 @ Abs @ x ⌋)] ~SetPrecision~ n

Test:

Table[f[x*Pi, 4], {x, {1/100, 1/10, 1, 10, 100}}]

% // FullForm
{0.03142, 0.3142, 3.142, 31.42, 314.2}

List[0.03142`4., 0.3142`4., 3.142`4., 31.42`4., 314.2`4.]

Update

The OP wrote:

I understand that there is a difference in the 'implied precision' between the number 0.5 and 1/2 when entered in Mathematica. But my request is to perform a very simple calculation: consider the number 1.004 and double it. The answer is 2.008. Then round it to 3 sig. fig, the answer is 2.01. Take that number, divide it by two/multiply by half/multiply by 0.5 (mathematically equivalent). The mathematical answer is 1.005. I did not ask to round the final answer to 3 sig. fig. as that could be done by doing f to the final answer. Is this possible?

I suspect that I am failing to comprehend the needs that are behind this request and as such that my recommendations may be inadequate or inappropriate. However I am trying both to understand and to help, so I shall venture forward.

When performing the following operations:

1.004*2
f[%, 3]
x = %/2
2.008

2.01

1.01

The result is as desired except in the output formatting; the underlying value of x is correct as can be seen with FullForm:

FullForm[x]
1.005`3.

Increasing its precision also results in all four digits being formatted in output:

SetPrecision[x, 4]
1.005

If this is not an acceptable method then perhaps setting a higher precision beforehand would be usable.

1.004*2
f[%, 3]
f[%, 4]
%/2
2.008

2.01

2.010

1.005

If this too is not acceptable then to the best of my knowledge Mathematica has no floating point format that is, as you seem to want a fundamentally different precision arithmetic than what is implemented in Mathematica.

Perhaps working with Rational values could work for you. As a rough and partial example:

SetAttributes[num, NHoldAll]

num /: num[x_] * (num[y_] | y_.) := num[x * y]
num /: num[x_] + (num[y_] | y_.) := num[x + y]

Format[num[x_]] := N[x]

g[num[x_] | x_, n_] := num @ Round[x, 10^(1 - n + ⌊Log10@Abs@x⌋)]

Now:

g[1.00412, 4]  (* step to show that g may be used more than once *)

%*2

g[%, 3]

%/2
1.004

2.008

2.01

1.005

Related Q&A's:

Mr.Wizard

Posted 2015-01-25T04:11:53.920

Reputation: 259 163

What if I don't want the ` tick marks when I cut and paste the output list? – David G. Stork – 2015-01-25T08:08:40.087

It doesn't always work. Consider the above test: In[]=1.004,2%,f[%, 3],0.5% Out[]=1.005 which is correct and exactly what you expect due to the rounding error. However, if I replace the 0.5 with 1/2 then the output is 1.01 which doesn't make any sense. – George Papadopoulos – 2015-01-25T08:26:02.090

@David When are you getting (back)tick marks? I do not see these on copy and paste of the plain output. – Mr.Wizard – 2015-01-25T19:32:45.077

@George You seem to be confused regarding output formatting. By using f[%, 3] you have set three digits of precision on that output. Why would Mathematica print more than 1.01 in this case? The FullForm is still 1.005`3. however. The only reason that 1.005 is printed in the first case is that 0.5 is a machine precision number therefore precision tracking is lost. Your expectations seem perplexing. What are you actually trying to accomplish? – Mr.Wizard – 2015-01-25T19:38:30.070

Using Mr.Wizard's code: f[\[Pi], 3] yields 3.14. When I copy this output and paste it into an Input field I get: 3.14`3. Mr.Wizard describes the same thing in his note. My particular need, though, is to cut and paste large datasets (e.g., RandomVariates from continuous distributions) into Manipulate environments when I don't want more than three digits of precision. – David G. Stork – 2015-01-25T19:42:34.807

@David Well yes, that's necessary for meaningful use within Mathematica. If you copy to an external application you should get only 3.14. You can use Copy As Plain Text to get the same result within Mathematica. However you of course loose the precision setting this way and the numbers return to machine precision. – Mr.Wizard – 2015-01-25T19:45:12.637

I don't see why keeping all digits is necessary for meaningful use within Mathematica. I want to illustrate perhaps 1000 fixed points from a specific distribution (and don't want to recompute it each time), and for my illustration three-digit precision suffices. No need to keep dozens of digits. Yes: copying as Plaintext works, but it seems that Mathematica should be able to 'truly' truncate or round a number, trusting the user to know that precision is lost. – David G. Stork – 2015-01-25T19:47:42.547

@David What am I missing here? Who said anything about "all digits" before this? 3.14`3. does not have dozens of of digits?! If you want three digits of precision you must use this input form, otherwise the number is interpreted as machine precision. – Mr.Wizard – 2015-01-25T19:50:15.030

Example: RandomReal[] (* 0.453604 ). N[%,3] ( 0.453604 ). Simple cut and paste: ( 0.45360449288068017` *) -- 17 digits listed. Admittedly, Cut and Paste as text works fine. – David G. Stork – 2015-01-25T19:54:58.830

@David Why did you start using N instead of the provided f? That's the point of this Q&A isn't it? – Mr.Wizard – 2015-01-25T19:58:13.973

@Mr.Wizard okay sure, I understand that there is a difference in the 'implied precision' between the number 0.5 and 1/2 when entered in Mathematica. But my request is to perform a very simple calculation: consider the number 1.004 and double it. The answer is 2.008. Then round it to 3 sig. fig, the answer is 2.01. Take that number, divide it by two/multiply by half/multiply by 0.5 (mathematically equivalent). The mathematical answer is 1.005. I did not ask to round the final answer to 3 sig. fig. as that could be done by doing f to the final answer. Is this possible? – George Papadopoulos – 2015-01-31T06:17:26.350

@George I did my best to address this in an update. – Mr.Wizard – 2015-01-31T07:26:20.657

@Mr.Wizard I think that your function g that you created above (at the bottom) is perfect and works as intended from my testing so far. I have marked it as the answer to the question. While I will avoid ranting, I would like to comment however that I find it difficult to contemplate why such a construed function was necessary; this function would be nearly impossible for a beginner like myself to construct. I will send some feedback to Wolfram Research, and perhaps they will build your function into a future release of Mathematica! – George Papadopoulos – 2015-02-06T02:07:58.070

@George I am glad I could help, even if it took me a few tries. I suspect that your application hasn't come up often enough for them to previously include this. (In most cases I think the built-in arbitrary precision routine could be viewed as "better.") I will say however that the scope of Mathematica is a bit odd IMHO; there are both obscure and highly specific (e.g. Facebook stuff) functions implemented, while some seemingly basic functions are either missing, slow, or bugged. I believe the development team is pushed to spend their time on flashy functions, detrimentally IMO. – Mr.Wizard – 2015-02-06T04:12:12.993

1@Mr.Wizard (and others) FYI: Wolfram have replied, assigned [CASE:2440163]. The email reads:

"Hello George,

Thank you for contacting Wolfram Technical Support.

I understand that you would like the option of having a built-in function that could permanently round all numbers/values to a given number of significant figures. I have filed a suggestion on your behalf and also shared your contact information with our development team so you can be notified when this feature gets implemented.

Thank you once again for taking the time and providing us with this idea.

Sincerely,

Hamid Ardeh" – George Papadopoulos – 2015-02-10T22:36:32.267

@George Thank you. – Mr.Wizard – 2015-02-11T07:40:48.713

1Notes: 1. You can use RealExponent[x] instead of Log[Abs[x]]. 2. Here's another possibility to consider: SetPrecision[Round[#1, 10^-n], n] 10^#2 & @@ MantissaExponent[x]. – J. M.'s ennui – 2016-05-28T02:43:03.283

@J.M. Thank you, too. – Mr.Wizard – 2016-06-02T20:26:54.100

2

Most users probably want to use SetPrecision, which preserves extra digits and automagically handles fractional digits of precision. However, in this case, we need to somehow override this behavior.

I'll use a custom object, sigFigNumber. First I'll define how it's displayed.

Format[sigFigNumber[s_, d_]] := N[s, d]

So we can see that sigFigNumber has two fields: the first one is the significand, and the second is the desired number of digits of precision. But we need a way to get this object!

createSigFigNumber[s_, d_] := 
 sigFigNumber[
  If[d == \[Infinity], s, Round[s, 10^(-d + Floor[Log10[Abs[s]]] + 1)]], d]

createSigFigNumber[s_] := 
 createSigFigNumber[SetPrecision[s, \[Infinity]], 
  Floor[Log10[Abs[s]]] - Floor[-Precision[s] + Log10[Abs[s]]]]

We can use the two-argument form of createSigFigNumber to round a number to the specified number of digits. If we omit the number of digits, then the second function is invoked, which automatically determines the number of digits from the precision of the number. Let's look at some examples now.

createSigFigNumber[1.234, 3] (* 1.23, sigFigNumber[123/100, 3] *)
createSigFigNumber[1.23`3]   (* 1.23, sigFigNumber[123/100, 3] *)
createSigFigNumber[1]        (* 1, sigFigNumber[1, Infinity] *)
createSigFigNumber[Pi]       (* \[Pi], sigFigNumber[Pi, Infinity] *)

We can see that the significand of sigFigNumber is stored in exact form, rounded to the correct decimal place, but is displayed as a decimal (also with the correct number of decimal places showing). Also, exact numbers have a precision of Infinity and so are left unrounded.

Now let's define some functions to do math with these numbers. I'll just give the examples of multiplication (easy) and addition (harder).

sigFigNumber[s1_, d1_] * sigFigNumber[s2_, d2_] ^:= 
 createSigFigNumber[s1 s2, Min[d1, d2]]

sigFigNumber[s1_, d1_] + sigFigNumber[s2_, d2_] ^:= 
 createSigFigNumber[s1 + s2, 
  Floor[Log10[Abs[s1 + s2]]] - 
   Max[Floor[Log10[Abs[s1]]] - d1, Floor[Log10[Abs[s2]]] - d2]]

First note that I am using upvalues to define the behavior of operators on this new object.

For multiplication the problem is simple, the resulting number of significant figures is simply the minimum of the two input numbers. For addition the problem is more complex, requiring us to first convert the precisions to accuracies, find the largest (highest-value last decimal place) and then finally convert back to precision. Now for some examples:

x = createSigFigNumber[1.23, 3] (* 1.23 *)
y = createSigFigNumber[0.45, 2] (* 0.45 *)
z = createSigFigNumber[0.6, 1] (* 0.6 *)
x y (* 0.55 *)
y z (* 0.3 *)
z x (* 0.7 *)
x + y (* 1.68 *)
y + z (* 1.0 *)
z + x (* 1.8 *)

This looks pretty good, although I see one possible problem: you might expect y + z to return 1.1 (the unrounded value is 1.05). The Mathematica documentation states that "Round rounds numbers of the form x.5 toward the nearest even integer." In this case, it's effectively rounding 10.5, and the nearest even integer is 10, not 11, resulting in 1.0 instead of 1.1. So if your students use round-towards-even the results will match. Otherwise you will have to re-write the Round function to something like this:

sigFigRound[a_, b_] := 
 b (IntegerPart[a/b] + 
    If[Abs[FractionalPart[a/b]] >= 1/2, Sign[a], 0])

sigFigRound[105/100, 1/10] (* 11/10 *)

You'll have to write functions to handle every operation. However, you have a choice. In order to handle division/subtraction you can either write functions for Divide and Subtract, or you can write functions for Times and Power. See what Mathematica does internally:

a - b // FullForm (* Plus[a, Times[-1, b]] *)

2012rcampion

Posted 2015-01-25T04:11:53.920

Reputation: 7 465

1Why do you want Mathematica to ignore the extra digits? It seems to me like there is no case where you would want this, as it only reduces your accuracy. However, given your specific use we might be able to tailor a solution to your needs. – 2012rcampion – 2015-01-25T06:35:20.637

@GeorgePapadopoulos With that in mind I think I know how to approach this now. I'll update my answer in the morning tomorrow. – 2012rcampion – 2015-01-25T06:44:27.690

Thanks. As it stands it does not work. I do not want Mathematica to 'retain knowledge' of the extra digits. I am writing an exam, and want to simulate the effects of students rounding. I had come across SetPrecision before, but it does not work. Using your example shows why: In[]=1.004,2%,SetPrecision[%, 3],0.5% Out[]=1.004. If SetPrecision worked as desired, we should have Out[]=1.005 because I want to actually carry this error. Your last function also doesn't work in all cases, for example: round[-0.008445,3] produces `-0.00844' which is incorrect. – George Papadopoulos – 2015-01-25T06:46:29.320

@2012rcampion: There are many occasions where we want to preserve only a few digits, for example cutting and pasting a large output into a Manipulate as a fixed matrix, where only a few digits of precision are necessary. – David G. Stork – 2015-01-25T07:22:14.017

2@George not really relevant to the question, but IMO it would be more helpful for the students if they were told that, if they aren't prepared to do the error analysis at least semi-rigorously (i.e., not using the wildly inaccurate "significant figures" method), then they had better not round intermediate results, and must keep plenty of guard digits throughout the calculation. This would make your life easier by allowing you to mark any incorrect answer as incorrect, rather than having to guess what results could potentially have been produced by premature rounding. What do you think? – Oleksandr R. – 2015-01-26T01:01:30.493

@OleksandrR. I'll address your proposal by explaining my scenario: students are given measurements in two significant figures {1.0,2.0,3.0...} etc. They are required to take the logarithms of these numbers and write them to two significant figures i.e. {0.0, 0.69,...} etc. Why? Because then they need to plot these numbers on graph paper using a ruler and pencil, so at least they have 0.5mm error; there is no way in which a student can plot more than 2 sig. fig. on my scale, and I don't want them to. The purpose of the exercise is to produce a log-log plot and they need to round results. – George Papadopoulos – 2015-01-31T05:56:14.200

@OleksandrR. I also want to stress that I am marking according to student observed learning outcomes, so I don't actually care if the student gets 1093 or 1100, I am marking according to their understanding. Thus, I need to try to simulate a range of possible answers that would result from students rounding. Now, given what I've read above, it seems that the short answer to my original question is: "No, there is no simple function built into Mathematica that permanently rounds numbers, but a convoluted function can be created to do such a function". Thank you all. – George Papadopoulos – 2015-01-31T05:59:38.227