Skip to content

Commit 8771f35

Browse files
authored
Arithmetics: Implement Base.sqrt (#102)
1 parent aff7d2b commit 8771f35

File tree

10 files changed

+3660
-20
lines changed

10 files changed

+3660
-20
lines changed

docs/src/operations.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ julia> dec"0" / dec"0"
8989
ERROR: UndefinedDivisionError()
9090
```
9191

92+
### Square root
93+
94+
![Affected by context](https://img.shields.io/badge/ctxt-affected-blue)
95+
96+
Square root is implemented via the `Base.sqrt` function.
97+
```jldoctest
98+
julia> sqrt(dec"9")
99+
3
100+
101+
julia> sqrt(dec"2")
102+
1.414213562373095048801688724
103+
```
104+
92105
### Absolute value
93106

94107
![Affected by context](https://img.shields.io/badge/ctxt-affected-blue)

scripts/dectest.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function translate(io, dectest_path)
4747

4848
test = parse_test(line)
4949
any(isspecial, test.operands) && continue
50+
isspecial(test.result) && continue
5051

5152
dectest = decimal_test(test, directives)
5253
println(io, dectest)
@@ -60,7 +61,7 @@ function isspecial(value)
6061
end
6162

6263
function parse_precision(line)
63-
m = match(r"^precision:\s*(\d+)$", line)
64+
m = match(r"^precision:\s*(\d+).*$", line)
6465
isnothing(m) && throw(ArgumentError(line))
6566
return parse(Int, m[1])
6667
end
@@ -187,6 +188,8 @@ function decimal_operation(operation, operands)
187188
return decimal_reduce(operands...)
188189
elseif operation == "subtract"
189190
return decimal_subtract(operands...)
191+
elseif operation == "squareroot"
192+
return decimal_sqrt(operands...)
190193
else
191194
throw(ArgumentError(operation))
192195
end
@@ -204,4 +207,5 @@ decimal_multiply(x, y) = :($(dec(x)) * $(dec(y)))
204207
decimal_plus(x) = :(+($(dec(x))))
205208
decimal_reduce(x) = :(normalize($(dec(x))))
206209
decimal_subtract(x, y) = :($(dec(x)) - $(dec(y)))
210+
decimal_sqrt(x) = :(sqrt($(dec(x))))
207211

src/Decimals.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
module Decimals
66

77
export Decimal,
8-
number,
98
normalize,
109
@dec_str,
1110
DivisionByZeroError,
@@ -26,7 +25,9 @@ include("bigint.jl")
2625
include("context.jl")
2726
include("conversion.jl")
2827
include("decimal.jl")
29-
include("arithmetic.jl")
28+
include("arithmetic/elementary.jl")
29+
include("arithmetic/exceptions.jl")
30+
include("arithmetic/sqrt.jl")
3031
include("equals.jl")
3132
include("round.jl")
3233
include("hash.jl")

src/arithmetic.jl renamed to src/arithmetic/elementary.jl

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,7 @@
11
Base.promote_rule(::Type{Decimal}, ::Type{<:Real}) = Decimal
2-
3-
# override definitions in Base
42
Base.promote_rule(::Type{BigFloat}, ::Type{Decimal}) = Decimal
53
Base.promote_rule(::Type{BigInt}, ::Type{Decimal}) = Decimal
64

7-
"""
8-
DivisionByZeroError
9-
10-
Division was attempted with a denominator value of 0.
11-
"""
12-
struct DivisionByZeroError <: Exception end
13-
14-
"""
15-
UndefinedDivisionError
16-
17-
Division was attempted with both numerator and denominator value of 0.
18-
"""
19-
struct UndefinedDivisionError <: Exception end
20-
215
Base.:(+)(x::Decimal) = fix(x)
226
Base.:(-)(x::Decimal) = fix(Decimal(!x.s, x.c, x.q))
237

@@ -220,4 +204,3 @@ end
220204

221205
Base.abs(x::Decimal) = fix(Decimal(false, x.c, x.q))
222206

223-
# TODO exponentiation

src/arithmetic/exceptions.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""
2+
DivisionByZeroError
3+
4+
Division was attempted with a denominator value of 0.
5+
"""
6+
struct DivisionByZeroError <: Exception end
7+
8+
"""
9+
UndefinedDivisionError
10+
11+
Division was attempted with both numerator and denominator value of 0.
12+
"""
13+
struct UndefinedDivisionError <: Exception end
14+

src/arithmetic/sqrt.jl

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
function Base.sqrt(x::Decimal)
2+
if iszero(x)
3+
return x
4+
end
5+
6+
if signbit(x)
7+
throw(DomainError(x, "Square root of decimals is defined only for non-negative arguments"))
8+
end
9+
10+
if isone(x)
11+
return x
12+
end
13+
14+
(; c, q) = x
15+
16+
# We are computing
17+
#
18+
# sqrt(c * 10^q) = sqrt(c) * sqrt(10^q)
19+
# = d * sqrt(10^q)
20+
#
21+
# where `d` is constrained to have `precision` digits:
22+
#
23+
# 10^(p - 1) ≤ d ≤ 10^p
24+
# 10^(p - 1) ≤ sqrt(c) ≤ 10^p
25+
# 10^(2 * (p - 1)) ≤ c ≤ 10^(2p)
26+
#
27+
# Consequently, we need `c` to have at least `2 * (precision - 1)` digits.
28+
# However, to figure out correct rounding, we compute in an increased
29+
# precision, `precision + 1`.
30+
n = 1 + 2 * precision(Decimal) - ndigits(c)
31+
if n > 0
32+
c = c * BigTen^n
33+
q = q - n
34+
end
35+
36+
# To make sure, that we can compute `sqrt(10^q)`, we make sure that `q` is
37+
# even, `q = 2r` so that `sqrt(10^q) = sqrt(10^2r) = 10^r`.
38+
if isodd(q)
39+
c = 10 * c
40+
q -= 1
41+
end
42+
43+
d = isqrt(c)
44+
r = q ÷ 2
45+
46+
# If `d % 5 == 0`, we add one to `d` so that `fix` takes care of the
47+
# rounding properly
48+
if isdivisible(d, 5) && d^2 != c
49+
d += 1
50+
end
51+
52+
d, m = cancelfactor(d, Val(10))
53+
r += m
54+
55+
return fix(Decimal(0, d, r))
56+
end
57+

test/arithmetic/test_sqrt.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Decimals
2+
using Test
3+
4+
@testset "sqrt" begin
5+
@test iszero(sqrt(zero(Decimal)))
6+
@test isone(sqrt(one(Decimal)))
7+
@test_throws DomainError sqrt(Decimal(1, 1, 0))
8+
end
9+

0 commit comments

Comments
 (0)