The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

friendlynumber friendlynumber website

Codecov test coverage R-CMD-check

{friendlynumber} translates vectors of numbers into character vectors of English numerals (AKA number words). Supported numerals include:

{friendlynumber} functions are intended to be used internally by other packages (e.g. for generating friendly error messages). To this end, {friendlynumber} is written in base R and has no Imports.

Installation

Install the released version from CRAN with:

install.packages("friendlynumber")

You can install the development version of {friendlynumber} from GitHub with:

# install.packages("pak")
pak::pak("EthanSansom/friendlynumber")

Features

library(friendlynumber)

Cardinal Numerals

number_friendly() is an S3 generic which converts numbers into cardinal numerals.

number_friendly(c(0:3, 2/3, 1/100, NA, NaN, Inf))
#> [1] "zero"          "one"           "two"           "three"        
#> [5] "two thirds"    "one hundredth" "missing"       "not a number" 
#> [9] "infinity"

number_friendly() defines methods for four number classes included in base R and the {bignum} package:

Each method has a corresponding standalone *_friendly() function which expects a number of a specific class.

number_friendly(1L)                     # integerish_friendly()
#> [1] "one"
number_friendly(1.0)                    # numeric_friendly()
#> [1] "one"
number_friendly(bignum::biginteger(1L)) # biginteger_friendly()
#> [1] "one"
number_friendly(bignum::bigfloat(1.0))  # bigfloat_friendly()
#> [1] "one"

Other Numerals

{friendlynumber} provides an additional set of functions for translating whole numbers (e.g. 1L or 1.00) into common numeral types.

# Ordinals
ordinal_friendly(0:4)
#> [1] "zeroth" "first"  "second" "third"  "fourth"

# Numeric Ordinals
nth_friendly(0:4)
#> [1] "0th" "1st" "2nd" "3rd" "4th"

# Counts
ntimes_friendly(0:4)
#> [1] "no times"    "once"        "twice"       "three times" "four times"

# Quantifiers
quantifier_friendly(0:4)
#> [1] "no"        "the"       "both"      "all three" "all four"

Precision

{friendlynumber} provides two options() for setting the number of decimals that {friendlynumber} functions report.

options(
  friendlynumber.numeric.digits = 3, # Effects `<numeric>` numbers
  friendlynumber.bigfloat.digits = 5 # Effects `<bignum_bigfloat>` numbers
)

numeric_friendly(0.12345)
#> [1] "one hundred twenty-three thousandths"
bigfloat_friendly(bignum::bigfloat(0.12345))
#> [1] "twelve thousand three hundred forty-five hundred-thousandths"

format_number() is a utility function which formats numbers via format() and abides by these options.

format_number(0.12345)
#> [1] "0.123"
format_number(bignum::bigfloat(0.12345))
#> [1] "0.12345"

This is useful for verifying whether unexpected results are a consequence of precision issues. For instance, look what happens when we attempt to translate the number “ten billion and one hundred-thousandth”.

options(
  friendlynumber.numeric.digits = 7, 
  friendlynumber.bigfloat.digits = 7
)

numeric_friendly(10000000000.00001)
#> [1] "ten billion and ninety-five ten-millionths"
bigfloat_friendly(bignum::bigfloat("10000000000.00001"))
#> [1] "ten billion and one hundred-thousandth"

We can use format_number() to confirm that, on my machine, a <numeric> vector lacks the precision to store this number accurately.

format_number(10000000000.00001)
#> [1] "10,000,000,000.0000095"
format_number(bignum::bigfloat("10000000000.00001"))
#> [1] "10,000,000,000.00001"

Similar problems can arise when working with whole numbers. Consider the number “ten quadrillion” minus one.

numeric_friendly(10000000000000000 - 1)
#> [1] "ten quadrillion"
biginteger_friendly(bignum::biginteger("10000000000000000") - 1L)
#> [1] "nine quadrillion nine hundred ninety-nine trillion nine hundred ninety-nine billion nine hundred ninety-nine million nine hundred ninety-nine thousand nine hundred ninety-nine"

Advantages

{friendlynumber} is faster than other alternatives written in base R - such as the {english} and {nombre} packages.

# Scalar (small)
bench::mark(
  english = as.character(english::english(1L)),
  nombre = as.character(nombre::nom_card(1L)),
  friendlynumber = as.character(number_friendly(1L))
)[1:6]
#> # A tibble: 3 × 6
#>   expression          min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>     <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 english         89.54µs  92.95µs    10520.     140KB     33.9
#> 2 nombre         120.83µs 125.95µs     7666.     685KB     29.4
#> 3 friendlynumber   6.64µs   7.26µs   134666.        0B     40.4
# Scalar (large)
bench::mark(
  english = as.character(english::english(100000)),
  nombre = as.character(nombre::nom_card(100000)),
  friendlynumber = as.character(number_friendly(100000))
)[1:6]
#> # A tibble: 3 × 6
#>   expression          min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>     <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 english         174.3µs    179µs     5498.    8.26KB     27.2
#> 2 nombre          126.2µs  130.6µs     7468.        0B     31.7
#> 3 friendlynumber   34.7µs   36.2µs    27136.        0B     29.9
# Vector
bench::mark(
  english = as.character(english::english(1:10000)),
  nombre = as.character(nombre::nom_card(1:10000)),
  friendlynumber = as.character(number_friendly(1:10000)),
  filter_gc = FALSE
)[1:6]
#> # A tibble: 3 × 6
#>   expression          min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>     <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 english           1.24s    1.24s     0.804    2.34MB    12.1 
#> 2 nombre           45.4ms  47.82ms    20.8     11.43MB    11.4 
#> 3 friendlynumber   8.91ms    9.2ms   105.       3.87MB     7.89

To increase the speed of processing scalar inputs, the set of *_friendly() functions do not check that their arguments are of valid types. All *_friendly() functions have a slightly slower *_friendly_safe() alternative which confirms that it’s arguments are of the correct type and emits an informative error otherwise.

try(integerish_friendly_safe(numbers = 1/2))
#> Error : `numbers` must be coercible to an integer without loss of precision.

When used with the {bignum} package, {friendlynumber} is capable of translating extremely large numbers into cardinal numerals. Here, we translate a number equal to 1 followed by three thousand and three 0’s.

number_friendly(bignum::biginteger(10L)^3003L)
#> [1] "one millinillion"

Inspiration

This package was originally inspired by the {english} package by John Fox, Bill Venables, Anthony Damico and Anne Pier Salverda, which inspired my fixation with the problem of converting numbers to numerals.

Several functions in {friendlynumber} were inspired by Alexander Rossell Hayes’ {nombre} package:

The following sources were very helpful for naming extremely large numbers:

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.