Skip to content

added support for an arbitrary number of fraction bits #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 24, 2016

Conversation

bjarthur
Copy link
Collaborator

@bjarthur bjarthur commented Aug 12, 2016

i need to add four Image{Gray{UFixed16}} together without overflowing. could convert to Float32, but thought a UFixed{UInt32,16} would be more graceful. so before realizing that the snazzy new @generated was taboo, i refactored ufixed.jl to support arbitrary fractional bit depths. anything goes, like UFixed{UInt64,3}, as an additional example.

all of the tests pass in julia 0.5-rc2.

so why are generated functions to be avoided?

@vchuravy
Copy link
Collaborator

A lot of the generated functions are unnecessary and I think we could rework most of the eval-for loops by using methods like @pure mask{T, f}(::Type{UFixed{T,f}}) = convert(T, 1<<(f-1)). LLVM constant-folds this calculation and we won't need to resort to the inlining trick.

Will put up an PR for that momentarily. Thank you for your help.

@bjarthur bjarthur force-pushed the arbitrarynbits branch 2 times, most recently from ddff03b to f9218ff Compare August 13, 2016 14:44
@bjarthur
Copy link
Collaborator Author

doh! it should've occurred to me that generated functions whose body only contains the returned expression could be rewritten as normal functions. thanks!

i've added more tests, updated the docs, and included a commit, which could be a separate PR if preferred, which fixes the type returned by round and ceil.

any ideas for benchmarks?

one(::Type{$T}) = $T($(2^f-1),0)
end
end
one{T<:UFixed}(::Type{T}) = T((2^nbitsfrac(T)-1),0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This will be costly on v0.4. On v0.5 we can constant fold this, but only if we outline the 2^nbitsfrac(T) into a pure function Base.@pure one_pattern(f)=(2^f-1), but this won't work on v0.4.

One solution would be for now to use the current way on v0.4 with your method as a fallback, and on v0.5 and higher use pure functions on the type-parameter to get the same effect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good catch. thanks. it was 4x slower. fixed for 0.4 and 0.5 with a generated function. that seems a simpler solution to me than having to switch on the version.

@vchuravy
Copy link
Collaborator

Thank you for tackling this. The added generality will be valuable, but we have to make sure that this comes at no extra cost, because FixedPointNumbers is used in performance sensitive places.

Would you mind also adding a few benchmarks with BenchmarkTools?

@bjarthur
Copy link
Collaborator Author

thanks for pointing out BenchmarkTools to me. i used it to fix a few things you pointed out. is it customary to commit benchmarks? if so, where?

end
end

trunc{T<:Integer}(::Type{T}, x::UFixed) = convert(T, div(reinterpret(x), rawone(x)))
round{T<:Integer}(::Type{T}, x::UFixed) = round(T, reinterpret(x)/rawone(x))
floor{T<:Integer}(::Type{T}, x::UFixed) = trunc(T, x)
ceil{T<:Integer}(::Type{T}, x::UFixed) = ceil(T, reinterpret(x)/rawone(x))
trunc(x::UFixed) = trunc(Int, x)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason for these to be removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is part of the second commit. removing them fixes the bug that round(UFixed) returns an Int, not a UFixed.

@timholy
Copy link
Member

timholy commented Aug 19, 2016

This looks quite good to me. I do not personally have a problem with the generated functions. Some could become @pure on Julia 0.5, of course.

@bjarthur
Copy link
Collaborator Author

show now omits FixedPointNumbers for brevity, as requested.

anything else?

@@ -94,4 +94,17 @@ scaledual{T<:FixedPoint}(Tdual::Type, x::Union{T,AbstractArray{T}}) =
scaledual{Tdual<:Number, T<:FixedPoint}(b::Tdual, x::Union{T,AbstractArray{T}}) =
convert(Tdual, b/one(T)), reinterpret(rawtype(T), x)

# printing
@generated function show{T,f}(io::IO, x::FixedPoint{T,f})
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this a @generated function? I would think it is unnecessary here ;)

Copy link
Member

Choose a reason for hiding this comment

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

Right, this should not be @generated, it's not performance-critical and in any case being @generated won't help here.

@vchuravy
Copy link
Collaborator

Looks good to me.

@bjarthur
Copy link
Collaborator Author

is this what you had in mind instead of a generated function?

`0xffff`).

To construct such a number, use `convert(UFixed12, 1.3)`, `ufixed12(1.3)` (a
convenience function), `UFixed{UInt16,12}`, or the literal syntax `0x14ccuf12`.
Copy link
Member

Choose a reason for hiding this comment

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

UFixed{UInt16,12} is a type, not a value.

@timholy
Copy link
Member

timholy commented Aug 24, 2016

Sorry to keep pestering you with review comments. I was about to hit merge and thought I should give it another once-over. I must be less tired this time.

@bjarthur
Copy link
Collaborator Author

dang. good catches. two less evil generated functions now. 0.4 behaves the same.

@timholy timholy merged commit 82f28bf into JuliaMath:master Aug 24, 2016
@timholy
Copy link
Member

timholy commented Aug 24, 2016

Thanks!

BTW, to answer your question above about why they are evil:

All of these actually come down to the same thing: the compiler can't easily reason about what a generated function will do, because they can do arbitrary things.

So really they're only partly evil, but it's fair to say they are to be avoided unless there is no really good alternative.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants