Skip to content

Commit a7a37e7

Browse files
committed
Update tests to handle improved precision
1 parent 9098d54 commit a7a37e7

File tree

3 files changed

+103
-27
lines changed

3 files changed

+103
-27
lines changed

src/FixedPointDecimals.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const FMAFloat = Union{Float16, Float32, Float64, BigFloat}
4242

4343
for fn in [:trunc, :floor, :ceil]
4444
fnname = Symbol(fn, "mul")
45+
opp_fn = fn == :floor ? :ceil : :floor
4546

4647
@eval begin
4748
@doc """
@@ -67,7 +68,11 @@ for fn in [:trunc, :floor, :ceil]
6768
@eval function $fnname{I, T <: FMAFloat}(::Type{I}, x::T, y::T)
6869
a = x * y
6970
b = fma(x, y, -a)
70-
$fn(I, a) + ifelse(isinteger(a), $fn(I, b), zero(I))
71+
if signbit(b)
72+
$fn(I, a) - (isinteger(a) ? $opp_fn(I, abs(b)) : zero(I))
73+
else
74+
$fn(I, a) + (isinteger(a) ? $fn(I, abs(b)) : zero(I))
75+
end
7176
end
7277
end
7378
end

test/runtests.jl

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ using Base.Test
44
using Compat
55
import Base.Checked: checked_mul
66

7+
include("utils.jl")
8+
79
@testset "FixedPointDecimals" begin
810

911
const SFD2 = FixedDecimal{Int16, 2}
@@ -77,6 +79,29 @@ if VERSION < v"0.6.0-dev.1849"
7779
Base.:/(x::UInt128, y::BigInt) = /(promote(x, y)...)
7880
end
7981

82+
# Basic tests for the methods created above
83+
@testset "alt" begin
84+
@test trunc_alt(FD2, 0.0) == FD2(0)
85+
@test floor_alt(FD2, 0.0) == FD2(0)
86+
@test ceil_alt(FD2, 0.0) == FD2(0)
87+
88+
@test trunc_alt(FD2, 2.149) == FD2(2.14)
89+
@test floor_alt(FD2, 2.149) == FD2(2.14)
90+
@test ceil_alt(FD2, 2.149) == FD2(2.15)
91+
92+
@test trunc_alt(FD2, -2.149) == FD2(-2.14)
93+
@test floor_alt(FD2, -2.149) == FD2(-2.15)
94+
@test ceil_alt(FD2, -2.149) == FD2(-2.14)
95+
96+
@test trunc_alt(FD2, nextfloat(0.0)) == FD2(0)
97+
@test floor_alt(FD2, nextfloat(0.0)) == FD2(0)
98+
@test ceil_alt(FD2, nextfloat(0.0)) == FD2(0.01)
99+
100+
@test trunc_alt(FD2, prevfloat(0.0)) == FD2(0)
101+
@test floor_alt(FD2, prevfloat(0.0)) == FD2(-0.01)
102+
@test ceil_alt(FD2, prevfloat(0.0)) == FD2(0)
103+
end
104+
80105
@testset "max_exp10" begin
81106
@test FixedPointDecimals.max_exp10(Int8) == 2
82107
@test FixedPointDecimals.max_exp10(Int64) == 18
@@ -629,18 +654,13 @@ end
629654
f = FixedPointDecimals.max_exp10(T)
630655
powt = FixedPointDecimals.coefficient(FD{T,f})
631656

632-
# Ideally we would just use `typemax(T)` but due to precision issues with
633-
# floating-point its possible the closest float will exceed `typemax(T)`.
634-
# Additionally, when the division results in a `BigFloat` we need to first
635-
# truncate to a `BigInt` before we can truncate the type we want.
636-
max_int = T(trunc(BigInt, prevfloat(typemax(T) / powt) * powt))
637-
min_int = T(trunc(BigInt, nextfloat(typemin(T) / powt) * powt))
657+
# When converting from typemax to a floating-point it is possible that due to
658+
# precision issues that the closest possible float will exceed the typemax.
659+
max_float = prevfloat(convert(AbstractFloat, typemax(FD{T,f})))
660+
min_float = nextfloat(convert(AbstractFloat, typemin(FD{T,f})))
638661

639-
# floating-point inprecision makes it hard to know exactly that value to
640-
# expect. Since we're primarily looking for issues relating to overflow this
641-
# we can have the expected result be a little flexible.
642-
@test value(trunc(FD{T,f}, max_int / powt)) in [max_int, max_int - 1]
643-
@test value(trunc(FD{T,f}, min_int / powt)) in [min_int, min_int + 1]
662+
@test trunc(FD{T,f}, max_float) == trunc_alt(FD{T,f}, max_float)
663+
@test trunc(FD{T,f}, min_float) == trunc_alt(FD{T,f}, min_float)
644664

645665
@test trunc(reinterpret(FD{T,f}, typemax(T))) == FD{T,f}(div(typemax(T), powt))
646666
@test trunc(reinterpret(FD{T,f}, typemin(T))) == FD{T,f}(div(typemin(T), powt))
@@ -694,23 +714,16 @@ epsi{T}(::Type{T}) = eps(T)
694714
f = FixedPointDecimals.max_exp10(T)
695715
powt = FixedPointDecimals.coefficient(FD{T,f})
696716

697-
# Ideally we would just use `typemax(T)` but due to precision issues with
698-
# floating-point its possible the closest float will exceed `typemax(T)`.
699-
# Additionally, when the division results in a `BigFloat` we need to first
700-
# truncate to a `BigInt` before we can truncate the type we want.
701-
max_int = T(trunc(BigInt, prevfloat(typemax(T) / powt) * powt))
702-
min_int = T(trunc(BigInt, nextfloat(typemin(T) / powt) * powt))
703-
704-
max_dec = max_int / powt
705-
min_dec = min_int / powt
717+
# When converting from typemax to a floating-point it is possible that due to
718+
# precision issues that the closest possible float will exceed the typemax.
719+
max_float = prevfloat(convert(AbstractFloat, typemax(FD{T,f})))
720+
min_float = nextfloat(convert(AbstractFloat, typemin(FD{T,f})))
706721

707-
# Note: Using a larger signed type as the max/min values may be at the
708-
# limits and overflow when adding or subtracting 1.
709-
@test value(floor(FD{T,f}, max_dec)) in [max_int, max_int - 1]
710-
@test value(floor(FD{T,f}, min_dec)) in [min_int, signed(widen(min_int)) - 1]
722+
@test floor(FD{T,f}, max_float) == floor_alt(FD{T,f}, max_float)
723+
@test floor(FD{T,f}, min_float) == floor_alt(FD{T,f}, min_float)
711724

712-
@test value(ceil(FD{T,f}, max_dec)) in [max_int, signed(widen(max_int)) + 1]
713-
@test value(ceil(FD{T,f}, min_dec)) in [min_int, min_int + 1]
725+
@test ceil(FD{T,f}, max_float) == ceil_alt(FD{T,f}, max_float)
726+
@test ceil(FD{T,f}, min_float) == ceil_alt(FD{T,f}, min_float)
714727

715728
# Note: rounding away from zero will result in an exception.
716729
max_int = typemax(T)

test/utils.jl

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Format a floating-point number as a string with a specific precision. Similar to
2+
# `@sprintf("%.$(dp)f", val)`.
3+
4+
if VERSION < v"0.6-"
5+
function float_string(val::AbstractFloat, dp::Integer)
6+
# On Julia 0.5 positive 0.0 can have extra bits. No other integer floating-point
7+
# seems to have this problem.
8+
isequal(val, 0.0) && return rpad("0.", dp, '0')
9+
format = "%.$(dp)f"
10+
@eval @sprintf($("%.$(dp)f"), nextfloat(0.0))
11+
@eval begin
12+
let buffer = Array{UInt8}(100 + $dp)
13+
ccall((:snprintf, :libc), Int, (Ptr{UInt8}, Csize_t, Cstring, Cdouble), buffer, length(buffer), $format, $val)
14+
unsafe_string(pointer(buffer))
15+
end
16+
end
17+
end
18+
else
19+
function float_string(val::AbstractFloat, dp::Integer)
20+
buffer = Array{UInt8}(100 + dp)
21+
ccall((:snprintf, :libc), Int, (Ptr{UInt8}, Csize_t, Cstring, Cdouble), buffer, length(buffer), "%.$(dp)f", val)
22+
unsafe_string(pointer(buffer))
23+
end
24+
end
25+
26+
# FixedDecimal methods which perform an alternative method of trunc, floor, and ceil which
27+
# primarily use the string representation of the floating-point. These `*_alt` methods exist
28+
# to ensure the accuracy of the packages mathematical methods for trunc, floor, and ceil.
29+
30+
function integer_alt{T<:Integer}(::Type{T}, dp::Integer, val::AbstractFloat)
31+
# Note: Use a precision larger than the value can represent so that `sprintf` doesn't
32+
# perform any rounding.
33+
# TODO: Ideally we could be using just be using `@sprintf("%.325f", val)` once this
34+
# issue is fixed: https://github.com/JuliaLang/julia/issues/22137
35+
str = float_string(val, 325) # 325 digits is large enough for `nextfloat(0.0)`
36+
sign = T(first(str) == '-' ? -1 : 1)
37+
decimal = findfirst(str, '.')
38+
int_start = sign < 0 ? 2 : 1
39+
int_end = decimal + dp
40+
v = parse(T, str[int_start:(decimal - 1)] * str[(decimal + 1):int_end])
41+
r = T(any(d -> d != '0', str[(int_end + 1):end])) # Remaining digits
42+
return (sign, v, r)
43+
end
44+
45+
function trunc_alt{T<:Integer,f}(::Type{FD{T,f}}, val::AbstractFloat)
46+
s, v, r = integer_alt(T, f, val)
47+
reinterpret(FD{T,f}, copysign(v, s))
48+
end
49+
50+
function floor_alt{T<:Integer,f}(::Type{FD{T,f}}, val::AbstractFloat)
51+
s, v, r = integer_alt(T, f, val)
52+
reinterpret(FD{T,f}, copysign(v + (s < 0 ? r : zero(T)), s))
53+
end
54+
55+
function ceil_alt{T<:Integer,f}(::Type{FD{T,f}}, val::AbstractFloat)
56+
s, v, r = integer_alt(T, f, val)
57+
reinterpret(FD{T,f}, copysign(v + (s > 0 ? r : zero(T)), s))
58+
end

0 commit comments

Comments
 (0)