Skip to content

Commit

Permalink
Normalization and rounding (#88)
Browse files Browse the repository at this point in the history
The PR replaces `round` and `normalize`.

Fixes #27
Fixes #36
Fixes #39
Fixes #50
  • Loading branch information
barucden authored Nov 11, 2024
1 parent 85560c3 commit 92225ac
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 117 deletions.
15 changes: 1 addition & 14 deletions src/Decimals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,13 @@ end

include("bigint.jl")
include("context.jl")

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

# Decimal normalization
include("norm.jl")

# Addition, subtraction, negation, multiplication
include("arithmetic.jl")

# Equality
include("equals.jl")

# Rounding
include("round.jl")

include("hash.jl")

include("parse.jl")

include("show.jl")

end
47 changes: 47 additions & 0 deletions src/conversion.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Decimal(x::Decimal) = x
Decimal(n::Integer) = Decimal(signbit(n), abs(n), 0)
function Decimal(x::AbstractFloat)
if !isfinite(x)
throw(ArgumentError("$x cannot be represented as a Decimal"))
end

# Express `x` as a rational `u = n / 2^k`, where `k ≥ 0`
u = Rational(abs(x))

# u.den = 2^k
k = ndigits(u.den, base=2) - 1

# We can write
#
# x = n / 2^k
# = n / 2^k * 10^k * 10^-k
# = (n * 10^k / 2^k) * 10^-k
# = (n * 5^k) * 10^-k

s = signbit(x)
c = u.num * BigInt(5)^k
q = -k
return Decimal(s, c, q)
end

Base.convert(::Type{Decimal}, x::Real) = Decimal(x)

function Base.BigFloat(x::Decimal)
y = BigFloat(x.c)
if x.q 0
y *= BigTen^x.q
else
y /= BigTen^(-x.q)
end
return x.s ? -y : y
end

(::Type{T})(x::Decimal) where {T<:Number} = T(BigFloat(x))

# String representation of Decimal
function Base.string(x::Decimal)
io = IOBuffer()
scientific_notation(io, x)
return String(take!(io))
end

67 changes: 20 additions & 47 deletions src/decimal.jl
Original file line number Diff line number Diff line change
@@ -1,50 +1,3 @@
Decimal(x::Decimal) = x
Decimal(n::Integer) = Decimal(signbit(n), abs(n), 0)
function Decimal(x::AbstractFloat)
if !isfinite(x)
throw(ArgumentError("$x cannot be represented as a Decimal"))
end

# Express `x` as a rational `u = n / 2^k`, where `k ≥ 0`
u = Rational(abs(x))

# u.den = 2^k
k = ndigits(u.den, base=2) - 1

# We can write
#
# x = n / 2^k
# = n / 2^k * 10^k * 10^-k
# = (n * 10^k / 2^k) * 10^-k
# = (n * 5^k) * 10^-k

s = signbit(x)
c = u.num * BigInt(5)^k
q = -k
return Decimal(s, c, q)
end

Base.convert(::Type{Decimal}, x::Real) = Decimal(x)

function Base.BigFloat(x::Decimal)
y = BigFloat(x.c)
if x.q 0
y *= BigTen^x.q
else
y /= BigTen^(-x.q)
end
return x.s ? -y : y
end

(::Type{T})(x::Decimal) where {T<:Number} = T(BigFloat(x))

# String representation of Decimal
function Base.string(x::Decimal)
io = IOBuffer()
scientific_notation(io, x)
return String(take!(io))
end

Base.signbit(x::Decimal) = x.s

Base.zero(::Type{Decimal}) = Decimal(false, 0, 0)
Expand All @@ -53,3 +6,23 @@ Base.one(::Type{Decimal}) = Decimal(false, 1, 0)
Base.iszero(x::Decimal) = iszero(x.c)
Base.isfinite(x::Decimal) = true
Base.isnan(x::Decimal) = false

"""
normalize(x::Decimal)
Return an equal number reduced to its simplest form with all trailing zeros in
the coefficient removed.
# Examples
```jldoctest
julia> normalize(dec"1.2000")
1.2
julia> normalize(dec"-10000")
-1E+4
```
"""
function normalize(x::Decimal)
c, e = cancelfactor(x.c, Val(10))
return fix(Decimal(x.s, c, x.q + e))
end
16 changes: 0 additions & 16 deletions src/norm.jl

This file was deleted.

58 changes: 41 additions & 17 deletions src/round.jl
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
# Rounding
function Base.round(x::Decimal; digits::Int=0, normal::Bool=false)
shift = BigInt(digits) + x.q
if shift > BigInt(0) || shift < x.q
(normal) ? x : normalize(x, rounded=true)
else
c = Base.round(x.c / BigInt(10)^(-shift))
d = Decimal(x.s, BigInt(c), x.q - shift)
(normal) ? d : normalize(d, rounded=true)
function _round(x::Decimal, r::RoundingMode, digits::Integer)
dec_places = -x.q
if digits dec_places
return x
end

d = dec_places - digits
c = div(x.c, BigTen ^ d, r)
return Decimal(x.s, c, x.q + d)
end

function _round(x::Decimal, r::RoundingMode,
digits::Union{Nothing, Integer},
sigdigits::Union{Nothing, Integer})
if !isnothing(digits) && !isnothing(sigdigits)
throw(ArgumentError("`round` cannot use both `digits` and `sigdigits` arguments"))
end

if isnothing(digits) && isnothing(sigdigits)
digits = 0
elseif isnothing(digits)
digits = -(ndigits(x.c) + x.q - sigdigits)
end

return _round(x, r, digits)
end

function Base.trunc(x::Decimal; digits::Int=0, normal::Bool=false)
shift = BigInt(digits) + x.q
if shift > BigInt(0) || shift < x.q
(normal) ? x : normalize(x, rounded=true)
else
c = Base.trunc(x.c / BigInt(10)^(-shift))
d = Decimal(x.s, BigInt(c), x.q - shift)
(normal) ? d : normalize(d, rounded=true)
for r in (RoundingMode{:Up},
RoundingMode{:Down},
RoundingMode{:FromZero},
RoundingMode{:Nearest},
RoundingMode{:NearestTiesAway},
RoundingMode{:ToZero})
@eval function Base.round(x::Decimal, r::$r;
digits::Union{Nothing, Integer}=nothing,
sigdigits::Union{Nothing, Integer}=nothing)
return _round(x, r, digits, sigdigits)
end

if VERSION < v"1.9"
@eval function Base.round(::Type{T}, x::Decimal, r::$r) where T<:Integer
return T(_round(x, r, 0))
end
end
end

1 change: 0 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ include("test_context.jl")
include("test_decimal.jl")
include("test_equals.jl")
include("test_hash.jl")
include("test_norm.jl")
include("test_parse.jl")
include("test_round.jl")
include("test_show.jl")
Expand Down
6 changes: 6 additions & 0 deletions test/test_decimal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,9 @@ end
@test isfinite(Decimal(0, 1, 1))
@test !isnan(Decimal(0, 1, 1))
end

@testset "Normalize" begin
x = normalize(dec"-15.11000")
@test (x.c % 10) 0
@test x == dec"-15.11"
end
15 changes: 0 additions & 15 deletions test/test_norm.jl

This file was deleted.

Loading

0 comments on commit 92225ac

Please sign in to comment.