typhoid - non-standard numeric datatypes
typhoid provides implementations of some non-standard datatypes using {vctrs}

What’s in the box
numberwang(x)for creating numeric values which present themselves as words.uint16(x)for creating 16-bit unsigned integers, where each value is stored as a pair of raw bytes.fixedpt(x, decimals = 3)for creating 32-bit fixed point numeric values.
Each of the types supports as_<type>() and is_<type>(), as well as standard
arithmetic operations (+, * etc) and mathematical functions e.g. abs()
Limitations
These types were created in order to explore the
{vctrs} package, and its capabilities
for easily creating new types, with minimal effort, along with broad support
for standard R features.
These are all pretty naive implementations with ill-defined overflow, and incomplete implementations.
They could be the core of properly useful, robust datatypes - but for now
they merely stand as small tutorials on how new types can be defined with
the {vctrs} package.
Installation
You can install from GitHub with:
# install.package('remotes')
remotes::install_github('coolbutuseless/numberwang')
remotes::install_github('coolbutuseless/typhoid')

The numberwang type is backed by the {numberwang} package which provides
functions for converting from numbers to words, and vice versa.
num <- numberwang(42.123)
num
<typ_numberwang[1]>
[1] "forty-two point one two three"
num + 5
<typ_numberwang[1]>
[1] "forty-seven point one two three"
numberwang(1:3) * 10
<typ_numberwang[3]>
[1] "ten" "twenty" "thirty"
numberwang(1:3) * as_numberwang("twenty seven")
<typ_numberwang[3]>
[1] "twenty-seven" "fifty-four" "eighty-one"

uint16 values are represented internally as a pair of bytes.
Since a uint16 takes up only 16bits, it can only hold integers in the range 0-65535.
This offers space savings compared with R’s standard integer (four bytes), as long as your numbers fit in the given range.
Arithmetic operations are performed modulo 65536.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# uint16 doesn't really behave differently from a small integer
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
uint16(42)
#> <typ_uint16[1]>
#> [1] 42
uint16(c(100, 150)) * 2
#> <typ_uint16[2]>
#> [1] 200 300
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# uint16 uses only half the memory of a regular integer, but has
# a much reduced range as well
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ints <- sample(65535)
lobstr::obj_size(ints)
#> 262,192 B
lobstr::obj_size(uint16(ints))
#> 131,904 B

Fixed point numbers are implemented as integer values and a divisor which is a
power of 10. For instance 12.34 is represented as the integer 1234 with a
divisor of 100.
Fixed point numbers show as many decimal places as
set during initialisation with the fixedpt(x, decimals = 3) call.
When numeric operations are performed on a fixedpt, the result is also a
fixed point, with the number of decimals set to the be the maximum of decimal
places of the two arguments.
Numbers are only representable as a fixedpt if the unscaled number fits into
a normal R integer.
n1 <- fixedpt(1:5, decimals = 3)
n1
#> <typ_fixedpt[5]>
#> [1] 1.000 2.000 3.000 4.000 5.000
n2 <- fixedpt(101:105, decimals = 5)
n2
#> <typ_fixedpt[5]>
#> [1] 101.00000 102.00000 103.00000 104.00000 105.00000
n1 * n2
#> <typ_fixedpt[5]>
#> [1] 101.00000 204.00000 309.00000 416.00000 525.00000
Acknowledgements
- R Core for developing and maintaining the language.
- CRAN maintainers, for patiently shepherding packages onto CRAN and maintaining the repository