diff --git a/src/arithmetic.jl b/src/arithmetic.jl index 28de52c..6f95e95 100644 --- a/src/arithmetic.jl +++ b/src/arithmetic.jl @@ -212,10 +212,6 @@ function Base.:(/)(x::Decimal, y::Decimal) return fix(Decimal(s, c, q)) end -function Base.inv(x::Decimal) - return Decimal(false, BigOne, 0) / x -end - Base.abs(x::Decimal) = fix(Decimal(false, x.c, x.q)) # TODO exponentiation diff --git a/src/decimal.jl b/src/decimal.jl index fd80db3..d7071ef 100644 --- a/src/decimal.jl +++ b/src/decimal.jl @@ -1,16 +1,42 @@ 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 -# From real numbers to Decimal -Decimal(x::Real) = parse(Decimal, string(x)) Base.convert(::Type{Decimal}, x::Real) = Decimal(x) -function BigInt(x::Decimal) - coef = BigInt(x.c * BigTen ^ x.q) - return x.s ? -coef : coef +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 -# From Decimal to numbers -(::Type{T})(x::Decimal) where {T<:Number} = parse(T, string(x)) +(::Type{T})(x::Decimal) where {T<:Number} = T(BigFloat(x)) # String representation of Decimal function Base.string(x::Decimal) @@ -19,15 +45,11 @@ function Base.string(x::Decimal) return String(take!(io)) end -# Zero/one value +Base.signbit(x::Decimal) = x.s + Base.zero(::Type{Decimal}) = Decimal(false, 0, 0) Base.one(::Type{Decimal}) = Decimal(false, 1, 0) Base.iszero(x::Decimal) = iszero(x.c) - -# As long as we do not support Inf/NaN Base.isfinite(x::Decimal) = true Base.isnan(x::Decimal) = false - -# sign -Base.signbit(x::Decimal) = x.s diff --git a/test/test_arithmetic.jl b/test/test_arithmetic.jl index 5c8e284..99118ec 100644 --- a/test/test_arithmetic.jl +++ b/test/test_arithmetic.jl @@ -4,41 +4,6 @@ using Test @testset "Arithmetic" begin -@testset "Addition" begin - @test Decimal(0.1) + 0.2 == 0.1 + Decimal(0.2) == Decimal(0.1) + Decimal(0.2) == Decimal(0.3) - @test Decimal.([0.1 0.2]) .+ [0.3 0.1] == Decimal.([0.4 0.3]) - @test Decimal(2147483646) + Decimal(1) == Decimal(2147483647) - @test Decimal(1,3,-2) + parse(Decimal, "0.2523410412138103") == Decimal(false,2223410412138103,-16) - @test Decimal(0, 10000000000000000001, -19) + Decimal(0, 1, 0) == Decimal(0, 20000000000000000001, -19) -end - -@testset "Subtraction" begin - @test Decimal(0.3) - 0.1 == 0.3 - Decimal(0.1) - @test 0.3 - Decimal(0.1) == Decimal(0.3) - Decimal(0.1) - @test Decimal(0.3) - Decimal(0.1) == Decimal(0.2) - @test Decimal.([0.3 0.1]) .- [0.1 0.5] == Decimal.([0.2 -0.4]) -end - -@testset "Negation" begin - @test -Decimal.([0.3 0.2]) == [-Decimal(0.3) -Decimal(0.2)] - @test -Decimal(0.3) == zero(Decimal) - Decimal(0.3) - @test iszero(Decimal(12.1) - Decimal(12.1)) -end - -@testset "Multiplication" begin - @test Decimal(12.21) * Decimal(2.12) == Decimal(0,258852,-4) - @test Decimal(12.2112543) * Decimal(2.121352) == Decimal(0,259043687318136,-13) - @test Decimal(0.2) * 0.1 == 0.2 * Decimal(0.1) - @test 0.2 * Decimal(0.1) == Decimal(0.02) - @test Decimal(12.34) * 0.1234 == 12.34 * Decimal(0.1234) - @test 12.34 * Decimal(0.1234) == Decimal(1.522756) - @test Decimal(0.21084210) * -2 == -2 * Decimal(0.21084210) - @test -2 * Decimal(0.21084210) == Decimal(-0.4216842) - @test Decimal(0, 2, -1) * 0.0 == zero(Decimal) - @test Decimal.([0.3, 0.6]) .* 5 == [Decimal(0.3)*5, Decimal(0.6)*5] - @test one(Decimal) * 1 == Decimal(0, 1, 0) -end - @testset "Inversion" begin @with_context (Emax=384, Emin=-383, precision=9, rounding=RoundNearestTiesAway) @test inv(dec"1") == dec"1" @with_context (Emax=384, Emin=-383, precision=9, rounding=RoundNearestTiesAway) @test inv(dec"2") == dec"0.5" @@ -108,11 +73,4 @@ end @with_context (Emax=999, Emin=-999, precision=9, rounding=RoundNearestTiesAway) @test inv(dec"2") == dec"0.5" end -@testset "Division" begin - @test Decimal(0.2) / Decimal(0.1) == Decimal(2) - @test Decimal(0.3) / Decimal(0.1) == Decimal(0,3,0) - @test [Decimal(0.3) / Decimal(0.1), Decimal(0.6) / Decimal(0.1)] == [Decimal(0.3), Decimal(0.6)] ./ Decimal(0.1) - @test [Decimal(0.3) / 0.1, Decimal(0.6) / 0.1] == [Decimal(0.3), Decimal(0.6)] ./ 0.1 -end - end diff --git a/test/test_decimal.jl b/test/test_decimal.jl index 60aa335..2a3f751 100644 --- a/test/test_decimal.jl +++ b/test/test_decimal.jl @@ -2,33 +2,20 @@ using Decimals using Test @testset "Conversions" begin - -@testset "String/Number to Decimal" begin - @testset "Direct" begin - @test Decimal(0.01) == Decimal(false, 1, -2) - @test Decimal(.001) == Decimal(false, 1, -3) - @test Decimal(15.23) == Decimal(false, 1523, -2) - @test Decimal(543) == Decimal(false, 543, 0) - @test Decimal(-345) == Decimal(true, 345, 0) - @test Decimal(000123) == Decimal(false, 123, 0) - @test Decimal(-00032) == Decimal(true, 32, 0) - @test Decimal(200100) == Decimal(false, 2001, 2) - @test Decimal(-.123) == Decimal(true, 123, -3) - @test Decimal(1.23000) == Decimal(false, 123, -2) - @test Decimal(4734.612) == Decimal(false, 4734612, -3) - @test Decimal(541724.2) == Decimal(false,5417242,-1) - @test Decimal(2.5e6) == Decimal(false, 25, 5) - @test Decimal(2.385350e8) == Decimal(false, 238535, 3) - @test Decimal(12.3e-4) == Decimal(false, 123, -5) - @test Decimal(-12.3e4) == Decimal(true, 123, 3) - @test Decimal(-12.3e-4) == Decimal(true, 123, -5) - @test Decimal(0.1234567891) == Decimal(false,1234567891, -10) - @test Decimal(0.12345678912) == Decimal(false,12345678912, -11) + @testset "$T" for T in [Float32, Float64, BigFloat] + @test T(Decimal(T(0.0))) == T(0.0) + @test T(Decimal(T(-0.0))) == T(-0.0) + @test T(Decimal(T(1.1))) == T(1.1) + @test T(Decimal(T(-1.1))) == T(-1.1) + @test_throws ArgumentError Decimal(T(Inf)) + @test_throws ArgumentError Decimal(T(NaN)) end -end -@testset "Array{<:Number} to Array{Decimal}" begin - @test Decimal.([0.1 0.2 0.3]) == [Decimal(0.1) Decimal(0.2) Decimal(0.3)] + @testset "$T" for T in [Int32, Int64, BigInt] + @test T(Decimal(T(0))) == T(0) + @test T(Decimal(T(1))) == T(1) + @test T(Decimal(T(-1))) == T(-1) + end end @testset "Decimal to String" begin @@ -44,22 +31,18 @@ end @test string(Decimal(false, 123, -2)) == "1.23" end -@testset "Decimal to Number" begin - @test Float32(Decimal(false, 1, -2)) == 0.01f0 - @test Float64(Decimal(false, 1, -3)) == 0.001 - @test Float64(Decimal(false, 1523, -2)) == 15.23 - @test UInt(Decimal(false, 543, 0)) == 543 - @test Int(Decimal(true, 345, 0)) == -345 - @test Int32(Decimal(false, 123, 0)) == 123 - @test Int8(Decimal(true, 32, 0)) == -32 - @test BigInt(Decimal(false, 2001, 2)) == 200100 - @test BigFloat(Decimal(true, 123, -3)) == big"-0.123" - @test Float64(Decimal(false, 123, -2)) == 1.23 -end - @testset "Number functions" begin + @test signbit(Decimal(0, 0, 1)) == false + @test signbit(Decimal(1, 0, 1)) == true + + @test iszero(zero(Decimal)) + @test iszero(Decimal(0, 0, 1)) + @test !iszero(Decimal(0, 1, 1)) + + @test isone(one(Decimal)) + @test isone(Decimal(0, 1, 0)) + @test !isone(Decimal(0, 0, 1)) + @test isfinite(Decimal(0, 1, 1)) @test !isnan(Decimal(0, 1, 1)) end - -end diff --git a/test/test_equals.jl b/test/test_equals.jl index b850af3..68e5a6c 100644 --- a/test/test_equals.jl +++ b/test/test_equals.jl @@ -6,7 +6,6 @@ using Test @testset "isequal" begin @test isequal(Decimal(false, 2, -3), Decimal(false, 2, -3)) @test !isequal(Decimal(false, 2, -3), Decimal(false, 2, 3)) - @test isequal(Decimal(false, 2, -3), 0.002) @test isequal(Decimal(true, 2, 0), -2) @test !isequal(Decimal(true, 2, 0), 2) @test !isequal(Decimal(true, 0, -1), Decimal(false, 0, 0)) @@ -15,7 +14,6 @@ end @testset "==" begin @test Decimal(false, 2, -3) == Decimal(false, 2, -3) @test Decimal(false, 2, -3) != Decimal(false, 2, 3) - @test Decimal(false, 2, -3) == 0.002 @test -2 == Decimal(true, 2, 0) @test 2 != Decimal(true, 2, 0) diff --git a/test/test_hash.jl b/test/test_hash.jl index 4c65f5d..003283d 100644 --- a/test/test_hash.jl +++ b/test/test_hash.jl @@ -9,6 +9,7 @@ using Test @test hash(Decimal(0.09375)) == hash(0.09375) @test hash(Decimal(-3)) == hash(-3) @test hash(Decimal(-0.09375)) == hash(-0.09375) + @test hash(Decimal(1.1)) == hash(1.1) # Equality implies same hash @test hash(Decimal(0, 100, 0)) == hash(Decimal(0, 10, 1)) diff --git a/test/test_round.jl b/test/test_round.jl index dde597b..9a2af28 100644 --- a/test/test_round.jl +++ b/test/test_round.jl @@ -3,14 +3,11 @@ using Test @testset "Rounding" begin -@test round(Decimal(7.123456), digits=0) == Decimal(7) -@test round(Decimal(7.123456), digits=2) == Decimal(7.12) -@test round(Decimal(7.123456), digits=3) == Decimal(7.123) -@test round(Decimal(7.123456), digits=5) == Decimal(7.12346) -@test round(Decimal(7.123456), digits=6) == Decimal(7.123456) - -@test round.(Decimal.([0.1111, 0.2222, 0.8888]), digits=2) == Decimal.([0.11, 0.22, 0.89]) - -@test trunc(Decimal(7.123456), digits=5) == Decimal(7.12345) +@test round(Decimal(7.123456), digits=0) == dec"7" +@test round(Decimal(7.123456), digits=2) == dec"7.12" +@test round(Decimal(7.123456), digits=3) == dec"7.123" +@test round(Decimal(7.123456), digits=5) == dec"7.12346" +@test round(Decimal(7.123456), digits=6) == dec"7.123456" +@test trunc(Decimal(7.123456), digits=5) == dec"7.12345" end