- Require Elixir 1.15 or later.
-
Reduce the cost of every operation that goes through the context: results that signal nothing no longer copy the context or write it back to the process dictionary, the coefficient's digit count is computed once per operation instead of up to four times, and digit counting no longer walks a ladder of bignum comparisons before falling back to its estimate. Division is ~1.9x faster,
add/sub/mult/round/normalize~1.3x, comparison of same-scale values ~1.4x, parsing ~1.2x, all with 20-30% less allocation. -
Make
Decimal.to_float/1scale the operand with a computed shift instead of one bit at a time: ~1.6x faster for typical values and ~11x for exponents near the ends of the double range, with 96% fewer collections.
-
Fix
Decimal.div/2rounding the wrong way on inexact results. The long division discarded its remainder instead of carrying it into rounding as a sticky bit, so a guard digit of 5 with a nonzero tail was treated as an exact tie (:half_even/:half_down) and a guard digit of 0 with a nonzero tail was treated as zero (:ceiling/:floor/:up). Roughly 5% of random divisions at the default precision were affected, including:ceiling/:floorreturning a result on the wrong side of the true value. The same remainder is now also reflected in the:inexactflag, which was previously suppressed when the remainder was a power of ten. -
Re-round the coefficient when a rounding carry lengthens it past the context precision. Rounding an all-nines coefficient up (e.g.
9.99to precision 2, orDecimal.div(95, 10)at precision 1) produced a coefficient with one digit more thanprecision(d(1, 100, -1),d(1, 10, 0)) instead of re-rounding to exactlyprecisionsignificant digits (d(1, 10, 0),d(1, 1, 1)). This affects every context operation (add,sub,mult,div) and now matches the General Decimal Arithmetic spec and Python's decimal.
- Fix
Decimal.parse/2andDecimal.new/2rejecting inspect output for values at the context's full precision with negative exponents (e.g.Decimal.new("0.3162277660168379331998893544432719")). The:max_digitslimit no longer counts non-significant leading zeros.
Decimal.new/2now accepts an optionaloptskeyword list and forwards it toDecimal.parse/2, allowing callers to override:max_digitsand:max_exponentwhen constructing a decimal from a string.
- Fix infinite loop in
Decimal.to_integer/1when the coefficient is zero and the exponent is negative (e.g.Decimal.new("0.0")). Such values now correctly convert to the integer0.
The new decimal128 defaults are more than sufficient for currency and
other real-world numeric use cases. With precision: 34 and a scale of
2 (two digits after the decimal point for cents), values from 0.00 up
to roughly 99_999_999_999_999_999_999_999_999_999_999.99 (~10³², 100
nonillion) round-trip without rounding. Most upgrades from 2.x require
no code changes.
- Make the v2.4.0 mitigations for CVE-2026-32686 the default. The
default
Decimal.Contextand the public parse, cast, and to_string functions now follow IEEE 754 decimal128 limits, rejecting inputs such as1e1000000000without materializing them.
Decimal.Contextdefaults change from precision28and unboundedemax/eminto decimal128 values:precision: 34,emax: 6_144,emin: -6_143. Operation results whose adjusted exponent leaves that band signal overflow or underflow.Decimal.parse/1andDecimal.cast/1reject inputs whose digit count exceeds34(decimal128 precision) or whose absolute exponent exceeds6_144(decimal128 emax). Useparse/2/cast/2withmax_digits: :infinityandmax_exponent: :infinityto restore unbounded behavior.Decimal.parse/2andDecimal.cast/2default:max_digitsto34and:max_exponentto6_144when not specified.Decimal.to_string/2andDecimal.to_string/3raiseArgumentErrorwhen the rendered output would exceed6_178digit characters (precision + emax — the worst-case:normalwidth of any in-range decimal128 value).Inspect,String.Chars, andJSON.Encoderprotocol implementations passmax_digits: :infinityso debug output always succeeds.
- Mitigate exponent amplification (CVE-2026-32686).
Compact inputs such as
1e1000000could force multi-second expansions during arithmetic, parsing, normalization, comparison, or formatting.Decimal.add/2andDecimal.sub/2now scale operands toprecision + 2digits with a sticky bit instead of materializing the full coefficient.
- Add
:max_digitsand:max_exponentoptions toDecimal.parse/2andDecimal.cast/2to reject pathological inputs without expansion - Add
:max_digitsoption toDecimal.to_string/3to cap formatted output before materialization - Add
:emaxand:eminfields toDecimal.Contextfor IBM General Decimal Arithmetic-style overflow and underflow signaling - Optimize hot paths for large decimals:
coef_length,normalize,to_integer,integer?, parsing, and large-coefficient string formatting
- Implement the upcoming
JSON.Encoderprotocol
- Add
Decimal.gte?/2andDecimal.lte?/2 - Add
Decimal.compare/3andDecimal.eq?/3with threshold as parameter
Decimal v2.1 requires Elixir v1.8+.
- Fix
Decimal.compare/2when comparing against0
Decimal v2.1 requires Elixir v1.8+.
- Improve error message from
Decimal.to_integer/1during precision loss Inspectprotocol implementation returns strings in theDecimal.new(...)format- Add
Decimal.scale/1 - Optimize
Decimal.compare/2for numbers with large exponents
- Fix
Decimal.integer?/1spec - Fix
Decimal.integer?/1check on 0 with >1 significant digits
Decimal v2.0 requires Elixir v1.2+.
- Add
Decimal.integer?/1
- Change
Decimal.compare/2to return:lt | :eq | :gt - Change
Decimal.cast/1to return{:ok, t} | :error - Change
Decimal.parse/1to return{t, binary} | :error - Remove
:messageand:resultfields fromDecimal.Error - Remove sNaN
- Rename qNaN to NaN
- Remove deprecated support for floats in
Decimal.new/1 - Remove deprecated
Decimal.minus/1 - Remove deprecated
Decimal.plus/1 - Remove deprecated
Decimal.reduce/1 - Remove deprecated
Decimal.with_context/2,Decimal.get_context/1,Decimal.set_context/1, andDecimal.update_context/1 - Remove deprecated
Decimal.decimal?/1
- Deprecate
Decimal.cmp/2
- Add
Decimal.negate/1 - Add
Decimal.apply_context/1 - Add
Decimal.normalize/1 - Add
Decimal.Context.with/2,Decimal.Context.get/1,Decimal.Context.set/2, andDecimal.Context.update/1 - Add
Decimal.is_decimal/1
- Deprecate
Decimal.minus/1in favour of the newDecimal.negate/1 - Deprecate
Decimal.plus/1in favour of the newDecimal.apply_context/1 - Deprecate
Decimal.reduce/1in favour of the newDecimal.normalize/1 - Deprecate
Decimal.with_context/2,Decimal.get_context/1,Decimal.set_context/2, andDecimal.update_context/1in favour of new functions on theDecimal.Contextmodule - Deprecate
Decimal.decimal?/1in favour of the newDecimal.is_decimal/1
- Fix Decimal.compare/2 with string arguments
- Set :signal on error
- Add
Decimal.cast/1 - Add
Decimal.eq?/2,Decimal.gt?/2, andDecimal.lt?/2 - Add guards to
Decimal.new/3to prevent invalid Decimal numbers
- Add
Decimal.sqrt/1
- Support for canonical XSD representation on
Decimal.to_string/2
- Fix exponent off-by-one when converting from decimal to float
- Fix negative?/1 and positive?/1 specs
- Deprecate passing float to
Decimal.new/1in favor ofDecimal.from_float/1
- Add
Decimal.positive?/1andDecimal.negative?/1 - Accept integers and strings in arithmetic functions, e.g.:
Decimal.add(1, "2.0") - Add
Decimal.from_float/1
- Soft deprecate passing float to
new/1in favor offrom_float/1
- Include the given value as part of the error reason
- Fix
:half_even:lists.lastbug (empty signif) - Fix error message for round
- Fix
:half_downrounding error when remainder is greater than 5 - Fix
Decimal.new/1float conversion with bigger precision than 4 - Fix precision default value
- Fix
Decimal.to_integer/1for large coefficients - Fix rounding of ~0 values
- Fix errors when comparing and adding two infinities