Skip to content

Commit 6eee6cd

Browse files
committed
Implement Base.cmp
This operation is useful for implementing `:(==)`, `:(<)`, and `:(<=)`. The more efficient `cmp` is, the more efficient these comparisons are.
1 parent 29a80b4 commit 6eee6cd

File tree

3 files changed

+897
-28
lines changed

3 files changed

+897
-28
lines changed

src/equals.jl

+68-28
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,85 @@
1-
# Equality
1+
_sign(x::Decimal) = x.s ? -1 : 1
22

3-
# equals() now depends on == instead
4-
# of the other way round.
5-
function Base.:(==)(x::Decimal, y::Decimal)
6-
# return early on zero
7-
x_is_zero = iszero(x)
8-
y_is_zero = iszero(y)
9-
if x_is_zero || y_is_zero
10-
return x_is_zero === y_is_zero
3+
function Base.cmp(x::Decimal, y::Decimal)
4+
if iszero(x) && iszero(y)
5+
return 0
6+
elseif iszero(x) # && !iszero(y)
7+
return -_sign(y)
8+
elseif iszero(y) # && !iszero(x)
9+
return _sign(x)
1110
end
1211

13-
a = normalize(x)
14-
b = normalize(y)
15-
a.c == b.c && a.q == b.q && a.s == b.s
16-
end
12+
# Neither x nor y is zero here
1713

18-
function Base.:(<)(x::Decimal, y::Decimal)
19-
# return early on zero
20-
if iszero(x) && iszero(y)
21-
return false
14+
if x.s != y.s
15+
# x and y have different signs, so
16+
# if x < 0, then return -1 (because y is positive)
17+
# if x > 0, then return +1 (because y is negative)
18+
return _sign(x)
2219
end
2320

24-
# avoid normalization if possible
25-
if x.q == y.q
26-
return isless(x.s == 0 ? x.c : -x.c, y.s == 0 ? y.c : -y.c)
21+
cmp_c = cmp(x.c, y.c)
22+
cmp_q = cmp(x.q, y.q)
23+
24+
# If both x.c and x.q is greater (or equal, or less) than y.c and y.q,
25+
# then x is greater (or equal, or less) than y
26+
if cmp_c == cmp_q
27+
return cmp_c
2728
end
2829

29-
diff = y - x
30+
# Adjusted exponent of x and y
31+
# It is the position of the most significant digit with respect to
32+
# the decimal point
33+
expx = ndigits(x.c) + x.q - 1
34+
expy = ndigits(y.c) + y.q - 1
3035

31-
farther_from_0 = diff.c > 0 || (iszero(diff.c) && diff.q > 0)
36+
# If expx > expy, then abs(x) > abs(y)
37+
# If expx < expy, then abs(x) < abs(y)
38+
#
39+
# Then we need to consider the sign, which is the same for x and y here
40+
#
41+
# Overall:
42+
# -1 if expx > expy and they are negative
43+
# +1 if expx > expy and they are positive
44+
# -1 if expx < expy and they are positive
45+
# +1 if expx < expy and they are negative
46+
if expx != expy
47+
s = _sign(x) # same as _sign(y)
48+
return ifelse(expx > expy, s, -s)
49+
end
3250

33-
if diff.s == 1
34-
return !farther_from_0
51+
# cmp(x, y) = sign(x - y)
52+
# = sign(sign(x) * abs(x) - sign(y) * abs(y))
53+
#
54+
# We know that x and y have the same sign here:
55+
#
56+
# cmp(x, y) = sign(sign(x) * (abs(x) - abs(y)))
57+
# = sign(x) * sign(abs(x) - abs(y))
58+
# = sign(x) * sign(x.c * 10^x.q - y.c * 10^y.q)
59+
#
60+
# Now, for the latter sign:
61+
#
62+
# sign(x.c * 10^x.q - y.c * 10^y.q)
63+
# = sign(x.c * 10^(x.q - y.q) - y.c) * 10^y.q
64+
# = sign(x.c - y.c * 10^(y.q - x.q)) * 10^x.q
65+
# ^^^^^^ positive
66+
#
67+
# So, we just need to return
68+
#
69+
# sign(x) * sign(x.c * 10^(x.q - y.q) - y.c) if x.q ≥ y.q,
70+
# sign(x) * sign(x.c - y.c * 10^(y.q - x.q)) if x.q < y.q
71+
if x.q y.q
72+
q = x.q - y.q
73+
return _sign(x) * cmp(x.c * big(10) ^ q, y.c)
3574
else
36-
return farther_from_0
75+
q = y.q - x.q
76+
return _sign(x) * cmp(x.c, y.c * big(10) ^ q)
3777
end
3878
end
3979

40-
function Base.:(<=)(x::Decimal, y::Decimal)
41-
return x < y || x == y
42-
end
80+
Base.:(==)(x::Decimal, y::Decimal) = iszero(cmp(x, y))
81+
Base.:(<)(x::Decimal, y::Decimal) = cmp(x, y) < 0
82+
Base.:(<=)(x::Decimal, y::Decimal) = cmp(x, y) <= 0
4383

4484
# Special case equality with AbstractFloat to allow comparison against Inf/Nan
4585
# which are not representable in Decimal

test/runtests.jl

+2
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ include("test_hash.jl")
2323
include("test_norm.jl")
2424
include("test_round.jl")
2525

26+
include("test_compare.jl")
27+
2628
end

0 commit comments

Comments
 (0)