Skip to content
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

Converting between integer types #22235

Closed
wants to merge 8 commits into from
Closed

Conversation

arghhhh
Copy link
Contributor

@arghhhh arghhhh commented Jun 6, 2017

Julia currently has two ways of converting between integer types.
You can construct one integer type from another and this is range checked eg:

julia> Int8(1000)
ERROR: InexactError()

The other way is to use the modulo operator %:

julia> 1000 % Int8
-24

This never fails and just chops the MSBs to give you the answer modulo style - and this is very fast (a single LLVM instruction).

This pull request adds a third method - where you also want the conversion to always succeed, but you want the closest representable value. This is essentially the clamp function followed by the % operator - so I've extended the functionality of clamp rather than make up a new name. eg:

julia> clamp( 260, UInt8 )
0xff

For completeness, I have also added the equivalent in-place function clamp! though it cannot change the type.

base/math.jl Outdated
@@ -41,9 +41,11 @@ end

"""
clamp(x, lo, hi)
clamp(x, T)
Copy link
Member

Choose a reason for hiding this comment

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

Typically in Base APIs, the type argument comes first. So this should be clamp(T, x).

Copy link
Member

Choose a reason for hiding this comment

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

Documentation wasn't updated.

Copy link
Member

Choose a reason for hiding this comment

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

I pushed a commit to fix that.

base/math.jl Outdated
@@ -59,10 +61,13 @@ clamp(x::X, lo::L, hi::H) where {X,L,H} =
convert(promote_type(X,L,H), lo),
convert(promote_type(X,L,H), x)))

clamp( x, ::Type{T} ) where {T} = clamp( x, typemin(T), typemax(T) ) % T
Copy link
Member

Choose a reason for hiding this comment

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

No spaces around parens (here and elsewhere)

@ararslan
Copy link
Member

ararslan commented Jun 6, 2017

How does this differ from trunc(T, x)?

@TotalVerb
Copy link
Contributor

julia> trunc(Int8, 1000)
ERROR: InexactError()
Stacktrace:
 [1] trunc(::Type{Int8}, ::Int64) at ./int.jl:407

julia> clamp(Int8, 1000)
127

base/math.jl Outdated
@@ -72,6 +77,13 @@ function clamp!(x::AbstractArray, lo, hi)
x
end

function clamp!(x::AbstractArray, ::Type{T}) where {T}
Copy link
Contributor

Choose a reason for hiding this comment

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

not strictly related, but is the clamp! function even necessary? Seems to me it could be written

A .= clamp.(lo, A, hi)

instead of

clamp!(A, lo, hi)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I completely agree. It seemed strange that the vector version of clamp has been deprecated but the in place version hadn't.

Copy link
Sponsor Member

@KristofferC KristofferC Jun 6, 2017

Choose a reason for hiding this comment

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

Isn't that the case with multiple in place functions right now, e.g:

fill!(A, x) -> A .= x

Copy link
Member

Choose a reason for hiding this comment

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

fill! is a little different IMO since it's intended to work on the array as a whole, whereas this clamp! method is more like implicit vectorization.

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

What does "intended to work on the array as a whole" mean?

Copy link
Member

Choose a reason for hiding this comment

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

I guess I think of fill! as more like an initialization operation for the array (even though it isn't exactly)

@fredrikekre
Copy link
Member

FWIW, since the type T here has the role of both lo and hi would it not make more sense to define clamp(x, T) as the PR started? I like the symmetry more with clamp(x, hi, lo) and clamp(x, T). As @ararslan pointed out the type is usually the first argument though...

@ararslan
Copy link
Member

ararslan commented Jun 6, 2017

Personally I think it's more important to be consistent with APIs.

@rfourquet
Copy link
Member

I also prefer the symmetry with clamp(x, hi, lo) and clamp(x, T) for the function consistency. Even if rare, a type argument can be not the first argument, e.g. rem(x, T) vs rem(x, y) where the 2nd arg always specifies the destination range.

@ararslan
Copy link
Member

ararslan commented Jun 6, 2017

clamp(T, x) matches trunc(T, x) though, to which it's related. Plus AFAICT no one directly uses the rem(x, T) method; it's more common to do x % T, which looks better than T % x.

@StefanKarpinski
Copy link
Sponsor Member

+1 to this functionality; not sure about the argument order although my first instinct is clamp(x, T). We could actually support both, but that's kind of messy.

base/math.jl Outdated
@@ -41,9 +41,11 @@ end

"""
clamp(x, lo, hi)
clamp(T, x)
Copy link
Member

@fredrikekre fredrikekre Jun 7, 2017

Choose a reason for hiding this comment

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

I think that this would be more clear (i.e. more distinct) with two separate docstrings instead of trying to explain two things in the same docstring.
Something like:

"""
    clamp(T, x)

Clamp `x` between `typemin(T)` and `typemax(T)` and convert the result to type `T`.

```jldoctest
julia> clamp(Int8, 200)
127

julia> clamp(Int8, -200)
-128
```
"""

base/math.jl Outdated
@@ -59,6 +61,7 @@ clamp(x::X, lo::L, hi::H) where {X,L,H} =
convert(promote_type(X,L,H), lo),
convert(promote_type(X,L,H), x)))

clamp(::Type{T}, x) where {T} = clamp(x, typemin(T), typemax(T)) % T
Copy link
Member

Choose a reason for hiding this comment

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

