Repository navigation
Enhancements
-
Make most operations faster. Compared with v3.1.2 on OTP 29, on decimals
with up to 20 digits and exponents from -20 to 20,div/2is about 8x
faster,mult/26x,add/25x,round/34x,compare/22.7x and
normalize/12x. On money amounts with two decimals,to_float/1is about
23x faster,compare/318x,div/215x,rem/2andsqrt/14x,
round/32-3x,add/2,sub/2,mult/2,div_int/2andcompare/2
2-2.7x, and parsing 1.4x.from_float/1andcast/1of a float are about
3.4x faster andcast/1of an integer 3x.to_string/1is unchanged. -
Add the
:subnormaland:clampedsignals from the General Decimal
Arithmetic spec.:subnormalis signalled for every result whose adjusted
exponent is belowemin, even an exact one, and:underflowonly when
such a result is also inexact. A zero result's exponent is kept between
emin - precision + 1andemax, the range of nonzero results, and
:clampedis signalled when it has to move:
Decimal.mult("0e-3500", "0e-3500")returns0E-6176instead of
0E-7000. This also applies to the zero results of division by infinity,
ofdiv_int/2anddiv_rem/2with a smaller dividend, and ofsqrt/1. A
subnormal result that rounds to zero signals:clampedtoo.
Bug fixes
-
Fix
Decimal.div/2rounding some inexact results the wrong way. Under
:half_evenand:half_down, a result just above a tie was rounded as a
tie, and under:ceiling,:floorand:up, a result whose first
discarded digit was 0 was treated as exact even when later digits weren't,
so:ceilingand:floorcould return a value on the wrong side of the
true quotient. About 5% of random divisions at the default precision were
affected.:inexactwas also missing when the remainder was a power of
ten. -
Fix
:uprounding changing exact values. A result whose discarded digits
were all zero was still rounded away from zero: withprecision: 1and
rounding: :up,Decimal.add(0, Decimal.new("9.0"))returned10
instead of9, andDecimal.round(Decimal.new(0), -1, :up)returned
1E+1instead of0E+1. -
Fix rounding carries leaving one digit more than the precision. Rounding
9.99to precision 2 returned10.0instead of10, and
Decimal.div(95, 10)at precision 1 returned10instead of1E+1. This
affected every operation that rounds to the context precision, and the
results now match the General Decimal Arithmetic spec and Python's
decimal. -
Fix
Decimal.sqrt/1rounding inexact roots as if the first discarded
digit were the whole remainder. At precision 9 under:ceiling,
Decimal.sqrt(10)returned3.16227766instead of3.16227767, and at
precision 2 under:half_even,Decimal.sqrt("1.57")returned1.2
instead of1.3. The default:half_uprounding wasn't affected. Inexact
roots now signal:inexactas well as:rounded. -
Fix
Decimal.rem/2andDecimal.div_rem/2computing the remainder from a
product rounded to the context precision, which could cancel it out:
Decimal.rem("9999999999999999999999999999999999", "2.000000000000000000000000000000001")
returned0instead of3E-33. -
Fix
Decimal.to_float/1returning the next float up for odd integers
between 2^52 and 2^53, which doubles represent exactly:
Decimal.to_float(Decimal.new(4_503_599_627_370_497))returned
4503599627370498.0. -
Keep subnormal results instead of flushing them to zero. Since v3.0.0 set
the defaulteminto -6143, every result with an adjusted exponent below
it became 0 with:underflow, even an exact one:
Decimal.div("1e-6140", 10000)returned0instead of1E-6144. Such
results are now rounded at the exponentemin - precision + 1(-6176 by
default) with the context's rounding, as IEEE 754 and the General Decimal
Arithmetic spec specify, and signal:subnormal, plus:underflowwhen
that rounding is inexact. A result that rounds to zero keeps that exponent
(0E-6176).Decimal.round/3no longer flushes an input beloweminto
zero before rounding it. -
Fix
Decimal.round/3rounding an input with more digits than the
precision twice, first with the context's rounding instead ofmode: at
precision 3,Decimal.round(Decimal.new("0.4995"), 1, :down)returned
0.5instead of0.4. -
Make
Decimal.round/3signal:roundedwhen it discards digits of a
nonzero coefficient, and:inexactwhen any of them is nonzero, as the
quantize operation of the General Decimal Arithmetic spec does.
Decimal.round("1.25", 1)returned1.3with no flags. -
Make
Decimal.round/3signal:invalid_operationand return NaN where
quantize does: for ±Infinity, when-placesis outside
emin - precision + 1toemax, and when the result would need more
digits than the precision or an adjusted exponent aboveemax. The signal
is trapped by default, so these calls now raiseDecimal.Error. Before,
the context rounded such results to an exponent other than-places:
Decimal.round(Decimal.new("1e40"), 2)returned
1.000000000000000000000000000000000E+40, andDecimal.round("1.5", 7000)
returned1.500000000000000000000000000000000. ±Infinity was returned
unchanged. -
Make
Decimal.compare/3exact. It roundednum1 ± thresholdto the
context precision, so numbers near the threshold compared wrong:
Decimal.compare(1, "1.000000000000000000000000000000002", "1.5e-33")
returned:eqalthough they differ by2e-33. It no longer sets
:inexactor:rounded, accepts-0as a threshold, and raises
Decimal.Errorinstead ofCondClauseErrorfor a NaN when
:invalid_operationisn't trapped. -
Make
Decimal.compare/2raiseDecimal.Errorfor a NaN operand even when
:invalid_operationisn't trapped, instead of returning the NaN. Comparing
a NaN with ±Infinity now raises too, instead of returning:ltor:gt. -
Make
Decimal.Context.set/1,Decimal.Context.with/2and
Decimal.Context.update/1raiseArgumentErrorfor an invalid context: a
precision that isn't a positive integer, an unknown rounding mode, an
emaxoreminthat is neither an integer nor:infinity, or anemin
aboveemax. A precision of 0 silently gave wrong results
(Decimal.add(1, 1)returned0E+1), and the other cases failed with
FunctionClauseErrorin the next operation. -
Make
Decimal.to_integer/1raiseArgumentErrorfor NaN and ±Infinity,
likeDecimal.to_float/1, instead ofFunctionClauseError.