Skip to content

Commit 2b0f723

Browse files
committed
Multiply: implementation + dectests
1 parent 5878cbe commit 2b0f723

File tree

7 files changed

+4663
-26
lines changed

7 files changed

+4663
-26
lines changed

scripts/dectest.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ function translate(io, dectest_path)
149149
else
150150
test = _test(line)
151151
any(isspecial, test.operands) && continue
152+
occursin("Unsupported", directives["rounding"]) && continue
152153
print_test(io, test, directives)
153154
end
154155
end

src/arithmetic.jl

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,115 @@ Base.promote_rule(::Type{BigInt}, ::Type{Decimal}) = Decimal
77
Base.:(+)(x::Decimal) = fix(x)
88
Base.:(-)(x::Decimal) = fix(Decimal(!x.s, x.c, x.q))
99

10-
# Addition
11-
# To add, convert both decimals to the same exponent.
12-
# (If the exponents are different, use the smaller exponent
13-
# to make sure we're adding integers.)
1410
function Base.:(+)(x::Decimal, y::Decimal)
11+
rmod = rounding(Decimal)
12+
13+
# Handle zero operands
14+
if iszero(x) && iszero(y)
15+
s = (x.s && y.s) || (rmod == RoundDown)
16+
return Decimal(s, 0, min(x.q, y.q))
17+
elseif iszero(x) # && !iszero(y)
18+
return +(y)
19+
elseif iszero(y) # && !iszero(x)
20+
return +(x)
21+
end
22+
23+
# Make sure that x.q ≥ y.q
1524
if x.q < y.q
1625
x, y = y, x
1726
end
18-
# Here: x.q ≥ y.q
19-
# a₁ * 10^q₁ + a₂ * 10^q₂ =
20-
# (a₁ * 10^(q₁ - q₂) + a₂) * 10^q₂
21-
# ^^^^^^^^^^^^^^^^^ this is integer because q₁ ≥ q₂
22-
q = x.q - y.q
23-
c = (-1)^x.s * x.c * BigTen^q + (-1)^y.s * y.c
24-
s = signbit(c)
25-
return normalize(Decimal(s, abs(c), y.q))
27+
28+
# We are computing
29+
#
30+
# (-1)^x.s * x.c * 10^x.q + (-1)^y.s * y.c * 10^y.q =
31+
# [(-1)^x.s * x.c * 10^(x.q - y.q) + (-1)^y.s * y.c] * 10^y.q,
32+
#
33+
# where `10^(x.q - y.q)` is an integer because `x.q ≥ y.q`.
34+
#
35+
# Sometimes, `x.q` can be much larger than `y.q`, which would result in
36+
# a huge coefficient `x.c * 10^(x.q - y.q)`. We want to prevent that.
37+
#
38+
# In the end, the resulting coefficient is still constraned by precision.
39+
# Let `Ex, Ey` denote the adjusted exponents of `x` and `y`. If
40+
#
41+
# Ey < min(x.q - 1, Ex - prec - 1)
42+
# ndigits(y.c) + y.q - 1 < min(x.q - 1, ndigits(x.c) + x.q - 1 - prec - 1)
43+
# ndigits(y.c) + y.q < x.q + min(0, ndigits(x.c) - prec - 1)
44+
#
45+
# then we can replace `y` by a number `z` whose adjusted exponent `E`
46+
# satisfies the equation
47+
#
48+
# E = min(x.q - 1, Ex - prec - 1)
49+
#
50+
# and the result `x + z` equals `x + y` after rounding.
51+
#
52+
# From the above equation, we get that
53+
#
54+
# ndigits(z.c) + z.q = x.q + min(0, ndigits(x.c) - precision - 1)
55+
#
56+
# Setting `z.c = 1`, we have `ndigits(z.c) = 1`, and thus
57+
#
58+
# z.q = x.q - 1 + min(0, ndigits(x.c) - precision - 1).
59+
#
60+
# The problem of a huge coefficient is now gone because the difference
61+
#
62+
# x.q - z.q = 1 - min(0, ndigits(x.c) - precision - 1)
63+
# = 1 + max(0, precision - ndigits(x.c) - 1)
64+
#
65+
# is upper-bounded by precision.
66+
67+
v = x.q + min(0, ndigits(x.c) - precision(Decimal) - 1)
68+
if ndigits(y.c) + y.q < v
69+
y = Decimal(y.s, BigOne, v - 1)
70+
end
71+
72+
xc = x.c * BigTen ^ (x.q - y.q)
73+
yc = y.c
74+
q = y.q
75+
76+
# The addition now amounts to
77+
#
78+
# ((-1)^x.s * xc + (-1)^y.s * yc) * 10^q
79+
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
80+
#
81+
# We take some extra steps to compute the addition highlighted above while
82+
# allocating as few BigInts as possible
83+
84+
# If the signs are equal, we compute either `x + y` or `-x - y`, which can
85+
# be generally written as `s * (x + y)`, where `s` is the sign of `x` and `y`
86+
if x.s == y.s
87+
return fix(Decimal(x.s, xc + yc, q))
88+
end
89+
90+
# If the signs `x.s` and `y.s` differ, we compute either `xc - yc` or
91+
# `yc - xc`. There are four possibilities:
92+
#
93+
# Signs
94+
# x y Magnit. Result
95+
# -----------------------------------
96+
# + - xc > yc +(xc - yc) * 10^q
97+
# + - xc ≤ yc -(yc - xc) * 10^q
98+
# - + xc > yc -(xc - yc) * 10^q
99+
# - + xc ≤ yc +(yc - xc) * 10^q
100+
#
101+
# If the result is zero (i.e., `xc == yc`), it should be negative if the
102+
# rounding mode is `RoundDown` and positive otherwise.
103+
104+
if xc == yc
105+
return fix(Decimal(rmod === RoundDown, BigZero, q))
106+
elseif xc > yc
107+
return fix(Decimal(x.s, xc - yc, q))
108+
else
109+
return fix(Decimal(y.s, yc - xc, q))
110+
end
26111
end
27112

28113
# Subtraction
29114
Base.:(-)(x::Decimal, y::Decimal) = +(x, -y)
30115

31-
# Multiplication
32116
function Base.:(*)(x::Decimal, y::Decimal)
33117
s = x.s != y.s
34-
normalize(Decimal(s, BigInt(x.c) * BigInt(y.c), x.q + y.q))
118+
return fix(Decimal(s, x.c * y.c, x.q + y.q))
35119
end
36120

37121
# Inversion

src/bigint.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,17 @@ function cancelfactor(x::BigInt, ::Val{N}) where {N}
5757

5858
return x, q
5959
end
60+
61+
if VERSION < v"1.9.0"
62+
# Taken from PR #41246 at JuliaLang/julia
63+
# The methods are present from 1.9 so if we bump the required Julia version
64+
# to 1.9, we can remove this block
65+
66+
function Base.div(x::Integer, y::Integer, ::typeof(RoundFromZero))
67+
signbit(x) == signbit(y) ? div(x, y, RoundUp) : div(x, y, RoundDown)
68+
end
69+
70+
function Base.rem(x, y, ::typeof(RoundFromZero))
71+
signbit(x) == signbit(y) ? rem(x, y, RoundUp) : rem(x, y, RoundDown)
72+
end
73+
end

0 commit comments

Comments
 (0)