Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,7 @@ function Base.Float32(x::Normed{UInt8,f}) where f
f == 5 && return Float32(Int32(x.i) * 0x003) * @f32(0xb02c0bp-30)
f == 6 && return Float32(Int32(x.i) * 0x049) * @f32(0xe40039p-36)
f == 7 && return Float32(Int32(x.i) * 0x01f) * @f32(0x852b5fp-35)
f == 8 && return Float32(Int32(x.i) * 0x155) * @f32(0xc0f0fdp-40)
0.0f0
Comment on lines -160 to -161

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There has been discussion regarding the support range of f since before Issue #311.
Since this is a case where it is better to apply manual dispatch (or the equivalent), I have adopted the current "sentinel".
Although this loses the ability to act as a sentinel against future changes, I think it’s a reasonable change for the purpose of increasing current coverage.

Incidentally, while f is intended to be an Int (Int64 or Int32), intentionally no type restrictions have been imposed.
This allows for “hacks” like the following:

julia> Normed{UInt8, true}(5)
5.0N7ftrue

#= f == 8 =# return Float32(Int32(x.i) * 0x155) * @f32(0xc0f0fdp-40)
end
function Base.Float32(x::Normed{UInt16,f}) where f
f32 = Float32(x.i)
Expand Down
31 changes: 31 additions & 0 deletions test/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,32 @@ function test_convert_from_nan(TX::Type)
end
end

function test_hash_rational(TX::Type)
@testset "hash(::$X)" for X in target(TX, :i8, :i16, :i32, :i64; ex = :thin)
for x in (zero(X), eps(X), typemin(X), typemax(X))
@test x == Rational(x)
@test hash(x) == hash(Rational(x))
end
end
end

# a `Real` type with only the methods that `x % X` needs
struct IntReal <: Real
i::Int
end
Base.:*(a::Integer, x::IntReal) = IntReal(a * x.i)
Base.:*(x::IntReal, a::AbstractFloat) = IntReal(Int(x.i * a))
Base.isfinite(::IntReal) = true
Base.round(x::IntReal) = x
Base.unsafe_trunc(::Type{T}, x::IntReal) where {T <: Integer} = x.i % T

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is for testing (or, more specifically, for code coverage), I suppose it can't be helped, but is it realistic to expect that unsafe_trunc is implemented for custom types?


function test_rem_type(TX::Type)
@testset "% $X" for X in target(TX, :i8, :i16; ex = :thin)
xs = typemin(X):0.1:typemax(X)
@test all(x -> x % X === X(x), xs)
@test wrapping_rem(2, X) === saturating_rem(2, X) === checked_rem(2, X) === 2 % X
@test IntReal(1) % X === 1 % X
@test IntReal(2) % X === 2 % X
end
end

Expand Down Expand Up @@ -400,6 +421,16 @@ function test_isinteger(TX::Type)
end
end

function test_reductions_short(TX::Type)
@testset "sum/prod(::Vector{$X})" for X in target(TX; ex = :thin)
x = typemax(X)
@test sum(X[]) === 0.0
@test prod(X[]) === 1.0
@test sum([x]) === Float64(x)
@test prod([x]) === Float64(x)
end
end

function test_rand(TX::Type)
@testset "rand(::$X)" for X in target(TX; ex = :thin)
@test isa(rand(X), X)
Expand Down
10 changes: 10 additions & 0 deletions test/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ end
@test rationalize(Int16, 0.81Q3f4) === Rational{Int16}(13//16)
@test rationalize(-0.81Q3f4, tol=0.02) === Rational{Int}(-13//16)
@test rationalize(Int8, -0.81Q3f4, tol=0.07) === Rational{Int8}(-3//4)

test_hash_rational(Fixed)
end

@testset "BigFloat conversions" begin
Expand Down Expand Up @@ -403,6 +405,12 @@ end
end
test_mul(Fixed)

# `f == 0` has its own overflow bounds
@test checked_mul(Q7f0(127), Q7f0(1)) === Q7f0(127)
@test checked_mul(Q7f0(-64), Q7f0(2)) === Q7f0(-128)
@test_throws OverflowError checked_mul(Q7f0(64), Q7f0(2))
@test_throws OverflowError checked_mul(Q7f0(-128), Q7f0(-1))

FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearest) === 1.0Q6f1
FixedPointNumbers.mul_with_rounding(1.5Q6f1, -0.5Q6f1, RoundNearest) === -1.0Q6f1
FixedPointNumbers.mul_with_rounding(1.5Q6f1, 0.5Q6f1, RoundNearestTiesUp) === 1.0Q6f1
Expand Down Expand Up @@ -694,6 +702,8 @@ end
acmp = Float64(a[1])*Float64(a[2])
@test prod(a) == acmp
@test prod(a, dims=1) == [acmp]

test_reductions_short(Fixed)
end

@testset "reductions, Statistics" begin
Expand Down
9 changes: 9 additions & 0 deletions test/normed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ end
@test rationalize(Int16, 0.804N0f8) === Rational{Int16}(41//51)
@test rationalize(0.804N0f8, tol=0.002) === Rational{Int}(41//51)
@test rationalize(Int8, 0.804N0f8, tol=0.005) === Rational{Int8}(4//5)

test_hash_rational(Normed)
end

@testset "BigFloat conversions" begin
Expand Down Expand Up @@ -252,6 +254,11 @@ end
@test isa(y, T)
end

# the generic method serves `AbstractFloat` types without a dedicated method
for Tf in (Float16, Float32, Float64), x in (0.2N0f8, 0.2N0f16, 0.2N2f14, 0.2N0f32, 0.2N0f64)
@test invoke(Tf, Tuple{Normed}, x) === Tf(x)

@kimikage kimikage Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this invoke actually called in real codes?
IIUC, in the case of a non-concrete type, dynamic dispatch should call the respective specialized methods.

I think it would probably be better to prepare a Unsigned or AbstractFloat type for testing purposes.

Also, although this is outside the scope of this PR, it might be a good idea to include BFloat16 in the test cases.

end

for Tf in (Float16, Float32, Float64)
@testset "$Tf(::$N)" for N in target(Normed, :i8, :i16)
T = rawtype(N)
Expand Down Expand Up @@ -672,6 +679,8 @@ end
acmp = Float64(a[1])*Float64(a[2])
@test prod(a) == acmp
@test prod(a, dims=1) == [acmp]

test_reductions_short(Normed)
end

@testset "reductions, Statistics" begin
Expand Down
2 changes: 2 additions & 0 deletions test/traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ struct MyReal <: Real end
@test floattype(typeof(π)) === Float64

@test_skip(@test_throws MethodError floattype(MyReal)) # TODO: eliminate `@test_skipped` when depwarn is eliminated. See #177.
# meanwhile, the deprecated fallback warns and returns the type unchanged
@test (@test_logs (:warn, r"fallback definition of `floattype`") floattype(MyReal)) === MyReal
Comment on lines 19 to +21

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@test_skip(@test_throws MethodError floattype(MyReal)) # TODO: eliminate `@test_skipped` when depwarn is eliminated. See #177.
# meanwhile, the deprecated fallback warns and returns the type unchanged
@test (@test_logs (:warn, r"fallback definition of `floattype`") floattype(MyReal)) === MyReal
# TODO: eliminate `@test_skip` when the deprecated fallback is eliminated. See #177.
# Meanwhile, verify that the fallback warns and returns the type unchanged.
@test_skip(@test_throws MethodError floattype(MyReal))
@test (@test_logs (:warn, r"fallback definition of `floattype`") floattype(MyReal)) === MyReal

This is a matter of personal preference, but I think leading with # TODO would make it clearer that these two tests are a pair.
@test_skipped is probably a typo.

end
Loading