Skip to content

Commit

Permalink
Merge pull request #86 from barucden/float
Browse files Browse the repository at this point in the history
Fix conversion from floats
  • Loading branch information
barucden authored Nov 7, 2024
2 parents 514bf8a + 4d37211 commit 2fdb778
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 110 deletions.
4 changes: 0 additions & 4 deletions src/arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
48 changes: 35 additions & 13 deletions src/decimal.jl
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
42 changes: 0 additions & 42 deletions test/test_arithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
63 changes: 23 additions & 40 deletions test/test_decimal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
2 changes: 0 additions & 2 deletions test/test_equals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/test_hash.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
15 changes: 6 additions & 9 deletions test/test_round.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2fdb778

Please sign in to comment.