Detecting over-/underflow for uint64+int64

The name of the pictureThe name of the pictureThe name of the pictureClash 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:



  1. Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?


  2. 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.


  3. 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 is false. Maybe this offers some potential for optimisations?







share|improve this question

















  • 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

















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:



  1. Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?


  2. 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.


  3. 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 is false. Maybe this offers some potential for optimisations?







share|improve this question

















  • 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













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:



  1. Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?


  2. 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.


  3. 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 is false. Maybe this offers some potential for optimisations?







share|improve this question













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:



  1. Is this code correct? I tested all boundary cases I could think of, but maybe I overlooked something?


  2. 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.


  3. 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 is false. Maybe this offers some potential for optimisations?









share|improve this question












share|improve this question




share|improve this question








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













  • 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











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) 





share|improve this answer





















    Your Answer




    StackExchange.ifUsing("editor", function ()
    return StackExchange.using("mathjaxEditing", function ()
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    );
    );
    , "mathjax-editing");

    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    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






























    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) 





    share|improve this answer

























      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) 





      share|improve this answer























        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) 





        share|improve this answer













        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) 






        share|improve this answer













        share|improve this answer



        share|improve this answer











        answered May 25 at 3:34









        rolfl♦

        90.2k13186390




        90.2k13186390






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            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













































































            Popular posts from this blog

            Greedy Best First Search implementation in Rust

            Function to Return a JSON Like Objects Using VBA Collections and Arrays

            C++11 CLH Lock Implementation