4
$\begingroup$

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?

$\endgroup$
10
  • $\begingroup$ Is there anything wrong with using N[#,3]&? reference.wolfram.com/language/ref/N.html?q=N $\endgroup$ Commented Jan 25, 2015 at 4:52
  • $\begingroup$ Sorry I don't understand what you mean, can you explain in more detail? $\endgroup$ Commented Jan 25, 2015 at 5:14
  • $\begingroup$ 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. $\endgroup$
    – m_goldberg
    Commented Jan 25, 2015 at 5:14
  • 2
    $\begingroup$ If 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. $\endgroup$ Commented Jan 25, 2015 at 5:15
  • $\begingroup$ 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? $\endgroup$ Commented Jan 25, 2015 at 5:16

3 Answers 3

2
$\begingroup$
ClearAll[roundSig];
roundSig[x_, nsig_] := 
 DecimalForm[N@x, nsig] // ToString // ToExpression

Now

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

gives

{0.03142, 0.3142, 3.142, 31.42, 314.2}

and since roundSig use //ToString//ToExpression, it gives completely new number without backtick restriction.

$\endgroup$
2
  • $\begingroup$ Thank you @matheorem. I have marked this as the 'answer', as I have found it to satisfy the test and requirements and is quite a simple solution to the problem. Though, it has been seven-and-a-half years since I first posted this query, so I am out of the loop when it comes to this issue. I hope that others in the community can verify the accuracy of the function that you have created. Thank you once again. $\endgroup$ Commented Jul 8, 2022 at 5:42
  • $\begingroup$ @GeorgePapadopoulos Thank you so much for accepting. I changed NumberForm to DecimalForm so it won't fail for very small number like 0.000000000000001235, because NumberForm automatically use scientific form which cause a problem for ToString $\endgroup$
    – matheorem
    Commented Jul 8, 2022 at 6:00
6
$\begingroup$

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:

$\endgroup$
18
  • $\begingroup$ What if I don't want the ` tick marks when I cut and paste the output list? $\endgroup$ Commented Jan 25, 2015 at 8:08
  • $\begingroup$ 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. $\endgroup$ Commented Jan 25, 2015 at 8:26
  • $\begingroup$ @David When are you getting (back)tick marks? I do not see these on copy and paste of the plain output. $\endgroup$
    – Mr.Wizard
    Commented Jan 25, 2015 at 19:32
  • 1
    $\begingroup$ @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" $\endgroup$ Commented Feb 10, 2015 at 22:36
  • 1
    $\begingroup$ Notes: 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]. $\endgroup$ Commented May 28, 2016 at 2:43
2
$\begingroup$

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]] *)
$\endgroup$
7
  • 1
    $\begingroup$ Why 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. $\endgroup$ Commented Jan 25, 2015 at 6:35
  • $\begingroup$ @GeorgePapadopoulos With that in mind I think I know how to approach this now. I'll update my answer in the morning tomorrow. $\endgroup$ Commented Jan 25, 2015 at 6:44
  • $\begingroup$ 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. $\endgroup$ Commented Jan 25, 2015 at 6:46
  • $\begingroup$ @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. $\endgroup$ Commented Jan 25, 2015 at 7:22
  • 2
    $\begingroup$ @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? $\endgroup$ Commented Jan 26, 2015 at 1:01

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.