-
-
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
Int64*Uint64 #9292
Comments
I personally dislike this behavior, similar to the C behavior that unsigned*signed == unsigned (which is even worse — C unsigned is downright dangerous because there is no error if you multiply an unsigned integer by a negative one, it just wraps around to a positive result). |
@stevengj, what would you prefer? I'm pretty open to any kind of persuasive argument here and now is a good time to change this kind of thing. |
I would prefer Signed * Unsigned = Signed. Argument: in ordinary math, negative * positive = negative. Deviating from this just to get an extra 1 bit of width in the case of positive values seems unpleasant to me. (In the days of 16-bit integers, 1 bit was a big deal and maybe this was worth it. Nowadays, it seems a poor tradeoff.) |
Google "mix unsigned signed C" for lots of examples of how following C's promotion rules leads to grief. As a result, a lot of people recommend that you just don't use unsigned types in C (google "don't use unsigned"). |
At the very least the current behavior is inconsistent:
|
The argument presented earlier was that when you use unsigned integers, you have a good reason for doing so and you want them to persist. I personally prefer Steve's suggestion here. |
I think that argument is fine but it doesn't work well with the checked conversion from integers to unsigned integers. |
What I suggested elsewhere in passing was having |
|
@stevengj, I googled "mix unsigned signed C" as you said and found a few relevant links:
This article http://critical.eschertech.com/2010/04/07/danger-unsigned-types-used-here/ phrases my argument quite nicely:
So in general, I do agree that it is probably better to produced an Int for mixed UInt/Int arithmetic, but I'm not sure that most of the C horror stories are really relevant to the argument. |
I think that performance concerns are a bit of a false problem here – mixed type operations are not very common in high-performance code and if you really need that speed, you can just use |
Which would lead to a slightly philosophical question of why it's ok for |
+1 for Int * UInt to produce an Int. |
Where I have personally seen the current behavior as being undesirable is that often a (This came up in implementing FFTW, and is why we ended up using |
This seems to me to mostly be an argument for not using |
Yet it's true, as in @stevengj's example, that external C libraries may use |
So far the main argument that seems compelling for mixed |
That is, however, quite a good argument, imo. |
Agreed. I feel there's an analogy with |
When you are using When mixed-signedness operations always convert to signed, then bit mask operations look very cumbersome:
since every literal has to be converted to unsigned. If you are worried about unsigned ints coming from C, and if you want to treat them like signed ints -- then you may as well convert them explicitly to signed ints. |
@eschnett, I think it makes sense for bitwise operations ( |
@stevengj I was thinking more of |
If No matter what convention, there's the danger of an overflow. What about simply disallowing all mixed, "dangerous" operations? Adding |
That's why I was suggesting that the results of all of these be |
My proposal also included treating integer literals in a special way, probably similar to the way math constants are currently handled: |
I'm with @stevengj here. I think if anywhere in your calculation you have an idea of sign, that should be preserved. |
(deleted offtopic question from @udion) I looked into implementing this, and I agree with @JeffBezanson's comment from #15489. Implementing this as a change to the promotion rules turns out to be a big mess, because it screws up bitwise manipulations (e.g. in float.jl) where you want e.g. |
It's not totally true, as the simplest way to write a litteral is as an I see two main use cases:
I would favor use case 2 (for use case 1, a conversion to |
The result of a bitshift should have the same type as the left argument – where does promotion come into it? E.g.:
If there's somewhere we're not doing this, it should be fixed. |
@StefanKarpinski, it turned out that the problem I was initially running into was not due to bitshift. But I still think we probably want |
This is the core "interesting" part of the promotion table: Ts = sort!(Base.uniontypes(Base.BitInteger), by=T->(!(T<:Signed),sizeof(T)));
julia> for (i, T) in enumerate(Ts), (j, S) in enumerate(Ts)
i < j || continue
(T <: Signed) ⊻ (S <: Signed) || continue
P = promote_type(T, S)
@printf "%-7s + %-7s => %-7s\n" T S P
end
Int8 + UInt8 => Int64
Int8 + UInt16 => Int64
Int8 + UInt32 => Int64
Int8 + UInt64 => UInt64
Int8 + UInt128 => UInt128
Int16 + UInt8 => Int64
Int16 + UInt16 => Int64
Int16 + UInt32 => Int64
Int16 + UInt64 => UInt64
Int16 + UInt128 => UInt128
Int32 + UInt8 => Int64
Int32 + UInt16 => Int64
Int32 + UInt32 => Int64
Int32 + UInt64 => UInt64
Int32 + UInt128 => UInt128
Int64 + UInt8 => Int64
Int64 + UInt16 => Int64
Int64 + UInt32 => Int64
Int64 + UInt64 => UInt64
Int64 + UInt128 => UInt128
Int128 + UInt8 => Int128
Int128 + UInt16 => Int128
Int128 + UInt32 => Int128
Int128 + UInt64 => Int128
Int128 + UInt128 => UInt128 @JeffBezanson, could you run this on your little 32-bit machine for the 32-bit version of the table? |
@StefanKarpinski here's is what I get on 32-bit: Int8 + UInt8 => Int32
Int8 + UInt16 => Int32
Int8 + UInt32 => UInt32
Int8 + UInt64 => UInt64
Int8 + UInt128 => UInt128
Int16 + UInt8 => Int32
Int16 + UInt16 => Int32
Int16 + UInt32 => UInt32
Int16 + UInt64 => UInt64
Int16 + UInt128 => UInt128
Int32 + UInt8 => Int32
Int32 + UInt16 => Int32
Int32 + UInt32 => UInt32
Int32 + UInt64 => UInt64
Int32 + UInt128 => UInt128
Int64 + UInt8 => Int64
Int64 + UInt16 => Int64
Int64 + UInt32 => Int64
Int64 + UInt64 => UInt64
Int64 + UInt128 => UInt128
Int128 + UInt8 => Int128
Int128 + UInt16 => Int128
Int128 + UInt32 => Int128
Int128 + UInt64 => Int128
Int128 + UInt128 => UInt128 |
Here's the complete picture then:
|
One observation is that this is a place where the dependence on |
Agreed that the cases where signedness depends on system word size are most ripe for revision. |
The only reasonable platform-independent rules that I can come up with are:
|
One observation is that a common pattern to deal with mixed-signedness code is to put local type annotations on the variables and then let implicit conversion ensure that the types are right, so we should consider that pattern when choosing a behavior and ideally pick a design that allows the storage "locations" to be typed and from there let the compiler work things out and generate efficient, correct code. |
From triage, we've decided to give a try to option 4. |
Another option:
|
- for different-size arguments, the larger type wins - otherwise the unsigned type wins
The remainder of this is part of #15489. |
Since there was a complaint today that not enough detail about the motivation for this decision was recorded, I thought I'd write a bit about it. There are annoying cases no matter which rule you pick, so we picked a rule that was simple to understand and works most of the time. We had previously tried to design rules so that the result value was always possible to represent in the promoted type, but that led to very complex rules that were quite hard for people to remember or reason about. So we picked a rule that was simple to understand and works most of the time:
The thinking for the last rule is that since |
…ly in signedness. This choice was explained by Karpinski in JuliaLang#9292 (comment) but wasn't fully clear in the documentation.
Clarify that the unsigned type is promoted to, if the types differ only in signedness. This choice was explained by Karpinski in #9292 (comment) but wasn't fully clear in the documentation.
Clarify that the unsigned type is promoted to, if the types differ only in signedness. This choice was explained by Karpinski in JuliaLang#9292 (comment) but wasn't fully clear in the documentation.
This doesn't seem like the intended behavior. Are we accidentally trying to do the multiplication in Uint64?
The text was updated successfully, but these errors were encountered: