Skip to main content
added link to documentation for `Round`
Source Link
2012rcampion
  • 7.9k
  • 26
  • 44

YouMost users probably want to use SetPrecision, which preserves extra digits and automagically handles fractional digits of precision. For example: 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.

xFormat[sigFigNumber[s_, d_]] := N[Pi]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_, 3.14159d_] *)
x:= // 
 PrecisionsigFigNumber[
 (* MachinePrecisionIf[d *)

y== =\[Infinity], SetPrecision[xs, 3]Round[s, 10^(*-d 3.14+ *Floor[Log10[Abs[s]]] + 1)]], d]
y
createSigFigNumber[s_] //:= Precision 
 (*createSigFigNumber[SetPrecision[s, 3.\[Infinity]], *)

z = SetPrecision[PiFloor[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, 5]3] (* 31.141623, sigFigNumber[123/100, 3] *)
zcreateSigFigNumber[1.23`3] // Precision (* 51.23, sigFigNumber[123/100, 3] *)
 
v = ycreateSigFigNumber[1] * z (* 9.87 *)
v // Precision (* 2.99581, sigFigNumber[1, Infinity] *)
 
wcreateSigFigNumber[Pi] = y + z (* 6.28 (*)
w // Precision\[Pi], (*sigFigNumber[Pi, 3.29671Infinity] *)

So weWe can see that the significand of SetPrecisionsigFigNumber sets the number of significant digitsis stored in exact form, rounded to the desired number. This precision will be used when printing to setcorrect decimal place, but is displayed as a decimal (also with the correct number of digitsdecimal places showing). Also, and will also be used when computing with other arbitrary-precisionexact numbers to set thehave a precision of the resultInfinity and so are left unrounded.

However, mixing machine-precision and arbitrary-precision numbers does not work, as the result will be converted backNow let's define some functions to machine precision:

Precision[x*y] (* MachinePrecision *)

(This does not affect using arbitrary-precision numbersdo math with exactthese numbers like 4, 134/267,. I'll just give the examples of multiplication Pi, or(easy) and addition E.(harder) We can get around this restriction by converting any machine-precision numbers to exact form, or setting their precision manually:.

Precision[Rationalize[xsigFigNumber[s1_, 0]*y]d1_] (* 3.sigFigNumber[s2_, *)d2_] ^:= 
Precision[SetPrecision[x createSigFigNumber[s1 s2, $MachinePrecision]*y]Min[d1, (*d2]]

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

NoteFirst note that you must use $MachinePrecision, notI am using MachinePrecision. The first one will evaluateupvalues to a number (15.9546define the behavior of operators on my 64-bit machinethis new object.

For multiplication the problem is simple, equal to 53 bits: the sizeresulting number of significant figures is simply the significandminimum of an IEEE double) and return an arbitrary-precision number with precision equal to a machine-precision number, while the second onetwo input numbers. For addition the problem is more complex, requiring us to first convert the symbol that Mathematica usesprecisions to represent machine-precision numbersaccuracies, sofind the number will stay machinelargest (highest-precisionvalue last decimal place) and then finally convert back to precision.

Note that Mathematica keeps more digits internally than are displayed, Now for examplesome examples:

yx = createSigFigNumber[1.23, 3] (* 31.1423 *)
y //= FullFormcreateSigFigNumber[0.45, 2] (* 3.14159265358979311599796346854418516159`30.45 *)

This is a good thing. It prevents rounding errors, so that you know your 3-digit-precision number actually is accurate to three digits. For a simple example, convert the number $1.004$ to three digit precision, and double it. Doing this naively you get $1.00\times 2=2.00$. However, the answer should be $2.01$, closer to the actual full-precision value of $2.008$.

However, if you want to retain this erroneous behavior you will have to use a special function to round the numbers. Try this one:

round[n_]z := SetPrecision[Round[ncreateSigFigNumber[0.6, 10^Floor[-Precision[n]1] +(* Log10[Abs[n]]]],0.6 *)
x Precision[n]]y (* 0.55 *)
y z (* 0.3 *)
z x (* 0.147 *)
round[y]x + y (* 31.1468 *)
%y //+ FullFormz (* 31.142`30 *)
z + x (* 1.8 *)

Note that in this case it gives you one extra digit. This is due to the definition of precision in Mathematica:

With absolute uncertainty dx, Precision[x] is -Log[10,dx/x].

This is incompatible with the typical definition of precisionlooks pretty good, which has discontinuous behavioralthough I see one possible problem: you might expect y + z to return 1.1 (ethe unrounded value is 1.05).g 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 $1.00$10.5, and the nearest $9.99$ have 3 digits of precisioneven integer is 10, butnot $10.00$ has 4)11, resulting in 1.0 instead of 1.1.

