MyNixOS website logo
Description

Translate Numbers into Number Words.

Converts vectors of numbers into character vectors of numerals, including cardinals (one, two, three) and ordinals (first, second, third). Supports negative numbers, fractions, and arbitrary-precision integer and high-precision floating-point vectors provided by the 'bignum' package.

friendlynumber friendlynumber website

Codecov testcoverage 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:

Metadata

Version

1.0.0

License

Unknown

Platforms (75)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-freebsd
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64-windows
  • aarch64_be-none
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-windows