why does subtraction overflow with static_cast?

568 Views Asked by At

I understand that s1.size() - s2.size() underflows when s2 is bigger because it's subtraction of unsigned. Why casting one them to int doesn't result in integer subtraction? Why casting the whole thing gives me the correct result? I expected it to evaluate what is inside parentheses, then underflow which would give a big number and then the cast to int would not make difference. What am I missing?

#include <iostream>
#include <string>

using std::cout;
using std::cin;
using std::endl;
using std::string;

bool isShorter(const string &s1, const string &s2) {
    return (static_cast<int>(s1.size()) - s2.size() < 0) ? true : false; // underflows
    //return (static_cast<int>(s1.size() - s2.size()) < 0) ? true : false; // this works

}
int main() { 
    string s, t;
    getline(cin, s);
    getline(cin, t);
    cout << "s: " << s << endl;
    cout << "t: " << t << endl;
    cout << "printing shorter string of the two..." << endl;
    cout << ((isShorter(s, t)) ? s : t) << endl;
}
2

There are 2 best solutions below

0
NathanOliver On BEST ANSWER

When you do

static_cast<int>(s1.size()) - s2.size()

You convert s1.size() to a int and then when you subtract s2.size() from it that int is promoted to the same type as s2.size() and then it is subtracted. This means you still have unsigned integer subtraction and since that can't ever be negative it will wrap around to a larger number. It is no different from doing s1.size() - s2.size().

You have the same thing with

static_cast<int>(s1.size() - s2.size())

With the added bonus of possible signed integer overflow which is undefined behavior. You are still doing unsigned integer subtraction so if s1 is smaller than s2 than you wrap around to a large number.

What you need to do is convert both s1.size() and s2.size() to a signed integer type to get singed integer subtraction. That could look like

static_cast<ptrdiff_t>(s1.size())  - static_cast<ptrdiff_t>(s2.size())

And now you will actually get a negative number if s1.size() is less than s2.size().


It should be noted that all of this can be avoided by using less than operator. Your function can be rewritten to be

bool isShorter(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

which, IMHO, is much easier to read and understand.

0
AnT stands with Russia On

Casting "one of them" to int leaves you with arithmetic operation that mixes string::size_type and int. In this mix the unsigned type has the same rank as int or higher, which means that the unsigned type still "wins": your int is implicitly converted back to string::size_type and the calculations are performed in the domain of string::size_type. Your conversion to int is effectively ignored.

Meanwhile, casting the result to int means that you are attempting to convert a value that does not fit into int's range. The behavior in such cases is implementation defined. In real-life 2's-complement implementations it is not unusual to see a simple truncation of the representation, which produces the "correct" result. This is not a good approach though.

If you want to perform this subtraction as a signed one, you have to convert both operands to signed types, making sure that the target signed type can represent both values.

(Theoretically, you can get away with converting just one operand to signed type, but for that you'd need to choose a type that can represent the entire range of string::size_type.)