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_] :=
If[d == \[Infinity], s, Round[s, 10^(-d + Floor[Log10[Abs[s]]] + 1)]], d]
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, 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
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
Subtract, or you can write functions for
Power. See what Mathematica does internally:
a - b // FullForm (* Plus[a, Times[-1, b]] *)