Overview
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] TRUEDoubles 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
decimalmodule, 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 likeabs(),sqrt(),exp(), andlog(), plus reductionssum(),prod(),min(),max(), andmean().Decimal-aware tools.
quantize()to round to a fixed number of digits (say, cents),normalize(),fma(),same_quantum(),adjusted(), andnumber_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.
Usage
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.30Decimal 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.95Exactness 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.75Learning more
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.The General Decimal Arithmetic specification is the standard that mpdecimal implements and this package follows.