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]] *)
N[#,3]&
? reference.wolfram.com/language/ref/N.html?q=N $\endgroup$