Skip to content

Commit 8ab50a2

Browse files
committed
Context
1 parent 6eee6cd commit 8ab50a2

10 files changed

+1732
-834
lines changed

src/Decimals.jl

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ struct Decimal <: AbstractFloat
2121
end
2222

2323
include("bigint.jl")
24+
include("context.jl")
2425

2526
# Convert between Decimal objects, numbers, and strings
2627
include("decimal.jl")

src/arithmetic.jl

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Base.promote_rule(::Type{Decimal}, ::Type{<:Real}) = Decimal
44
Base.promote_rule(::Type{BigFloat}, ::Type{Decimal}) = Decimal
55
Base.promote_rule(::Type{BigInt}, ::Type{Decimal}) = Decimal
66

7-
const BigTen = BigInt(10)
7+
Base.:(+)(x::Decimal) = fix(x)
8+
Base.:(-)(x::Decimal) = fix(Decimal(!x.s, x.c, x.q))
89

910
# Addition
1011
# To add, convert both decimals to the same exponent.
@@ -24,9 +25,6 @@ function Base.:(+)(x::Decimal, y::Decimal)
2425
return normalize(Decimal(s, abs(c), y.q))
2526
end
2627

27-
# Negation
28-
Base.:(-)(x::Decimal) = Decimal(!x.s, x.c, x.q)
29-
3028
# Subtraction
3129
Base.:(-)(x::Decimal, y::Decimal) = +(x, -y)
3230

src/bigint.jl

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ else
44
const libgmp = Base.GMP.libgmp
55
end
66

7+
const BigTen = BigInt(10)
8+
79
function isdivisible(x::BigInt, n::Int)
810
r = ccall((:__gmpz_divisible_ui_p, libgmp), Cint,
911
(Base.GMP.MPZ.mpz_t, Culong), x, n)

src/context.jl

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
Base.@kwdef mutable struct Context
2+
precision::Int
3+
rounding::RoundingMode
4+
Emax::Int
5+
Emin::Int
6+
end
7+
8+
const CONTEXT = Context(precision=28,
9+
rounding=RoundNearest,
10+
Emax=999999,
11+
Emin=-999999)
12+
13+
function Base.setprecision(::Type{Decimal}, precision::Int)
14+
CONTEXT.precision = precision
15+
return precision
16+
end
17+
18+
Base.precision(::Type{Decimal}) = CONTEXT.precision
19+
20+
function Base.setrounding(::Type{Decimal}, rounding::RoundingMode)
21+
CONTEXT.rounding = rounding
22+
return rounding
23+
end
24+
25+
Base.rounding(::Type{Decimal}) = CONTEXT.rounding
26+
27+
"""
28+
fix(x)
29+
30+
Round and fix the exponent of `x` to keep it within the precision and exponent
31+
limits as given by the current `CONTEXT`.
32+
"""
33+
function fix(x::Decimal)
34+
prec = precision(Decimal)
35+
rmod = rounding(Decimal)
36+
37+
Emin, Emax = CONTEXT.Emin, CONTEXT.Emax
38+
Etiny = Emin - prec + 1
39+
Etop = Emax - prec + 1
40+
41+
if iszero(x)
42+
return Decimal(x.s, x.c, clamp(x.q, Etiny, Etop))
43+
end
44+
45+
clen = ndigits(x.c)
46+
exp_min = clen + x.q - prec
47+
48+
# Equivalent to `clen + x.q - 1 > Emax`
49+
if exp_min > Etop
50+
throw(OverflowError("Exponent limit ($Emax) exceeded: $x"))
51+
end
52+
53+
subnormal = exp_min < Etiny
54+
if subnormal
55+
exp_min = Etiny
56+
end
57+
58+
# Number of digits and exponent within bounds
59+
if x.q exp_min
60+
return x
61+
end
62+
63+
# Signed coefficient for rounding modes like RoundToZero
64+
c = (-1)^x.s * x.c
65+
q = exp_min
66+
67+
# Number of digits of the resulting coefficient
68+
digits = clen + x.q - exp_min
69+
if digits < 0
70+
c = big(1)
71+
q = exp_min - 1
72+
digits = 0
73+
end
74+
75+
# Number of least significant digits to remove from `c`
76+
trun_len = clen - digits
77+
78+
# Split `c` into `digits` most significant digits and `trun_len` least
79+
# significant digits
80+
# This is like round(c, rmod, sigdigits=digits), except here we can
81+
# tell from `rem` if the rounding was lossless
82+
c, rem = divrem(c, BigTen ^ trun_len, rmod)
83+
84+
# Rounding is exact if the truncated digits were zero
85+
exact = iszero(rem)
86+
87+
# If the number of digits exceeded `digits` after rounding,
88+
# it means that `c` was like 99...9 and was rounded up,
89+
# becoming 100...0, so `c` is divisible by 10
90+
if ndigits(c) > prec
91+
c = exactdiv(c, 10)
92+
q += 1
93+
end
94+
95+
# Exponent might have exceeded due to rounding
96+
if q > Etop
97+
throw(OverflowError("Exponent limit ($Emax) exceeded: $x"))
98+
end
99+
100+
if subnormal && !exact
101+
# throw(ErrorException("Underflow"))
102+
end
103+
104+
return Decimal(signbit(c), abs(c), q)
105+
end
106+

0 commit comments

Comments
 (0)