Detecting over-/underflow for uint64+int64
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
3
down vote
favorite
I am trying to write a function in Go which adds an uint64
value and an int64
value and which detects whether the result can be represented as an uint64
.
func addUintInt64(x uint64, y int64) (uint64, bool)
if y >= 0
z := x + uint64(y)
if z < x
return 0, false
return z, true
else if y == -(1 << 63)
if x < 1<<63
return 0, false
return x - 1<<63, true
else
negY := uint64(-y)
if x < negY
return 0, false
return x - negY, true
(Also on the Go Playground)
The second argument indicates whether the result is in the range of an int64
. If this is true
, the first argument is the sum of x
and y
.
My questions:
Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?
Is there a nicer or more obvious looking way to write this code? The fact that I'm not sure whether the code is correct probably indicates that it is not well written.
Is there a more efficient way to perform this task? In particular, I would love to get rid of the special case for
y == -(1<<63)
. Note that I don't care about the first result, in cases where the second result isfalse
. Maybe this offers some potential for optimisations?
go integer
add a comment |Â
up vote
3
down vote
favorite
I am trying to write a function in Go which adds an uint64
value and an int64
value and which detects whether the result can be represented as an uint64
.
func addUintInt64(x uint64, y int64) (uint64, bool)
if y >= 0
z := x + uint64(y)
if z < x
return 0, false
return z, true
else if y == -(1 << 63)
if x < 1<<63
return 0, false
return x - 1<<63, true
else
negY := uint64(-y)
if x < negY
return 0, false
return x - negY, true
(Also on the Go Playground)
The second argument indicates whether the result is in the range of an int64
. If this is true
, the first argument is the sum of x
and y
.
My questions:
Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?
Is there a nicer or more obvious looking way to write this code? The fact that I'm not sure whether the code is correct probably indicates that it is not well written.
Is there a more efficient way to perform this task? In particular, I would love to get rid of the special case for
y == -(1<<63)
. Note that I don't care about the first result, in cases where the second result isfalse
. Maybe this offers some potential for optimisations?
go integer
1
This resource on branchless saturating arithmetic may prove helpful (the "saturating" part especially). It is written in C, but could be easily extended to Go, depending on your needs.
â esote
May 25 at 2:13
add a comment |Â
up vote
3
down vote
favorite
up vote
3
down vote
favorite
I am trying to write a function in Go which adds an uint64
value and an int64
value and which detects whether the result can be represented as an uint64
.
func addUintInt64(x uint64, y int64) (uint64, bool)
if y >= 0
z := x + uint64(y)
if z < x
return 0, false
return z, true
else if y == -(1 << 63)
if x < 1<<63
return 0, false
return x - 1<<63, true
else
negY := uint64(-y)
if x < negY
return 0, false
return x - negY, true
(Also on the Go Playground)
The second argument indicates whether the result is in the range of an int64
. If this is true
, the first argument is the sum of x
and y
.
My questions:
Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?
Is there a nicer or more obvious looking way to write this code? The fact that I'm not sure whether the code is correct probably indicates that it is not well written.
Is there a more efficient way to perform this task? In particular, I would love to get rid of the special case for
y == -(1<<63)
. Note that I don't care about the first result, in cases where the second result isfalse
. Maybe this offers some potential for optimisations?
go integer
I am trying to write a function in Go which adds an uint64
value and an int64
value and which detects whether the result can be represented as an uint64
.
func addUintInt64(x uint64, y int64) (uint64, bool)
if y >= 0
z := x + uint64(y)
if z < x
return 0, false
return z, true
else if y == -(1 << 63)
if x < 1<<63
return 0, false
return x - 1<<63, true
else
negY := uint64(-y)
if x < negY
return 0, false
return x - negY, true
(Also on the Go Playground)
The second argument indicates whether the result is in the range of an int64
. If this is true
, the first argument is the sum of x
and y
.
My questions:
Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?
Is there a nicer or more obvious looking way to write this code? The fact that I'm not sure whether the code is correct probably indicates that it is not well written.
Is there a more efficient way to perform this task? In particular, I would love to get rid of the special case for
y == -(1<<63)
. Note that I don't care about the first result, in cases where the second result isfalse
. Maybe this offers some potential for optimisations?
go integer
edited May 25 at 2:08
Jamalâ¦
30.1k11114225
30.1k11114225
asked May 24 at 15:10
jochen
1184
1184
1
This resource on branchless saturating arithmetic may prove helpful (the "saturating" part especially). It is written in C, but could be easily extended to Go, depending on your needs.
â esote
May 25 at 2:13
add a comment |Â
1
This resource on branchless saturating arithmetic may prove helpful (the "saturating" part especially). It is written in C, but could be easily extended to Go, depending on your needs.
â esote
May 25 at 2:13
1
1
This resource on branchless saturating arithmetic may prove helpful (the "saturating" part especially). It is written in C, but could be easily extended to Go, depending on your needs.
â esote
May 25 at 2:13
This resource on branchless saturating arithmetic may prove helpful (the "saturating" part especially). It is written in C, but could be easily extended to Go, depending on your needs.
â esote
May 25 at 2:13
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
2
down vote
accepted
I really like that you put those tests together in the playground, thanks!
I am not a fan of losing information that may be useful. You will always need to check 0-value results to see if they indicate an overflow/underflow condition, so it's just simpler to return a useful result for those times when overflow is OK.... why return 0 when the actual remaining result is just as good?
More specifically, you will always have code like:
value, ok := addUintInt64(x, y)
if !ok
....
so why force the 0 in to the value for !ok
?
In addition, the magnitude of an int64 is never going to exceed half the magnitude of a uint64, so a double-overflow is not possible, and the logic required to check an overflow condition is a case of checking the change, or the direction of the change, of the sign-bit of the equivalent int64 value.
Perhaps this is best described if you handle the complete calculation in int64 space, and convert the result to uint64 - and realize that the MSB of the uint is the sign bit of the int.
The following conditions are all OK:
- the sign bit does not change after the addition, then there's no overflow condition (no possibility of wrap)
- the sign bit changes, but the int value was positive and the source sign bit was not set
- the sign bit changes, but the int value was negative and the source sign bit was set
This is easy to codify in Go as:
result := uint64(int64(x) + y)
before := x >> 63
after := result >> 63
ok := before == after || (before == 0 && y >= 0) || (before == 1 && y < 0)
The above calculates in int64 space, wraps back to uint64 space, and computes the overflow conditions....
Using your same test cases (with the 0 return value for !ok
) you can see this in the playground.
I would rather return the result though even if !ok
, to avoid the additional check, so it would be the function:
func addUintInt64(x uint64, y int64) (uint64, bool) (before == 0 && y >= 0)
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
I really like that you put those tests together in the playground, thanks!
I am not a fan of losing information that may be useful. You will always need to check 0-value results to see if they indicate an overflow/underflow condition, so it's just simpler to return a useful result for those times when overflow is OK.... why return 0 when the actual remaining result is just as good?
More specifically, you will always have code like:
value, ok := addUintInt64(x, y)
if !ok
....
so why force the 0 in to the value for !ok
?
In addition, the magnitude of an int64 is never going to exceed half the magnitude of a uint64, so a double-overflow is not possible, and the logic required to check an overflow condition is a case of checking the change, or the direction of the change, of the sign-bit of the equivalent int64 value.
Perhaps this is best described if you handle the complete calculation in int64 space, and convert the result to uint64 - and realize that the MSB of the uint is the sign bit of the int.
The following conditions are all OK:
- the sign bit does not change after the addition, then there's no overflow condition (no possibility of wrap)
- the sign bit changes, but the int value was positive and the source sign bit was not set
- the sign bit changes, but the int value was negative and the source sign bit was set
This is easy to codify in Go as:
result := uint64(int64(x) + y)
before := x >> 63
after := result >> 63
ok := before == after || (before == 0 && y >= 0) || (before == 1 && y < 0)
The above calculates in int64 space, wraps back to uint64 space, and computes the overflow conditions....
Using your same test cases (with the 0 return value for !ok
) you can see this in the playground.
I would rather return the result though even if !ok
, to avoid the additional check, so it would be the function:
func addUintInt64(x uint64, y int64) (uint64, bool) (before == 0 && y >= 0)
add a comment |Â
up vote
2
down vote
accepted
I really like that you put those tests together in the playground, thanks!
I am not a fan of losing information that may be useful. You will always need to check 0-value results to see if they indicate an overflow/underflow condition, so it's just simpler to return a useful result for those times when overflow is OK.... why return 0 when the actual remaining result is just as good?
More specifically, you will always have code like:
value, ok := addUintInt64(x, y)
if !ok
....
so why force the 0 in to the value for !ok
?
In addition, the magnitude of an int64 is never going to exceed half the magnitude of a uint64, so a double-overflow is not possible, and the logic required to check an overflow condition is a case of checking the change, or the direction of the change, of the sign-bit of the equivalent int64 value.
Perhaps this is best described if you handle the complete calculation in int64 space, and convert the result to uint64 - and realize that the MSB of the uint is the sign bit of the int.
The following conditions are all OK:
- the sign bit does not change after the addition, then there's no overflow condition (no possibility of wrap)
- the sign bit changes, but the int value was positive and the source sign bit was not set
- the sign bit changes, but the int value was negative and the source sign bit was set
This is easy to codify in Go as:
result := uint64(int64(x) + y)
before := x >> 63
after := result >> 63
ok := before == after || (before == 0 && y >= 0) || (before == 1 && y < 0)
The above calculates in int64 space, wraps back to uint64 space, and computes the overflow conditions....
Using your same test cases (with the 0 return value for !ok
) you can see this in the playground.
I would rather return the result though even if !ok
, to avoid the additional check, so it would be the function:
func addUintInt64(x uint64, y int64) (uint64, bool) (before == 0 && y >= 0)
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
I really like that you put those tests together in the playground, thanks!
I am not a fan of losing information that may be useful. You will always need to check 0-value results to see if they indicate an overflow/underflow condition, so it's just simpler to return a useful result for those times when overflow is OK.... why return 0 when the actual remaining result is just as good?
More specifically, you will always have code like:
value, ok := addUintInt64(x, y)
if !ok
....
so why force the 0 in to the value for !ok
?
In addition, the magnitude of an int64 is never going to exceed half the magnitude of a uint64, so a double-overflow is not possible, and the logic required to check an overflow condition is a case of checking the change, or the direction of the change, of the sign-bit of the equivalent int64 value.
Perhaps this is best described if you handle the complete calculation in int64 space, and convert the result to uint64 - and realize that the MSB of the uint is the sign bit of the int.
The following conditions are all OK:
- the sign bit does not change after the addition, then there's no overflow condition (no possibility of wrap)
- the sign bit changes, but the int value was positive and the source sign bit was not set
- the sign bit changes, but the int value was negative and the source sign bit was set
This is easy to codify in Go as:
result := uint64(int64(x) + y)
before := x >> 63
after := result >> 63
ok := before == after || (before == 0 && y >= 0) || (before == 1 && y < 0)
The above calculates in int64 space, wraps back to uint64 space, and computes the overflow conditions....
Using your same test cases (with the 0 return value for !ok
) you can see this in the playground.
I would rather return the result though even if !ok
, to avoid the additional check, so it would be the function:
func addUintInt64(x uint64, y int64) (uint64, bool) (before == 0 && y >= 0)
I really like that you put those tests together in the playground, thanks!
I am not a fan of losing information that may be useful. You will always need to check 0-value results to see if they indicate an overflow/underflow condition, so it's just simpler to return a useful result for those times when overflow is OK.... why return 0 when the actual remaining result is just as good?
More specifically, you will always have code like:
value, ok := addUintInt64(x, y)
if !ok
....
so why force the 0 in to the value for !ok
?
In addition, the magnitude of an int64 is never going to exceed half the magnitude of a uint64, so a double-overflow is not possible, and the logic required to check an overflow condition is a case of checking the change, or the direction of the change, of the sign-bit of the equivalent int64 value.
Perhaps this is best described if you handle the complete calculation in int64 space, and convert the result to uint64 - and realize that the MSB of the uint is the sign bit of the int.
The following conditions are all OK:
- the sign bit does not change after the addition, then there's no overflow condition (no possibility of wrap)
- the sign bit changes, but the int value was positive and the source sign bit was not set
- the sign bit changes, but the int value was negative and the source sign bit was set
This is easy to codify in Go as:
result := uint64(int64(x) + y)
before := x >> 63
after := result >> 63
ok := before == after || (before == 0 && y >= 0) || (before == 1 && y < 0)
The above calculates in int64 space, wraps back to uint64 space, and computes the overflow conditions....
Using your same test cases (with the 0 return value for !ok
) you can see this in the playground.
I would rather return the result though even if !ok
, to avoid the additional check, so it would be the function:
func addUintInt64(x uint64, y int64) (uint64, bool) (before == 0 && y >= 0)
answered May 25 at 3:34
rolflâ¦
90.2k13186390
90.2k13186390
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195094%2fdetecting-over-underflow-for-uint64int64%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
This resource on branchless saturating arithmetic may prove helpful (the "saturating" part especially). It is written in C, but could be easily extended to Go, depending on your needs.
â esote
May 25 at 2:13