The MASS
package contains two small helper functions
called fractions
and rational
. Originally
these were included to allow patterns in patterned matrices to be more
easily exhibited and hence, detected. This happens when the entries are
expressible in the form of “vulgar” fractions,
i.e. numerator/denominator, rather than as recurring decimals.
For example a Hilbert matrix:
C1 | C2 | C3 | C4 | C5 | |
---|---|---|---|---|---|
R1 | 1.0000000 | 0.5000000 | 0.3333333 | 0.2500000 | 0.2000000 |
R2 | 0.5000000 | 0.3333333 | 0.2500000 | 0.2000000 | 0.1666667 |
R3 | 0.3333333 | 0.2500000 | 0.2000000 | 0.1666667 | 0.1428571 |
R4 | 0.2500000 | 0.2000000 | 0.1666667 | 0.1428571 | 0.1250000 |
R5 | 0.2000000 | 0.1666667 | 0.1428571 | 0.1250000 | 0.1111111 |
has a pattern much easier to appreciate if the entries are expressed as vulgar fractions:
C1 | C2 | C3 | C4 | C5 | |
---|---|---|---|---|---|
R1 | 1 | 1/2 | 1/3 | 1/4 | 1/5 |
R2 | 1/2 | 1/3 | 1/4 | 1/5 | 1/6 |
R3 | 1/3 | 1/4 | 1/5 | 1/6 | 1/7 |
R4 | 1/4 | 1/5 | 1/6 | 1/7 | 1/8 |
R5 | 1/5 | 1/6 | 1/7 | 1/8 | 1/9 |
This package facilitates the calculation and presentation of numerical quantities as rational approximations.
To be more specific, a rational approximation to a real number is a ratio of two integers, the numerator and denominator, with the denominator positive. For uniqueness of representation we assume that the numerator and denominator are relatively prime, that is, have no common factor, or in other words the fraction is expressed in its lowest terms.
A second vignette, Theory, outlines, for the curious reader, the standard method used to find them, using continued fractions, and some basic mathematical properties. It is of mathematical interest, but not necessary to use the package itself.
This small package offers merely a way to present numerical values by means of close rational approximations, displayed as vulgar fractions. It does not provide exact rational arithmetic. Such a facility would require unlimited precision rational arithmetic, which is well beyond the scope of this package. In fact, integer overflow is a constant limitation in our use of fractions in the present package.
The package provides two main functions:
fractional
and
numerical
, and several auxiliaries. We
discuss these in sequence now.
Readers interested in the details of the algorithm used and its
implementation in R
should refer to the Theory
vignette.
fractional
and unfractional
fractional
is the main function of the package. The
argument list is as follows:
function (x, eps = 1e-06, maxConv = 20, sync = FALSE)
x
A numeric vector, matrix or
array.eps = 1e-06
An absolute error
tolerance. The rational approximation is the first convergent for which
the absolute error falls below this bound.maxConv = 20
A secondary stopping
criterion. An upper limit on the number of convergents (See
Theory vignette) to consider.sync = FALSE
Should the numerical
value be “synchronized” with the displayed numerical approximation? See
below.The value returned is a numeric vector like x
, but
inheriting from class fractional
. It may be used in
arithmetic operations in the normal way and retains full floating point
accuracy, unless sync = TRUE
. When printed, or coerced to
character, however, the rational approximation is used as the
representation, which as a numerical value may not precisely equal the
full floating point value.
If sync = TRUE
is set, then the numerical value is
changed to agree with the numerical value and this agreement is
maintained in synchrony, as much as possible with floating point
arithmetic, during future numerical operations.
unfractional
The function unfractional
is a
convenience function that strips the class and some additional
attributes from an object of class "fractional"
, thus
demoting it back to a numerical object like the one from which it was
generated.
fractional
objects in arithmetic
operations.Methods are provided for the S3
group generic functions
Ops
and Math
, allowing the mathematical
operators and simple mathematical functions to be used with them. The
normal rules apply. In an expression e1 + e2
(where
+
can be any of the operators) the fractional
method is dispatched if either e1
or
e2
(or both) is/are of class
fractional
, and the result is of class
fractional
.
For the Math
group generic, operations are conducted but
treating the objects as a normal numeric object, that is, ignoring the
fractional
class.
as.character
The package provides a method for the primitive generic function
base::as.character
for objects of class
"fractional"
. The arguments are as follows:
function (x, eps = attr(x, "eps"), maxConv = attr(x, "maxConv"), ...)
That is, the values for eps
and maxConv
are
carried forward from the original call. This is because it is only when
the object is displayed or coerced to character that the computation of
the continued fraction is carried out.
The result is a character string object of primary class
charFrac
. This primary class membership essentially ensures
that the object is printed without quotation marks.
numerators
and denominators
These two S3
generic functions will extract the
numerators and denominators, respectively.
The argument is a single object, x
, and the result is an
object of type integer
, but carrying attributes such as
dim
or dimnames
from the original.
Methods are provided for objects of S3
class
fractional
or charFrac
numerical
This is essentially a method function for
base::as.numeric
providing coercion to numeric for objects
of S3
class fractional
or
charFrac
.
It is written as an S3 generic function, and the main methods uses
numerators
and denominators
in an obvious
way.
The default
method passes the object to
base::as.numeric
.
rat
, ratr
and .ratr
The function rat
is a front-end to the C++
function that carries out the continued fraction computations. It would
normally not be called directly by users.
The functions ratr
and .ratr
, in
combination, provide the identical computation but done in pure
R
. They are provided for pedagogical and timing purposes
only.
vfractional
This is a variant on fractional
, without the
sync
argument, but vectorized with respect to the
three arguments x
, eps
and
maxConv
. It is mainly used for examples.
The Theory vignette provides many example in context, but the following very elementary examples may give the flavour of how the functions are used.
[,1] [,2] [,3]
[1,] 1/12 1/3 7/12
[2,] 1/6 5/12 2/3
[3,] 1/4 1/2 3/4
[,1] [,2] [,3]
[1,] 12 3 12/7
[2,] 6 12/5 3/2
[3,] 4 2 4/3
[,1] [,2] [,3]
[1,] 121/12 31/3 127/12
[2,] 61/6 125/12 32/3
[3,] 41/4 21/2 43/4
[,1] [,2] [,3]
[1,] 1/144 1/9 49/144
[2,] 1/36 25/144 4/9
[3,] 1/16 1/4 9/16
[,1] [,2] [,3]
[1,] 0.08333333 0.3333333 0.5833333
[2,] 0.16666667 0.4166667 0.6666667
[3,] 0.25000000 0.5000000 0.7500000
[,1] [,2] [,3]
[1,] 1/12 1/3 7/12
[2,] 1/6 5/12 2/3
[3,] 1/4 1/2 3/4
[,1] [,2] [,3]
[1,] 7/18 -10/9 3/4
[2,] -35/9 160/9 -15
[3,] 14/3 -70/3 21
[,1] [,2] [,3]
[1,] 1 1 49
[2,] 1 25 4
[3,] 1 1 9
[,1] [,2] [,3]
[1,] 144 9 144
[2,] 36 144 9
[3,] 16 4 16
The golden ratio of the ancient Greeks, $\phi = \left(\sqrt{5}+1\right)/2 = 1.61803398874989490\dots$ is well-known to be the limit of the ratio of successive Fibonacci numbers. As a partial indication of this result, the following demonstration is at least suggestive:
F <- c(1,1,numeric(15))
for(i in 3:17) ## Fibonacci sequence by recurrence
F[i] <- F[i-1] + F[i-2]
F
[1] 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610
[16] 987 1597
[1] 1.618034
The function vfractional
may be used to provide
a sequence of optimal1 rational approximations with non-decreasing
denominators:
[1] 1 2 3/2 5/3 8/5 13/8 21/13 34/21
[9] 55/34 89/55 144/89 233/144 377/233 610/377 987/610 1597/987
With random numbers it is mildly interesting from a theoretical point of view to look at the distribution of the number of convergents in the continued fraction expansion needed to achieve a specific error tolerance. We have the tools to explore this somewhat arcane issue:
library(ggplot2)
N <- 500000
set.seed(3210)
rat(rnorm(N), eps = 1.0e-07)[, "n"] %>%
table(x = .) %>%
as.data.frame(responseName = "f") %>%
ggplot(.) + aes(x = x, y = f/N) +
geom_bar(stat = "identity", fill = "steel blue") +
ylab("Relative frequency") + xlab("Convergents")
In particular this appears to justify the choice of
maxConv = 20
as a suitable default2.