Maybe where {T<:Integer} ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. It doesn't make sense for floats - the % operator isn't defined, and the problem doesn't exist because conversion to a narrower float clamps to typemin and typemax (+/-Inf) anyway.

base/math.jl Outdated
clamp!(array::AbstractArray, lo, hi)
clamp!(array::AbstractArray, T)
Copy link
Member

Choose a reason for hiding this comment

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

This line should be removed since you aren't defining this method

@rfourquet
Copy link
Member

Sorry to insist for the order of parameters, but in my eyes clamp(T, x) looks really confusing. The fact that a type often comes first in Base APIs is not a convention (AFAICT) we try to follow, like having the first arg being the mutated one in mutating functions. I would say that x % T "looks better than" T % x because we are used to having the left arg of % being the value of which we take the remainder, and the right arg specifying the range. Again I see clamp closer to % than to trunc, I feel it's gratuitous to change the argument order depending on whether the range is specified as a type or not. I would like more opinions before merging.

@tkelman
Copy link
Contributor

tkelman commented Jun 8, 2017

Type first is a frequent convention when the return type (or element type) is part of what's asked for in an API, but not an absolutely universal one. I think clamp(T, x) would have more in common with related API's for functions other than clamp, and clamp(x, T) would have more in common with the other methods of clamp since it's used to determine the bounds.

If this had the same promotion behavior as clamp(x, lo, hi) (and there might be an argument that it should?) then I'd be more in favor of clamp(x, T), if it always returns type T then I'd be in favor of clamp(T, x).

@rfourquet
Copy link
Member

Oh, I didn't notice the % T at the end of the definition of clamp(T, x). I think I prefer clamp(x, T) = clamp(x, typemin(T), typemax(T) without conversion to T...

@ararslan
Copy link
Member

ararslan commented Jun 8, 2017

Not converting the result to T would then make it inconsistent with both trunc and %.

@arghhhh
Copy link
Contributor Author

arghhhh commented Jun 9, 2017

For me, the conversion is important - I want to be able to define clamping-conversions for my own integer types - preferably using a standardized interface.

How about:
clamp(T, x) converts - like trunc
clamp(x,T) doesn't convert - like clamp(x, typemin(T), typemax(T)) (I had this wrong originally)

and implement & document both.

@fredrikekre
Copy link
Member

clamp(T1, x, T2) = clamp(x, typemin(T2), typemax(T2)) % T1
clamp(x, T2) = clamp(x, typemin(T2), typemax(T2))
Seems to follow the convention with optional return type T1 as first argument, and T2 takes the place of lo and hi.

@arghhhh
Copy link
Contributor Author

arghhhh commented Jun 9, 2017

Not sure about the three argument version:

julia> Base.clamp( ::Type{T1}, x::T3, ::Type{T2}) where {T1<:Integer,T2,T3} = clamp(x, typemin(T2), typemax(T2)) % T1

julia> clamp( Int8, 10000, Int16 )
16

This has overflowed rather than been clamped.

I guess we could try to limit the type T1 to be 'bigger than or equal to' T2. The original use of '%' was to do a quick and safe (from overflow) conversion.

It seems simpler just to have two variants of the two arg version:
clamp(T, x) clamps and safely converts - like trunc with clamping and never throwing
clamp(x, T) doesn't necessarily convert - it is exactly clamp(x, typemin(T), typemax(T)) which returns a value with the type promote_type(typeof(x), typemin(T), typemax(T))

@ararslan
Copy link
Member

ararslan commented Jun 9, 2017

IMO having methods clamp(R, x, T)::R and clamp(x, T)::typeof(x) seems too confusing. I think we should stick to one approach/method here rather than converting in some cases and not in others.

@arghhhh
Copy link
Contributor Author

arghhhh commented Jun 9, 2017

My entire motivation for this was to define a standard name for a clamping-conversion. The existing clamp does not convert, and the one I want to add does. I don't think we need a version where we supply two type names though.

@Keno
Copy link
Member

Keno commented Jan 17, 2020

👍 to the clamp(x, T) argument order. The clamp function takes the range to clamp to after the second argument and here T specifies the range. The fact that that's also the return value is somewhat incidental, so the I don't think the return value argument applies.

Keno added a commit that referenced this pull request Jan 17, 2020
This is #22235, but with the reversed argument order.
```
julia> clamp( 260, UInt8 )
0xff
```

Co-authored-by: "arghhhh <[email protected]>"
Keno added a commit that referenced this pull request Jan 17, 2020
This is #22235, but with the reversed argument order.
```
julia> clamp( 260, UInt8 )
0xff
```

Co-authored-by: "arghhhh <[email protected]>"
@Keno Keno mentioned this pull request Jan 17, 2020
@Keno
Copy link
Member

Keno commented Jan 17, 2020

Rebased with the reversed argument order in #34426.

Keno added a commit that referenced this pull request Jan 19, 2020
This is #22235, but with the reversed argument order.
```
julia> clamp( 260, UInt8 )
0xff
```

Co-authored-by: "arghhhh <[email protected]>"
@Keno Keno closed this Jan 19, 2020
KristofferC pushed a commit that referenced this pull request Apr 11, 2020
This is #22235, but with the reversed argument order.
```
julia> clamp( 260, UInt8 )
0xff
```

Co-authored-by: "arghhhh <[email protected]>"
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.

10 participants