Skip to content

Commit bfb4f47

Browse files
authored
Support floattype(Rational) and require <:AbstractFloat for fallback (#177)
It seems to be a non-sequitur to allow `floattype` to return a type that is not an `AbstractFloat`, unless it has been extended as such. The docs have been enhanced to clarify when it is OK to extend `floattype` in ways that don't return an `AbstractFloat`.
1 parent 0872417 commit bfb4f47

File tree

3 files changed

+55
-8
lines changed

3 files changed

+55
-8
lines changed

src/FixedPointNumbers.jl

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,36 +106,62 @@ floatmax(::Type{T}) where {T <: FixedPoint} = typemax(T)
106106

107107

108108
"""
109-
floattype(::Type{T})
109+
floattype(::Type{T})::Type{<:AbstractFloat}
110110
111-
Return the minimum float type that represents `T` without overflow to `Inf`.
111+
Return a minimal type suitable for performing computations with instances of type `T` without integer overflow.
112112
113-
# Example
113+
The fallback definition of `floattype(T)` applies only to `T<:AbstractFloat`.
114+
However, it is permissible to extend `floattype` to return types that are not subtypes of
115+
`AbstractFloat`; the key characteristic is that the return type should support computation without integer overflow.
116+
117+
In general the returned type should have the minimum bitwidth needed to encode the full precision of the input type.
118+
however, a priority should be placed on computational efficiency; consequently, types like `Float16` should be avoided
119+
except in scenarios where they are guaranteed to have hardware support.
120+
121+
# Examples
114122
115123
A classic usage is to avoid overflow behavior by promoting `FixedPoint` to `AbstractFloat`
116124
117-
```julia
125+
```jldoctest
118126
julia> x = N0f8(1.0)
119127
1.0N0f8
120128
121129
julia> x + x # overflow
122130
0.996N0f8
123131
124-
julia> float_x = floattype(eltype(x))(x)
125-
1.0f0
132+
julia> T = floattype(x)
133+
Float32
126134
127-
julia> float_x + float_x
135+
julia> T(x) + T(x)
128136
2.0f0
129137
```
138+
139+
The following represents a valid extension of `floattype` to non-AbstractFloats:
140+
141+
```julia
142+
julia> using FixedPointNumbers, ColorTypes
143+
144+
julia> floattype(RGB{N0f8})
145+
RGB{Float32}
146+
```
147+
148+
`RGB` itself is not a subtype of `AbstractFloat`, but unlike `RGB{N0f8}` operations with `RGB{Float32}` are not subject to integer overflow.
130149
"""
131-
floattype(::Type{T}) where {T <: Real} = T # fallback
150+
floattype(::Type{T}) where {T <: AbstractFloat} = T # fallback (we want a MethodError if no method producing AbstractFloat is defined)
132151
floattype(::Type{T}) where {T <: Union{ShortInts, Bool}} = Float32
133152
floattype(::Type{T}) where {T <: Integer} = Float64
134153
floattype(::Type{T}) where {T <: LongInts} = BigFloat
154+
floattype(::Type{T}) where {I <: Integer, T <: Rational{I}} = typeof(zero(I)/oneunit(I))
155+
floattype(::Type{<:AbstractIrrational}) = Float64
135156
floattype(::Type{X}) where {T <: ShortInts, X <: FixedPoint{T}} = Float32
136157
floattype(::Type{X}) where {T <: Integer, X <: FixedPoint{T}} = Float64
137158
floattype(::Type{X}) where {T <: LongInts, X <: FixedPoint{T}} = BigFloat
138159

160+
# Non-Real types
161+
floattype(::Type{Complex{T}}) where T = Complex{floattype(T)}
162+
floattype(::Type{Base.TwicePrecision{Float64}}) = Float64 # wider would be nice, but hardware support is paramount
163+
floattype(::Type{Base.TwicePrecision{T}}) where T<:Union{Float16,Float32} = widen(T)
164+
139165
float(x::FixedPoint) = convert(floattype(x), x)
140166

141167
function minmax(x::X, y::X) where {X <: FixedPoint}

src/deprecations.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
import Base.@deprecate_binding
2+
3+
function floattype(::Type{T}) where {T <: Real}
4+
Base.depwarn("""
5+
In a future release, the fallback definition of `floattype` will throw a MethodError if it cannot return a type `<:AbstractFloat`.
6+
See the documentation on `floattype` for guidance on whether to define a custom `floattype(::Type{$T})` method.
7+
""", :floattype)
8+
return T
9+
end

test/traits.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
using FixedPointNumbers, Test
2+
3+
struct MyReal <: Real end
4+
15
@testset "floattype" begin
26
function _is_fixed_type(x::Symbol)
37
try
@@ -16,4 +20,13 @@
1620
for T in exact_types
1721
@test typemax(T) <= maxintfloat(floattype(T))
1822
end
23+
@test floattype(Rational{Int}) === Float64
24+
@test floattype(Complex{Int16}) === Complex{Float32}
25+
@test floattype(Complex{Float32}) === Complex{Float32}
26+
@test floattype(Base.TwicePrecision{Float16}) === Float32
27+
@test floattype(Base.TwicePrecision{Float32}) === Float64
28+
@test floattype(Base.TwicePrecision{Float64}) === Float64
29+
@test floattype(typeof(π)) === Float64
30+
31+
@test_skip(@test_throws MethodError floattype(MyReal)) # TODO: eliminate `@test_skipped` when depwarn is eliminated. See #177.
1932
end

0 commit comments

Comments
 (0)