-
-
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
Fix big(typemin(T))
#29340
Fix big(typemin(T))
#29340
Conversation
base/gmp.jl
Outdated
if x isa Union{Int8,Int16,Int32,Int64,Int128} # two's complement | ||
x = s == -1 ? Unsigned(~x) + one(x) : Unsigned(x) | ||
else | ||
s == -1 && (x = -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.
Obviously, this may still fail for other integer types than the above if they use two's complement (or some other representation that does not allow every negative value to be negated without overflow). I'm not sure what to do in the general case, though. At least check that x > 0
after negation and throw an error otherwise to prevent silently wrong results?
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 we need to have an abstract type between Signed
and those builtin types to represent this class of types, e.g. SignedTwoComplement
or something (or a trait indicating that Unsigned
can be applied to the number). For now, what about a mix of your solution and your idea to check that x > 0
:
s == -1 && (x = -x)
if x < 0
x = s == -1 ? Unsigned(~x) + one(x) : Unsigned(x)
end
This may throw at the Unsigned
call for number which don't support that, but only if the basic x = -x
already fails.
Would there be a type-instability performance problem due to having x <: Unsigned
based on a dynamic condition?
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.
Yes, that would introduce type-instability, although the performance-penalty may be acceptably low with the Union
optimizations. However, just the fact that x < 0 && -x < 0
does not necessarily mean that x
is in two complement (although it is very likely, of course.)
I've also considered introducing an unsigned_abs
that returns the absolute value as an Unsigned
type (or rather, a type which can definitely hold abs(x)
and may be unsigned) which could then be implemented for new types as needed. Introducing SignedTwoComplement
sounds more attractive to me, but adding a new abstract type in the Number
hierarchy is, of course, a change that should be well thought through.
That said, the present code is simple and a strict improvement 😄
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.
Ah, but that should be unsigned
rather than Unsigned
because the latter unnecessarily checks that its argument is not negative which we already know here. Will update locally and push once we agree on the general path forward 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.
What if we do the negation on the individual limbs which we know are in two complement? Would require having a carry to be carried across the loop, but that does not sound too bad. Barring better ideas, I'll give it a shot tomorrow.
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.
But doens't that assume anyway two's complement? the previous code (before the bug) was doing something like that, taking the bit pattern of x < 0
and at the end returning -b-1
where b
was the constructed BigInt
. So I actually think we can assume here two's complement without breaking code.
One of my motivation doing this change was to accelerate converting bigger primitive types, like Int512
, so it would be great if the fix works for non-base types!
Don't you think that if x
is transformed to Unsigned
in the case x < 0 && -x < 0
, then the type-instability possible performance hit would occur only for this rare corner case? This seems simpler than implementing the "carry carried across the loop".
What's wrong with the new behavior? |
"Before" = master |
Yep, makes sense. I got the order backwards. |
test/bigint.jl
Outdated
|
||
@testset "conversion from typemin($T)" for T in (Int8, Int16, Int32, Int64, Int128) | ||
x = typemin(T) | ||
@test big(x) == 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.
Is this actually that useful of a test? If the comparison uses promotion, then it won't work correctly.
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.
True. Currently, comparison does not promote (that's how I stumbled upon this), but if it does in the future, this test will become useless. I'll change to use explicit constants.
7a308c4
to
ef633a1
Compare
Ok, having slept this over, KISS seems best here. I'm not even sure we can truely support general, non-two complement integer representations. Apart from the negation I deal with here, we also use |
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.
Thanks for this beautiful KISS solution!
master:
this PR:
(Bug was introduced by #29165, i.e. after 1.0.)