Function Repository Resource:

DecimalRound

Source Notebook

Round a machine number to a prescribed number of significant digits using conventional rounding rules

Contributed by: Peter Valko

ResourceFunction["DecimalRound"][machnum,ndig]

rounds the machine number machnum to ndig significant digits using conventional rounding rules.

Details and Options

The argument ndig should be an integer between 1 and 15. If not, it is rounded and clipped.
The default value of ndig is 6.
ResourceFunction["DecimalRound"] has the Listable attribute, so either argument can be a list. However, both arguments cannot be lists simultaneously.
In case of failure, the first argument is returned.

Examples

Basic Examples (3) 

Create a machine number approximating π:

In[1]:=
machinePi = N[\[Pi]]
Out[1]=

Use DecimalRound to round the machine number to four significant digits:

In[2]:=
roundedmachinePi = ResourceFunction["DecimalRound"][machinePi, 4]
Out[2]=

Using InputForm shows that these are different values:

In[3]:=
{InputForm[machinePi], InputForm[roundedmachinePi]}
Out[3]=

Applications (3) 

Generate a random matrix with elements drawn from the stable probability distribution, calculate the matrix inverse, check the product and compute its matrix norm:

In[4]:=
SeedRandom[1234];
mat = RandomVariate[StableDistribution[0.2], {5, 5}];
invmat = Inverse[mat];
prod = invmat . mat;
normprod = Norm[prod];

Make a version of the matrix rounded to six significant digits and process as before:

In[5]:=
mat6 = ResourceFunction["DecimalRound"][ mat ];
invmat6 = Inverse[mat6];
prod6 = invmat6 . mat;
normprod6 = Norm[prod6];

Compare the results:

In[6]:=
Column[{
  "mat:",
  mat // MatrixForm,
  "prod:",
  prod // MatrixForm // Chop,
  "norm:",
  normprod}, Frame -> {{True, None}}]
Out[6]=
In[7]:=
Column[{
  "mat6:",
  mat6 // MatrixForm,
  "prod6:",
  prod6 // MatrixForm // Chop,
  "norm6:",
  normprod6}, Frame -> {{True, None}}]
Out[7]=

The product norm should be near 1. We can see that rounding to 6 significant digits before inversion gives rise to a discernable change in the computed inverse.

Properties and Relations (2) 

Compare to Round:

In[8]:=
numlist = {602214076000000000000000, Rational[132521403, 200000000000000000000000000000000000000], 15.0009933`, SetPrecision[22/7., 5.283185307179586], -0.000037641866426300784330078433`25.17567114925435};
machnumlist = N[numlist];
ndiglist = {-1, 1, 2, 3, 6, 7, 8, 9, 10, 11, 14, 19, 20};
In[9]:=
roundedmatrix1 = Table[
   Table[Round[machnum, 10^-ndig], {machnum, machnumlist}]
   , {ndig, ndiglist}];

TableForm[Map[FullForm, roundedmatrix1, {2}], TableHeadings -> {ndiglist, Map[FullForm, machnumlist]}]
Out[10]=

As the results are not machine numbers, we make them so:

