| Title: | Exact Arbitrary-Precision Decimal Vectors |
| Version: | 0.1.1 |
| Description: | Arbitrary-precision vectors with an exact decimal representation, avoiding the rounding surprises of binary floating point. Built on the 'mpdecimal' C library, arithmetic is governed by an explicit decimal context controlling precision, rounding, and signaling, and vectors integrate with 'vctrs' for use in data frames, 'tibble' objects, summaries, and common numeric workflows. Missing values, signed zeros, infinities, and not-a-number values are supported throughout. The arithmetic model follows Cowlishaw (2009) "General Decimal Arithmetic" https://speleotrove.com/decimal/decarith.html. |
| License: | MIT + file LICENSE |
| Language: | en-US |
| URL: | https://github.com/pedrobtz/decimal, https://pedrobtz.github.io/decimal/ |
| BugReports: | https://github.com/pedrobtz/decimal/issues |
| Depends: | R (≥ 4.2.0) |
| Imports: | methods, rlang, vctrs, withr |
| Suggests: | arrow, covr, data.table, dplyr, knitr, pillar, R6, rmarkdown, tibble, testthat (≥ 3.0.0) |
| VignetteBuilder: | knitr |
| Config/testthat/edition: | 3 |
| Config/Needs/documentation: | roxygen2 |
| Encoding: | UTF-8 |
| NeedsCompilation: | yes |
| Config/roxygen2/version: | 8.0.0 |
| Packaged: | 2026-09-23 13:13:23 UTC; pbtz |
| Author: | Pedro Baltazar [aut, cre, cph], Stefan Krah [ctb, cph] (Vendored 'mpdecimal' library in src/mpdecimal; see inst/COPYRIGHTS.) |
| Maintainer: | Pedro Baltazar <pedrobtz@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-23 14:20:02 UTC |
decimal: Arbitrary-Precision Decimal Vectors for R
Description
decimal provides arbitrary-precision decimal vectors for R backed by the
vendored mpdecimal C library.
Details
The package follows a three-part model:
immutable decimal values stored as exact strings;
an active arithmetic context controlling precision, rounding, exponent limits, traps, and sticky flags; and
vector semantics implemented with
vctrs.
Version 0.1.0 is a correctness-first release. It supports exact
construction, context-aware arithmetic, comparison, summaries, special
values, and tibble/data-frame use. Some advanced Python decimal
capabilities remain deferred and are documented in the package README and
vignettes.
Missing decimal scalar
Description
A length-one missing decimal value.
Usage
NA_decimal_
Value
A length-one decimal vector containing the typed R missing value.
Examples
c(decimal("1"), NA_decimal_)
Compute the adjusted exponent
Description
adjusted() returns the position of the most significant digit after
accounting for the stored exponent. For a finite nonzero value, this is
equivalent to the base-10 order of magnitude. The representation of zero
retains its exponent, so differently scaled zeros can have different
adjusted exponents.
Usage
adjusted(x)
Arguments
x |
A decimal-compatible vector. |
Value
An integer vector with the same length as x. Infinities, NaNs,
and R missing values produce NA_integer_.
Examples
adjusted(decimal(c("123", "0.01")))
Convert an Arrow table to a data frame, keeping decimal columns exact
Description
as.data.frame() on an Arrow Table or RecordBatch converts a plain
decimal field through double, which silently drops digits beyond the
seventeenth. arrow_as_data_frame() converts those fields with
as_decimal() instead, so they arrive as decimal vectors with the scale
declared by their Arrow type. Every other column, including decimal columns
this package wrote as its extension type, is converted by arrow in the
usual way.
Usage
arrow_as_data_frame(x)
Arguments
x |
An Arrow |
Value
A data frame whose decimal columns are decimal vectors.
See Also
decimal_arrow for when a decimal field is plain and when it is the extension type.
Examples
if (requireNamespace("arrow", quietly = TRUE)) {
tab <- arrow::arrow_table(
id = 1:2,
amount = arrow::Array$create(
c("100.05", "99999999999999999999.99")
)$cast(arrow::decimal128(25, 2))
)
arrow_as_data_frame(tab)
}
Arrow type for a decimal column
Description
Builds the Arrow extension type this package writes decimal vectors as,
with a chosen precision and scale rather than ones inferred from the
values. Use it to pin the type of a column that will be appended to, for
example through the schema argument of arrow::arrow_table() or the
type argument of arrow::as_arrow_array(). The storage is
arrow::decimal128() up to 38 digits of precision and
arrow::decimal256() above that.
Usage
arrow_decimal_type(precision, scale = 0L)
Arguments
precision |
Total number of digits, at most 76. |
scale |
Number of fractional digits. |
Value
An Arrow extension type.
See Also
decimal_arrow for how the type behaves.
Examples
if (requireNamespace("arrow", quietly = TRUE)) {
arrow_decimal_type(20, 2)
arrow::as_arrow_array(decimal("1.25"), type = arrow_decimal_type(20, 2))
}
Convert a vector to decimal
Description
as_decimal() is the conversion generic for decimal vectors. Character
values are parsed exactly, integer values are converted exactly, and
existing decimal vectors are returned or rescaled. Promotion to a finer
shared scale is exact and context-free; reduction to a coarser scale is
quantized. Double values are decoded from their exact IEEE 754
representation and therefore require an explicit or globally configured
scale before quantization.
Usage
## S3 method for class 'Array'
as_decimal(x, scale = NULL)
## S3 method for class 'ChunkedArray'
as_decimal(x, scale = NULL)
as_decimal(x, scale = NULL)
## S3 method for class 'decimal'
as_decimal(x, scale = NULL)
## S3 method for class 'character'
as_decimal(x, scale = NULL)
## S3 method for class 'integer'
as_decimal(x, scale = NULL)
## S3 method for class 'numeric'
as_decimal(x, scale = NULL)
## Default S3 method:
as_decimal(x, scale = NULL)
Arguments
x |
A decimal, character, integer, or double vector, or an Arrow
|
scale |
An integer scalar giving the number of fractional digits to
store, or |
Details
When scale is NULL, getOption("decimal.default_scale") is used when
set. Without that option, character input uses the largest number of
fractional digits found in the input, integer input uses scale zero, and
double input raises an error. Quantization uses the active
decimal_context().
An Arrow Array or ChunkedArray of any decimal type converts exactly,
taking its scale from the Arrow type rather than from the values, so a
chunk holding only whole numbers keeps its declared fractional digits. An
Arrow integer array converts exactly at every width. Any other Arrow type
converts to an R vector first and then follows the rules above. See
decimal_arrow and vignette("arrow-decimal-types").
Value
A decimal vector.
Examples
as_decimal(c("1.2", "3.45"))
Clear sticky decimal flags
Description
Clear sticky decimal flags
Usage
clear_decimal_flags()
Value
The previously active sticky flags, invisibly.
Examples
clear_decimal_flags()
Construct decimal vectors
Description
decimal() creates an immutable decimal vector backed by exact strings, at
a single shared scale (number of fractional digits) for the whole vector.
Character and integer inputs are parsed exactly. Promotion to a finer
shared scale only appends zeros and is context-free; a requested coarser
scale quantizes using the active context. Double inputs are decoded from
their exact IEEE 754 binary value and require either an explicit scale or
the decimal.default_scale option before quantization.
Usage
decimal(x = character(), scale = NULL)
Arguments
x |
A decimal, character, integer, or double vector. |
scale |
An integer scalar giving the number of fractional digits to
store. |
Details
When scale is NULL, getOption("decimal.default_scale") is used when
set. Otherwise, character input infers the largest number of fractional
digits present in x, and integer input uses scale zero. Because scale is
a property of the vector, not the element, decimal("1.2") == decimal("1.20"), and combining them yields one uniformly scaled vector.
Value
A decimal vector.
Examples
decimal(c("1.20", "2.30"))
Arrow interoperability
Description
decimal vectors convert to and from Arrow decimal arrays exactly, in both
directions, when the arrow package is installed.
Arrow to decimal. as_decimal() accepts an Arrow Array or
ChunkedArray. A decimal32(), decimal64(), decimal128() or
decimal256() array converts exactly, taking its scale from the Arrow
type. An integer array of any width converts exactly too. Other Arrow
types convert to the equivalent R vector first and follow the ordinary
as_decimal() rules. arrow_as_data_frame() applies the same conversion
to every decimal field of a Table or RecordBatch.
Decimal to Arrow. A decimal vector becomes a decimal field wherever
arrow infers types: arrow::as_arrow_array(), arrow::arrow_table(),
arrow::write_parquet() and arrow::write_dataset(). By default the field
is an Arrow extension type whose storage is a real decimal128() or
decimal256() with the vector's scale and a precision inferred from the
values. Other readers see the storage, a plain decimal column. In R the
column returns as a decimal vector on every read path, as.data.frame(),
arrow::read_parquet() and dplyr::collect() included.
arrow_decimal_type() pins a precision and scale.
Arrow's compute engine does not operate on extension columns, so arrow-side
arithmetic or filtering on such a column fails. To write a plain field
instead, pass a plain Arrow decimal type as type to
arrow::as_arrow_array(), or set the option below. A plain field comes
back from arrow's own conversion as a double wearing the decimal class,
because arrow reapplies the column's recorded R attributes; the package
refuses to format such an object. Read those tables with
arrow_as_data_frame().
Infinities and NaNs have no Arrow decimal representation and raise an error
on conversion to Arrow. Arrow accepts a negative scale but Parquet does not,
so rescale such a vector with as_decimal(x, scale = 0) before
arrow::write_parquet().
Options
decimal.arrow_extension: TRUE (the default) writes the extension type;
FALSE writes plain decimal fields everywhere.
See Also
vignette("arrow-decimal-types").
Create a decimal arithmetic context
Description
Constructs a validated arithmetic context for native mpdecimal
operations. The context controls precision, rounding, exponent limits,
traps, sticky flags, and classification of normal versus subnormal values.
Usage
decimal_context(
precision = 28L,
rounding = "half_even",
emax = 999999L,
emin = -999999L,
traps = decimal_default_traps(),
flags = character(),
clamp = FALSE
)
Arguments
precision |
Integer scalar precision. |
rounding |
One of |
emax |
Integer scalar maximum exponent. |
emin |
Integer scalar minimum exponent. |
traps |
Character vector of trapped signals. Trapped signals raise an
error; all other raised signals are recorded as flags and, unless
|
flags |
Character vector of sticky signal flags. |
clamp |
Logical scalar clamp mode. |
Details
Each operation may raise one or more signals (see decimal_flags()). Their
disposition depends on traps:
A raised signal that is in
trapsstops the operation with an error.Any other raised signal is recorded as a sticky flag and, by default, also surfaced as a warning of class
decimal_flags_warning.
The warnings are purely informational; sticky flags accumulate either way.
Set options(decimal.report_flags = FALSE) to silence them and rely on
decimal_flags() alone.
Public signal names are "clamped", "division_by_zero", "inexact",
"invalid_operation", "overflow", "rounded", "subnormal", and
"underflow". The standard invalid_operation condition groups
lower-level invalid subconditions such as undefined division (0 / 0).
A few operations are exempt from the warning because inexact/rounded
is their guaranteed, expected outcome rather than a surprise:
quantize() (and round()/signif(), built on it), and sqrt(),
exp(), log(), and log10(), which are irrational for nearly every
input. These still accumulate sticky flags as usual.
Value
A decimal_context object.
Examples
decimal_context(precision = 10L)
decimal_context(precision = 3L, rounding = "floor", traps = character())
Read sticky decimal flags
Description
Sticky flags accumulate the signals raised by operations that were not
trapped (see decimal_context()). They persist until clear_decimal_flags()
is called. By default the same non-trapped signals are also reported as
warnings as they occur; set options(decimal.report_flags = FALSE) to
silence the warnings and inspect flags only through this function.
Usage
decimal_flags()
Value
A character vector of active sticky flags.
Examples
decimal_flags()
Controlled conversion from double
Description
Decodes each IEEE 754 double to its exact decimal value, then quantizes that
value to the requested scale. This differs from parsing a character literal
such as "0.1". Because the exact binary value of a double can require
dozens of fractional digits, a scale must be given explicitly or configured
with options(decimal.default_scale = ). Quantization uses the active
context's rounding and trap settings.
Usage
decimal_from_double(x, scale = NULL)
Arguments
x |
A double vector. |
scale |
An integer scalar giving the number of fractional digits to
store, or |
Value
A decimal vector.
Examples
decimal_from_double(0.1, scale = 20)
decimal_from_double(c(0.5, 0.25), scale = 2)
Fused multiply-add
Description
fma() computes x * y + z with a single final rounding step. This can be
more accurate than evaluating multiplication and addition separately under
a limited-precision context. The arguments follow normal vctrs recycling
rules.
Usage
fma(x, y, z)
Arguments
x, y, z |
Decimal-compatible vectors. |
Value
A decimal vector.
Examples
fma(decimal("2"), decimal("3"), decimal("4"))
Get the active decimal arithmetic context
Description
Get the active decimal arithmetic context
Usage
get_decimal_context()
Value
A decimal_context object.
Examples
get_decimal_context()
Test whether an object is a decimal vector
Description
is_decimal() reports whether x inherits from the decimal vector
class. It does not attempt to parse or convert other objects.
Usage
is_decimal(x)
Arguments
x |
An object to test. |
Value
A single logical value.
Examples
is_decimal(decimal("1.5"))
Identify normal decimal values
Description
is_normal() reports whether each finite, nonzero value is normal under
the active decimal context. Normality depends on the context's exponent
limits and precision.
Usage
is_normal(x)
Arguments
x |
A decimal-compatible vector. |
Value
A logical vector with the same length as x.
Examples
is_normal(decimal(c("1", "0", "Infinity")))
Identify quiet NaN values
Description
is_qnan() identifies quiet not-a-number values. Signaling NaNs and R
missing values are not quiet NaNs.
Usage
is_qnan(x)
Arguments
x |
A decimal-compatible vector. |
Value
A logical vector with the same length as x.
Examples
is_qnan(decimal(c("NaN", "sNaN", "1")))
Identify values with a negative sign
Description
is_signed() inspects the stored sign bit rather than comparing with zero.
It therefore identifies negative zero as signed.
Usage
is_signed(x)
Arguments
x |
A decimal-compatible vector. |
Value
A logical vector with the same length as x.
Examples
is_signed(decimal(c("-2", "2", "-0", "0")))
Identify signaling NaN values
Description
is_snan() identifies signaling not-a-number values without performing an
arithmetic operation or raising the invalid_operation signal.
Usage
is_snan(x)
Arguments
x |
A decimal-compatible vector. |
Value
A logical vector with the same length as x.
Examples
is_snan(decimal(c("sNaN", "NaN", "1")))
Identify subnormal decimal values
Description
is_subnormal() reports whether each finite, nonzero value is subnormal
under the active decimal context. A value can therefore be subnormal in one
context and normal in another.
Usage
is_subnormal(x)
Arguments
x |
A decimal-compatible vector. |
Value
A logical vector with the same length as x.
Examples
x <- decimal("0.001")
with_decimal_context(
decimal_context(precision = 3L, emin = -2L),
is_subnormal(x)
)
Identify decimal zeros
Description
is_zero() identifies both positive and negative zero, regardless of the
vector's scale.
Usage
is_zero(x)
Arguments
x |
A decimal-compatible vector. |
Value
A logical vector with the same length as x.
Examples
is_zero(decimal(c("0.00", "-0", "1")))
Install a decimal context for the current scope
Description
Install a decimal context for the current scope
Usage
local_decimal_context(x, .local_envir = parent.frame())
Arguments
x |
A |
.local_envir |
Environment whose scope should control restoration. |
Value
x, invisibly.
Examples
f <- function() {
local_decimal_context(decimal_context(precision = 2L, traps = character()))
decimal("1.234") + decimal("0")
}
f()
Report the bundled mpdecimal runtime version
Description
Returns the version string reported by the bundled mpdecimal library that
was loaded with the package DLL.
Usage
mpdecimal_version()
Value
A length-one character vector.
Examples
mpdecimal_version()
Remove unnecessary trailing zeros
Description
normalize() reduces each finite value to its shortest equivalent decimal
representation, then chooses the finest scale required by any element so
the result remains a valid shared-scale decimal vector. Special values pass
through unchanged.
Usage
normalize(x)
Arguments
x |
A decimal-compatible vector. |
Value
A normalized decimal vector.
Examples
normalize(decimal(c("1.2300", "1.2")))
Classify decimal values
Description
number_class() returns the General Decimal Arithmetic class of each
value. Possible finite classes include "+Normal", "-Normal",
"+Subnormal", "-Subnormal", "+Zero", and "-Zero"; infinities and
NaNs have their corresponding class names. Normal and subnormal classes
depend on the active decimal context.
Usage
number_class(x)
Arguments
x |
A decimal-compatible vector. |
Value
A character vector with the same length as x. R missing values
produce NA_character_.
Examples
number_class(decimal(c("1", "-0", "Infinity", "NaN")))
Quantize decimal values to a scale
Description
quantize() rounds each value in x to the scale declared by quantum.
The operation uses the active context's rounding mode, updates sticky
flags, and raises any enabled traps. The arguments follow normal vctrs
recycling rules.
Usage
quantize(x, quantum)
Arguments
x |
A decimal-compatible vector to quantize. |
quantum |
A decimal-compatible vector whose shared scale determines the result scale. |
Details
Reducing the scale is the explicit purpose of quantize() (and of
round() and signif(), both implemented on top of it), so the inexact
and rounded signals this commonly raises are not reported as warnings
the way other operations' signals are (see decimal_context()); they
still accumulate as sticky flags.
Value
A decimal vector with the shared scale of quantum.
Examples
quantize(decimal("1.23456"), decimal("0.01"))
Compare decimal vector scales
Description
same_quantum() tests whether x and y have the same shared vector
scale. Scale is a vector-level property in this package, so every recycled
element comparison receives the same result.
Usage
same_quantum(x, y)
Arguments
x, y |
Decimal-compatible vectors. |
Value
A logical vector with the common recycled size of x and y.
Examples
same_quantum(decimal("1.00"), decimal("2.0"))
Set the active decimal arithmetic context
Description
Set the active decimal arithmetic context
Usage
set_decimal_context(x)
Arguments
x |
A |
Value
The previously active decimal_context, invisibly.
Examples
old <- set_decimal_context(decimal_context(precision = 5L))
set_decimal_context(old)
Summarize a decimal vector
Description
The six-number summary that summary() gives for a numeric vector, computed
in exact decimal arithmetic and returned as a decimal vector rather than a
vector of doubles.
Usage
## S3 method for class 'decimal'
summary(object, ..., maxsum = 100L, digits = NULL)
Arguments
object |
A |
... |
These dots must be empty. |
maxsum, digits |
Accepted for compatibility with |
Details
Missing values are removed before the statistics are computed and reported
as an NA's entry, matching summary.default(). As in base R, NaN counts
as missing here, because is.na() is true for it.
The quartiles use the type 7 definition, the default of
stats::quantile(). A quantile that falls between two elements is
interpolated, and the mean divides by the number of elements, so both run
under the active decimal context and may raise inexact and rounded
signals like any other division. The minimum, the maximum, and any quantile
that lands exactly on an element are always exact.
Because a decimal vector carries one shared scale, every entry is padded to
the widest one present – including the NA's count, which is a count rather
than a measured value. Interpolating a quartile can need more digits than the
input carries, which widens that shared scale.
Interpolating between -Infinity and Infinity is an invalid operation, and
the default context traps it, so summarizing a vector that spans both signed
infinities raises an error rather than returning NaN quartiles. That is the
same error decimal("Infinity") - decimal("Infinity") raises. Clear the trap
with with_decimal_context() to get base R's NaN instead.
Value
A named decimal vector holding Min., 1st Qu., Median,
Mean, 3rd Qu. and Max., followed by NA's when the input contains
missing values.
Examples
summary(decimal(c("1.25", "2.50", "3.75", "10.00")))
# Missing values are counted, not propagated.
summary(decimal(c("1.5", NA, "2.5")))
Arithmetic for decimal vectors
Description
Implements the vctrs arithmetic group generic (vctrs::vec_arith())
for decimal vectors. It is not normally called directly; it dispatches when
decimals are combined with operators such as +, -, *, and /.
Usage
## S3 method for class 'decimal'
vec_arith(op, x, y, ...)
Arguments
op |
A length-one character vector giving the arithmetic operator. |
x, y |
A pair of vectors, at least one of which is a |
... |
Passed on to methods. |
Value
A decimal vector with the result of the operation.
Examples
decimal("1.5") + decimal("2.5")
decimal(c("10", "20")) * 3L
Use a decimal context within a block
Description
Use a decimal context within a block
Usage
with_decimal_context(x, code)
Arguments
x |
A |
code |
Code evaluated with |
Value
The result of code.
Examples
with_decimal_context(
decimal_context(precision = 3L, traps = character()),
decimal("1.25") + decimal("0")
)