To So if your students use round-towards-even the typical definition you'llresults will match. Otherwise you will have to round manuallyre-write the Round function to something like this:

round[n_sigFigRound[a_, d_]b_] := 
 SetPrecision[Round[n,b 10^(Ceiling[Log10[Abs[n]]] -IntegerPart[a/b] d)],+ d]
a = round[1, 3] (* 1.00 *)
a /If[Abs[FractionalPart[a/ FullFormb]] (*>= 1.`3. *)
b = round[0.999/2, 3] (* 0.999Sign[a], *0])
b /
sigFigRound[105/100, FullForm1/10] (* 0.999`3.11/10 *)

To avoid Mathematica's default precision-handling behavior you will alsoYou'll have to compute the precision and re-round for every operation, or at least write custom functions forto handle every operation.

This is not a good idea. You will then be subject to errors as I describe above However, and the whole point of Mathematica is that you can ignore details like thishave a choice. In summary, just useorder to handle division/subtraction you can either write functions for SetPrecisionDivide and your calculations will automagically work out as accurately as theySubtract, or you can write functions for Times and Power. See what Mathematica does internally:

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

You probably want to use SetPrecision. For example:

x = N[Pi] (* 3.14159 *)
x // Precision (* MachinePrecision *)

y = SetPrecision[x, 3] (* 3.14 *)
y // Precision (* 3. *)

z = SetPrecision[Pi, 5] (* 3.1416 *)
z // Precision (* 5. *)
 
v = y * z (* 9.87 *)
v // Precision (* 2.9958 *)
 
w = y + z (* 6.28 *)
w // Precision (* 3.29671 *)

So we can see that SetPrecision sets the number of significant digits to the desired number. This precision will be used when printing to set the number of digits, and will also be used when computing with other arbitrary-precision numbers to set the precision of the result.

However, mixing machine-precision and arbitrary-precision numbers does not work, as the result will be converted back to machine precision:

Precision[x*y] (* MachinePrecision *)

(This does not affect using arbitrary-precision numbers with exact numbers like 4, 134/267, Pi, or E.) We can get around this restriction by converting any machine-precision numbers to exact form, or setting their precision manually:

Precision[Rationalize[x, 0]*y] (* 3. *)
Precision[SetPrecision[x, $MachinePrecision]*y] (* 3. *)

Note that you must use $MachinePrecision, not MachinePrecision. The first one will evaluate to a number (15.9546 on my 64-bit machine, equal to 53 bits: the size of the significand of an IEEE double) and return an arbitrary-precision number with precision equal to a machine-precision number, while the second one is the symbol that Mathematica uses to represent machine-precision numbers, so the number will stay machine-precision.

Note that Mathematica keeps more digits internally than are displayed, for example:

y (* 3.14 *)
y // FullForm (* 3.14159265358979311599796346854418516159`3. *)

This is a good thing. It prevents rounding errors, so that you know your 3-digit-precision number actually is accurate to three digits. For a simple example, convert the number $1.004$ to three digit precision, and double it. Doing this naively you get $1.00\times 2=2.00$. However, the answer should be $2.01$, closer to the actual full-precision value of $2.008$.

However, if you want to retain this erroneous behavior you will have to use a special function to round the numbers. Try this one:

round[n_] := SetPrecision[Round[n, 10^Floor[-Precision[n] + Log10[Abs[n]]]], 
 Precision[n]]
y (* 3.14 *)
round[y] (* 3.14 *)
% // FullForm (* 3.142`3. *)

Note that in this case it gives you one extra digit. This is due to the definition of precision in Mathematica:

With absolute uncertainty dx, Precision[x] is -Log[10,dx/x].

This is incompatible with the typical definition of precision, which has discontinuous behavior (e.g. $1.00$ and $9.99$ have 3 digits of precision, but $10.00$ has 4).

To use the typical definition you'll have to round manually:

round[n_, d_] := 
 SetPrecision[Round[n, 10^(Ceiling[Log10[Abs[n]]] - d)], d]
a = round[1, 3] (* 1.00 *)
a // FullForm (* 1.`3. *)
b = round[0.999, 3] (* 0.999 *)
b // FullForm (* 0.999`3. *)

To avoid Mathematica's default precision-handling behavior you will also have to compute the precision and re-round for every operation, or at least write custom functions for every operation.

This is not a good idea. You will then be subject to errors as I describe above, and the whole point of Mathematica is that you can ignore details like this. In summary, just use SetPrecision and your calculations will automagically work out as accurately as they can.

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]] *)
Source Link
2012rcampion
  • 7.9k
  • 26
  • 44