In[11]:=
roundedmatrix2 = Table[
   Table[Round[machnum, 10^-ndig] // N, {machnum, machnumlist}]
   , {ndig, ndiglist}];
In[12]:=
TableForm[Map[FullForm, roundedmatrix2, {2}], TableHeadings -> {ndiglist, Map[FullForm, machnumlist]}]
Out[15]=

A trailing 0000x or 9999x is not desirable. Moreover the approach works only for numbers of order one, so now scale the second argument:

In[16]:=
roundedmatrix3 = Table[
   Table[
    Round[machnum, Abs[machnum] 10^-ndig] // N, {machnum, machnumlist}]
   , {ndig, ndiglist}];
In[17]:=
TableForm[Map[InputForm, roundedmatrix3, {2}], TableHeadings -> {ndiglist, Map[InputForm, machnumlist]}]
Out[20]=

Use of RealDigits shows similar issues:

In[21]:=
DecimalRoundAlt[machnum_, ndig_] := Module[{n, e, nmad, nman, ma, nd},
   nd = Clip[Round[ndig], {1, 15}];
   n = 20;
   {nmad, e} = RealDigits[Abs[machnum]];
   nmad = PadRight[nmad, n];
   ma = 0 nmad;
   ma[[n]] = nman = FromDigits[nmad];
   While[n > 1,
    nman = FromDigits[Most[nmad]];
    If[Last[nmad] >= 5, nman++];
    nmad = IntegerDigits[nman];
    ma[[--n]] = nman;
    If[n == nd, Break[]]
    ];
   N[Sign[machnum] ma[[nd]] 10^(e - nd)] ];
In[22]:=
TableForm[
 Table[{ResourceFunction["DecimalRound"][N[Pi 10^-18], n] // FullForm,
    DecimalRoundAlt[N[Pi 10^-18], n] // FullForm}, {n, 15}], TableHeadings -> {Range[20], {"DecimalRound", "DecimalRoundAlt"}}]
Out[23]=

Possible Issues (2) 

In case of any error, the first argument is returned. Here the first argument is not a machine number:

In[24]:=
ResourceFunction["DecimalRound"][6.02214076*  10^b, 3] // FullForm
Out[24]=

The first argument is returned unevaluated here as well:

In[25]:=
ResourceFunction["DecimalRound"][E^\[Pi], 3] // FullForm
Out[25]=

Publisher

Peter Valko

Version History

  • 1.0.0 – 12 August 2019

Related Resources

Author Notes

In general, the Precision of an approximate real number is the effective number of decimal digits in it which are treated as significant for computations. In contrast to the other types of approximate numbers (the arbitrary‐precision numbers), machine numbers contain a fixed number of digits (MachinePrecision). Since they maintain no information on their precision, all their digits are treated as significant during computations.
In rare occasions one wants to prescribe the effective number of digits in a machine number, putting zeros into the trail and perhaps modifying the last significant digit. In everyday life, this operation is called rounding, but the Wolfram Language function Round is not designed specifically for machine numbers. It is not straightforward to use Round to set explicitly the number of significant digits in the decimal representation of a machine number.
The resource function DecimalRound takes a machine number and the number of required digits and attempts to provide the result as a machine number with exactly the requested number of significant digits.
In the following example, numlist consists of five numbers, originally having wildly spread precisions. For instance, the first number is the AvogadroConstant in SI which is perfectly defined by an international standard. On the other hand, the fourth number is the result of a calculation to approximate π via dividing 22 by 7 and the indicated precision is itself the result of an approximate calculation. Here is the list:
In[1]:=
numlist = {602214076000000000000000, Rational[132521403, 200000000000000000000000000000000000000], 15.0009933`, SetPrecision[22/7., 5.283185307179586], -0.000037641866426300784330078433`25.17567114925435};
Map[Precision, numlist]
When we create the list of machine numbers, we lose some information on precision (for instance our approximate π will look like a 16-digit good approximation), but computations and storage will be more efficient in the world of machine numbers:
In[2]:=
machnumlist = N[numlist];
Map[Precision, machnumlist]
Interestingly, some information is preserved, for instance the Avogadro number will still show that it has nine "good" digits, albeit only implicitly, by the fact that seven trailing digits are zeros. If we want to convey our knowledge regarding the approximate π, we should round it to, say, three digits, as we would do it in a hand calculation. DecimalRound can do this.
To be more general, in this example we create a variable which contains possible requirements for the number of significant digits:
In[3]:=
ndiglist = {-1, 1, 2, 3, 6, 7, 8, 9, 10, 11, 14, 19, 20};
Then DecimalRound is called with various combinations of the two arguments:
In[4]:=
roundedmatrix = Table[
   Table[DecimalRound[machnum, ndig], {machnum, machnumlist}],
   {ndig, ndiglist}];
In the output the column heading shows the rounded machine numbers. The row heading shows the requested digit counts. We use FullForm to see what we are really having in the result matrix:
In[5]:=
TableForm[Map[FullForm, roundedmatrix, {2}], TableHeadings -> {ndiglist, Map[FullForm, machnumlist]}]
Since the function is Listable, we can easily get a whole row of the matrix as well as as a whole column:
In[6]:=
DecimalRound[machnumlist, 10] // FullForm
In[7]:=
DecimalRound[6.02214076`*^23, ndiglist] // FullForm

License Information