Skip to content

Converts number vectors to character vectors of numerals, including cardinals (one, two, three) and ordinals (first, second, third).

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md
Notifications You must be signed in to change notification settings

EthanSansom/friendlynumber

Repository files navigation

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:

  • Cardinals: one, ten and two thirds, one thousand twenty-one
  • Ordinals: first, second, one millionth, 1st, 2nd, 1,000,000th
  • Counts: no times, once, twice, three times
  • Quantifiers: no, the, both, all three, all 999, every

{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:

  • Base <integer> and <numeric> vectors
  • {bignum} <bignum_biginteger> vectors, which can store arbitrarily large integers
  • {bignum} <bignum_bigfloat> vectors, which store numbers with 50 decimal digits of precision

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:

About

Converts number vectors to character vectors of numerals, including cardinals (one, two, three) and ordinals (first, second, third).

Topics

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published