You probably want to use SetPrecision. For example:

x = N[Pi] (* 3.14159 *)
x // Precision (* MachinePrecision *)

y = SetPrecision[x, 3] (* 3.14 *)
y // Precision (* 3. *)

z = SetPrecision[Pi, 5] (* 3.1416 *)
z // Precision (* 5. *)

v = y * z (* 9.87 *)
v // Precision (* 2.9958 *)

w = y + z (* 6.28 *)
w // Precision (* 3.29671 *)

So we can see that SetPrecision sets the number of significant digits to the desired number. This precision will be used when printing to set the number of digits, and will also be used when computing with other arbitrary-precision numbers to set the precision of the result.

However, mixing machine-precision and arbitrary-precision numbers does not work, as the result will be converted back to machine precision:

Precision[x*y] (* MachinePrecision *)

(This does not affect using arbitrary-precision numbers with exact numbers like 4, 134/267, Pi, or E.) We can get around this restriction by converting any machine-precision numbers to exact form, or setting their precision manually:

Precision[Rationalize[x, 0]*y] (* 3. *)
Precision[SetPrecision[x, $MachinePrecision]*y] (* 3. *)

Note that you must use $MachinePrecision, not MachinePrecision. The first one will evaluate to a number (15.9546 on my 64-bit machine, equal to 53 bits: the size of the significand of an IEEE double) and return an arbitrary-precision number with precision equal to a machine-precision number, while the second one is the symbol that Mathematica uses to represent machine-precision numbers, so the number will stay machine-precision.

Note that Mathematica keeps more digits internally than are displayed, for example:

y (* 3.14 *)
y // FullForm (* 3.14159265358979311599796346854418516159`3. *)

This is a good thing. It prevents rounding errors, so that you know your 3-digit-precision number actually is accurate to three digits. For a simple example, convert the number $1.004$ to three digit precision, and double it. Doing this naively you get $1.00\times 2=2.00$. However, the answer should be $2.01$, closer to the actual full-precision value of $2.008$.

However, if you want to retain this erroneous behavior you will have to use a special function to round the numbers. Try this one:

round[n_] := SetPrecision[Round[n, 10^Floor[-Precision[n] + Log10[Abs[n]]]], 
 Precision[n]]
y (* 3.14 *)
round[y] (* 3.14 *)
% // FullForm (* 3.142`3. *)

Note that in this case it gives you one extra digit. This is due to the definition of precision in Mathematica:

With absolute uncertainty dx, Precision[x] is -Log[10,dx/x].

This is incompatible with the typical definition of precision, which has discontinuous behavior (e.g. $1.00$ and $9.99$ have 3 digits of precision, but $10.00$ has 4).

To use the typical definition you'll have to round manually:

round[n_, d_] := 
 SetPrecision[Round[n, 10^(Ceiling[Log10[Abs[n]]] - d)], d]
a = round[1, 3] (* 1.00 *)
a // FullForm (* 1.`3. *)
b = round[0.999, 3] (* 0.999 *)
b // FullForm (* 0.999`3. *)

To avoid Mathematica's default precision-handling behavior you will also have to compute the precision and re-round for every operation, or at least write custom functions for every operation.

This is not a good idea. You will then be subject to errors as I describe above, and the whole point of Mathematica is that you can ignore details like this. In summary, just use SetPrecision and your calculations will automagically work out as accurately as they can.