-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
[DO NOT MERGE] make sinpi/cospi return Int #35823
Conversation
base/special/trig.jl
Outdated
@@ -860,8 +860,8 @@ function cospi(x::T) where T<:Union{Integer,Rational} | |||
end | |||
end | |||
|
|||
sinpi(x::Integer) = x >= 0 ? zero(float(x)) : -zero(float(x)) | |||
cospi(x::Integer) = isodd(x) ? -one(float(x)) : one(float(x)) | |||
sinpi(x::Integer) = 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the return type match the input, i.e. zero(x)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my other comment. I think for consistency, the return type of sinpi
should always be the same as cospi
, though, even if we could actually return Unsigned
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine if they differ, so that some computations remain Unsigned
if all their underlying functions allow it. If someone plugs results of sinpi
and cospi
together, type promotion will fix that difference.
If you don't want a pull request to be merged, just make it draft, so you also prevent someone from accidentally hitting the button 😉 You can also turn to draft after opening the pull request, there is a button on the right |
Thanks! I thought draft PRs could only be created by members. Maybe they changed that. |
I'm still unconvinced that this is a good change. I'd argue that the fact that |
We do generally preserve integerness when a function maps integers to integers. I don't necessarily see why this should be any different. |
I pushed back hard on this on Slack, so I'd thought I'd write it here as well so my objections don't just disappear into the Slack memory hole. Making this function return an Why is it breaking, if
This will not work any more.
This will suddenly fail with this "minor change". Ultimately, I don't think we can just run PkgEval to see any user code it breaks happens to be in a package. Most of my Julia code is not uploaded as a package. Certainly most commercial code is not a public available package. This change could very easily break code we don't even know exists. Also, this is not some obscure corner case of the language, like relying on |
@jakobnissen I'm 100% with you that there is a lot of code outside of public packages that shouldn't break by a Julia upgrade, unless it hasn't been "broken" before. I was happy to see this PR since I had the same thoughts when reading #33341. Hence I consider this more of a fix rather than a breaking feature. Some code will break, that's without question (to me), but has that code been "correct" in the first place?
I assume you mean
then everything is just fine. This would only break if you would use your
As there is no multiple dispatch in this example, what was your intent? Since If it was meant as a readable version of
then I'd say that is a code smell, i.e. broken already. Would you mind to elaborate a bit more on your examples? |
@jonas-schulze Yes, let me be more precise. I don't think it's useful to argue with precise examples of code that would break, because it's very hard to predict what kind of code is in packages we don't know about. What we can argue about is whether useful functionality could be written that depends on My first example highlights that
A more realistic example may be a situation where a container is initialized with You are right that my second example does not show multiple dispatch. It does show strict type annotation in functions. Nonetheless, type annotations is absolutely a thing, e.g. I use it all the time for internal function as a way of documenting what input and output types are expected to catch errors early when developing. One could argue that adding specific type annotations in functions is not the best coding style. That's irrelevant. I like doing it and think it help my development, and in any case, arguing that we can break users' code if we consider their coding style to be poor is a complete non-starter. You are probably right that multiple dispatch is not relevant here. It's hard to imagine a function that does one thing when given an integer and something completely different when given a float. But as mentioned earlier, it's not hard to imagine a function that simply won't work with integers, but will with floats. |
Would it make sense to move this discussion to #35820? I do understand both sides of the argument and am also not 100% sure, this still counts as "minor breaking". The reason I opened this PR was to get a sense of how much code would actually be affected by this. I do understand that just because something passes PkgEval, does not mean this change won't break any other code, but I do think it should be quite representative of real world code. With pretty much every change that is made to Julia, you can of course construct code examples that this change would break, but what I am more interested in is whether this breaks code patterns that are commonly used. |
ad2cb25
to
3004668
Compare
@jakobnissen thanks for explaining, now I got your point. I too like and use type annotations a lot, often times too strict ones too. Breaking stuff because one might consider someone elses coding style poor hasn't been part of my reasoning; I'm not adept enough to judge. I don't want to offend anyone. If instead things break because one hasn't yet fully understood it, is IMHO just part of the learning experience. If Julia would be 100% enterprise, there wouldn't even be "minor breaking" changes. |
@@ -860,8 +860,10 @@ function cospi(x::T) where T<:Union{Integer,Rational} | |||
end | |||
end | |||
|
|||
sinpi(x::Integer) = x >= 0 ? zero(float(x)) : -zero(float(x)) | |||
cospi(x::Integer) = isodd(x) ? -one(float(x)) : one(float(x)) | |||
sinpi(x::T) where {T<:Integer} = zero(signed(T)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine if sinpi
and cospi
have different return types, so that some computations remain Unsigned
if all their underlying functions allow it. If someone plugs results of sinpi
and cospi
together, type promotion will fix that difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the optimizer smart enough to remove the branch for toes where zero=-zero?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I don't know what you mean. For integers there (usually?) is no difference between +0 and -0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think sinpi
and cospi
are too closely related to return different types. They are often used together, for example in cispi
#35792 and sincospi
#35816. Type promotion doesn't address all those problems, since the latter would produce an inhomogenous typed tuple and could produce quite unexpected and hard to debug results in edge cases. What would be the benefit of returning unsigned integers instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like type promotion as quiete preservative with UInt
s already, so I'm ok with your solution.
julia> typeof(UInt8(10) - Int8(2)) == typeof(UInt8(10) + Int8(2)) == UInt8
true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Would we only merge this for purists, and this method not ever used in practice, as "useless"?]
@jonas-schulze, Julia assumes all platforms use two's complement integers (like current C++, unlike C and older C++), https://en.wikipedia.org/wiki/Ones'_complement is very outdated, but given such a type (and with Julia you could make one, I'm not sure why you would), I'm not even sure if it should do the similar to floats:
julia> sin(-0.0)
-0.0
julia> sin(-0.0) == sin(-0) # defined true, even with returning opposite signs (as -0 = 0):
true
[Does -0 == +0 usually, on the ancient one's complement platforms?]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PallHaraldsson That does sound like a very hypothetical example to me. I am not aware of Julia running on any non-two's-complement architectures. This assumption is also made throughout all of Base, so if there was such a platform, these would probably be the least of our worries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I was just addressing his "(usually?) is no difference between +0 and -0." I'm not worried about any of this, and would be ok with status quo, just like division returns floats (as natural at least there). Your change would work with all integer types, I know already defined, e.g. BitIntegers.jl, and all future types hopefully (like if someone made up one's complement on two-complements architecture).
base/special/trig.jl
Outdated
sinpi(x::Integer) = x >= 0 ? zero(float(x)) : -zero(float(x)) | ||
cospi(x::Integer) = isodd(x) ? -one(float(x)) : one(float(x)) | ||
sinpi(x::T) where {T<:Integer} = zero(signed(T)) | ||
sinpi(x::Bool) = 0 |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
What about
|
I believe the current behavior for
But that doesn't sound like a very convincing case to me. |
3004668
to
7ae1eae
Compare
I think the point here is that the current policy of always returning floats for these kind of mathematical functions, even if it's in principle possible to return integers for certain inputs, is easy to understand and predict. If we start making exceptions then it becomes much harder to reason about which functions and what circumstances are exceptional. What's the concrete benefit of returning integers for |
The original idea was to come up with a convenience function that calculates |
The main benefit I see in returning integers is that it avoids unnecessary widening types. Returning |
6dea33e
to
6cb6c58
Compare
ref #35820. There was some discussion on Slack about how breaking this change would be, so could someone with permission trigger PkgEval on this? I think it would be good to get a better understanding of how much code actually relies on the specific return type. Of course, this won't catch performance regressions, e.g. due to dispatching on
generic_matmul
instead of BLAS, so we might still want to be careful, even if PkgEval is successful.