decimal provides exact, arbitrary-precision decimal vectors for R. If
you’ve ever been surprised that 0.1 + 0.2 == 0.3 is FALSE, this
package is for you:
library(decimal)
0.1 + 0.2 == 0.3
#> [1] FALSE
decimal("0.1") + decimal("0.2") == decimal("0.3")
#> [1] TRUE
Doubles are binary fractions, so they can’t represent most decimal numbers exactly, and tiny errors accumulate as you compute. That’s usually fine — but not when you’re working with money, invoices, exchange rates, or anything else where cents have to add up. decimal uses a decimal representation and performs arithmetic under an explicit decimal context, so any rounding is controlled and observable.
Under the hood, decimal is built on:
mpdecimal, the
battle-tested C library behind Python’s decimal module, implementing
the General Decimal
Arithmetic standard.
vctrs, so decimal vectors work naturally in
data frames, tibbles, dplyr::mutate(), joins, sorting, and
everything else you already do with vectors.
Highlights:
Exact values. Strings and integers are parsed exactly; promotion
to a finer shared scale only adds trailing zeros. Values round-trip
through as.character() without loss — nothing changes on the way to
a CSV file or database column and back.
Full arithmetic. +, -, *, /, ^, %%, %/%,
comparisons, and math functions like abs(), sqrt(), exp(), and
log(), plus reductions sum(), prod(), min(), max(),
mean(), and summary().
Decimal-aware tools. quantize() to round to a fixed number of
digits (say, cents), normalize(), fma(), same_quantum(),
adjusted(), and number_class().
You control the rules. A decimal context sets the precision,
rounding mode, and which conditions (overflow, division by zero, …)
are errors — see vignette("contexts-and-signals").
Special values. NA, signed zeros, infinities, and quiet and
signaling NaNs are supported throughout.
Arrow and Parquet. A decimal column becomes a real Arrow
decimal128() or decimal256() field in arrow::write_parquet() and
arrow::arrow_table(), and comes back as a decimal vector on every
read path — see vignette("arrow-decimal-types").
Install the released version from CRAN:
install.packages("decimal")
Or install the development version from GitHub with pak:
# pak::pak("pedrobtz/decimal")
install.packages("decimal", repos = c('https://pedrobtz.r-universe.dev', 'https://cloud.r-project.org'))
Create decimal vectors from strings (exact, and the recommended way) or integers, and use them like any other numeric vector:
library(decimal)
x <- decimal(c("1.20", "2.30", "3.40"))
x
#> <decimal[3]>
#> [1] 1.20 2.30 3.40
sum(x)
#> <decimal[1]>
#> [1] 6.90
mean(x)
#> <decimal[1]>
#> [1] 2.30
Decimal vectors are first-class citizens in tibbles and dplyr pipelines:
library(dplyr)
sales <- tibble::tibble(
item = c("coffee", "bagel", "juice"),
price = decimal(c("2.50", "1.25", "3.95")),
qty = c(3L, 2L, 1L)
)
sales |>
mutate(total = price * qty) |>
summarise(revenue = sum(total))
#> # A tibble: 1 × 1
#> revenue
#> <dec>
#> 1 13.95
Exactness matters most when small errors compound — literally, in the case of interest:
principal <- decimal(c("1000.00", "2500.00", "500.00"))
rate <- decimal("0.05")
balance <- principal * (1L + rate)^4L
balance
#> <decimal[3]>
#> [1] 1215.5062500000 3038.7656250000 607.7531250000
# round to cents for reporting
quantize(balance, decimal("0.01"))
#> <decimal[3]>
#> [1] 1215.51 3038.77 607.75
vignette("decimal-values") introduces decimal vectors: how to create
them, how scale works, and the everyday operations.
vignette("contexts-and-signals") covers the arithmetic context:
precision, rounding modes, traps, and flags.
vignette("arrow-decimal-types") covers lossless conversion to and
from Arrow decimal columns and Parquet files.
The General Decimal Arithmetic specification is the standard that mpdecimal implements and this package follows.
decimal is MIT licensed. The vendored mpdecimal library retains its own
BSD-2-Clause terms; see inst/COPYRIGHTS.