3

I'm new to C++ and is trying to learn the concept of array. I saw this code snippet online. For the sample code below, does it make any difference to declare:

unsigned scores[11] = {};
unsigned grade; 

as:

int scores[11] = {};
int grade; 

I guess there must be a reason why score[11] = {}; and grade is declared as unsigned, but what is the reason behind it?

int main() {
    unsigned scores[11] = {};
    unsigned grade;
    while (cin >> grade) {
        if (0 <= grade <= 100) {
            ++scores[grade / 10];
        }
    }
    for (int i = 0; i < 11; i++) {
        cout << scores[i] << endl;
    }
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Thor
  • 9,638
  • 15
  • 62
  • 137
  • 5
    Ask the original author of the code. In my opinion, there is no reason to use `unsigned` here. – MikeMB Feb 24 '16 at 07:06
  • 2
    @MikeMB Can the score and the grade be negative ? If they can't then they should be unsigned. In particular, it seems grade is used as an array index, so it probably must be positive. My advise would be to put unsigned if you now that a negative value doesn't make sense – Colin Pitrat Feb 24 '16 at 07:12
  • 1
    Off topic: `cin >> grade` `cin` is too stupid to do this correctly. Enter -1 and watch what `grade` results. – user4581301 Feb 24 '16 at 07:13
  • 3
    @ColinPitrat MikeMB is correct here. Going `unsigned` actually increases the required complexity of the code. To make this work, `cin >> astring` and then parse the string into an unsigned datatype with `strtoul` or `std::stoul`. `strtoul` would probably be preferable here because it can handle the exit condition without throwing an exception. – user4581301 Feb 24 '16 at 07:24
  • 2
    @user4581301: It's true for this very small example but this is not a good real life example. In real life, you don't want to just ignore when a user enter -1 or when you have invalid input. In real life, you will have a lot of code handling grades and you don't want to check for > 0 in all functions. So I'd prefer to stick with the semantic of the variable and use unsigned because it's future proof. Anyway, the check for <= 100 would catch the -1 (but maybe not some big well chosen negative value) – Colin Pitrat Feb 24 '16 at 07:40
  • 3
    `0 <= grade <= 100` probably doesn't do what you expect. IIRC some compilers also warn about that kind of mistake, but I might be wrong here... – anderas Feb 24 '16 at 07:49
  • `0 <= grade` would not be required for `unsigned`. – Jarod42 Feb 24 '16 at 09:33
  • http://stackoverflow.com/q/3729465/1639256 is not an exact duplicate, but has much better answers. – Oktalist Feb 24 '16 at 14:17

5 Answers5

6

unsigned means that the variable will not hold a negative values (or even more accurate - It will not care about the sign-). It seems obvious that scores and grades are signless values (no one scores -25). So, it is natural to use unsigned.

But note that: if (0 <= grade <= 100) is redundant. if (grade <= 100) is enough since no negative values are allowed.

As Blastfurnace commented, if (0 <= grade <= 100) is not right even. if you want it like this you should write it as:

if (0 <= grade && grade <= 100)

Humam Helfawi
  • 19,566
  • 15
  • 85
  • 160
4

Unsigned variables

Declaring a variable as unsigned int instead of int has 2 consequences:

  • It can't be negative. It provides you a guarantee that it never will be and therefore you don't need to check for it and handle special cases when writing code that only works with positive integers
  • As you have a limited size, it allows you to represent bigger numbers. On 32 bits, the biggest unsigned int is 4294967295 (2^32-1) whereas the biggest int is 2147483647 (2^31-1)

One consequence of using unsigned int is that arithmetic will be done in the set of unsigned int. So 9 - 10 = 4294967295 instead of -1 as no negative number can be encoded on unsigned int type. You will also have issues if you compare them to negative int.

More info on how negative integer are encoded.

Array initialization

For the array definition, if you just write:

unsigned int scores[11];

Then you have 11 uninitialized unsigned int that have potentially values different than 0.

If you write:

unsigned int scores[11] = {};

Then all int are initialized with their default value that is 0.

Note that if you write:

unsigned int scores[11] = { 1, 2 };

You will have the first int intialized to 1, the second to 2 and all the others to 0.

You can easily play a little bit with all these syntax to gain a better understanding of it.

Comparison

About the code:

if(0 <= grade <= 100)

as stated in the comments, this does not do what you expect. In fact, this will always evaluate to true and therefore execute the code in the if. Which means if you enter a grade of, say, 20000, you should have a core dump. The reason is that this:

0 <= grade <= 100

is equivalent to:

(0 <= grade) <= 100

And the first part is either true (implicitly converted to 1) or false (implicitly converted to 0). As both values are lower than 100, the second comparison is always true.

Colin Pitrat
  • 1,992
  • 1
  • 16
  • 28
  • 1
    What if you want to calculate the difference between `scores[0]` and `scores[1]`? `scores[0] - scores[1]` should be `-1`, not `4294967295`. – Oktalist Feb 24 '16 at 14:02
3

unsigned integers have some strange properties and you should avoid them unless you have a good reason. Gaining 1 extra bit of positive size, or expressing a constraint that a value may not be negative, are not good reasons.

unsigned integers implement arithmetic modulo UINT_MAX+1. By contrast, operations on signed integers represent the natural arithmetic that we are familiar with from school.

Overflow semantics

unsigned has well defined overflow; signed does not:

unsigned u = UINT_MAX;
u++; // u becomes 0
int i = INT_MAX;
i++; // undefined behaviour

This has the consequence that signed integer overflow can be caught during testing, while an unsigned overflow may silently do the wrong thing. So use unsigned only if you are sure you want to legalize overflow.

If you have a constraint that a value may not be negative, then you need a way to detect and reject negative values; int is perfect for this. An unsigned will accept a negative value and silently overflow it into a positive value.

Bit shift semantics

Bit shift of unsigned by an amount not greater than the number of bits in the data type is always well defined. Until C++20, bit shift of signed was undefined if it would cause a 1 in the sign bit to be shifted left, or implementation-defined if it would cause a 1 in the sign bit to be shifted right. Since C++20, signed right shift always preserves the sign, but signed left shift does not. So use unsigned for some kinds of bit twiddling operations.

Mixed sign operations

The built-in arithmetic operations always operate on operands of the same type. If they are supplied operands of different types, the "usual arithmetic conversions" coerce them into the same type, sometimes with surprising results:

unsigned u = 42;
std::cout << (u * -1); // 4294967254
std::cout << std::boolalpha << (u >= -1); // false

What's the difference?

Subtracting an unsigned from another unsigned yields an unsigned result, which means that the difference between 2 and 1 is 4294967295.

Double the max value

int uses one bit to represent the sign of the value. unsigned uses this bit as just another numerical bit. So typically, int has 31 numerical bits and unsigned has 32. This extra bit is often cited as a reason to use unsigned. But if 31 bits are insufficient for a particular purpose, then most likely 32 bits will also be insufficient, and you should be considering 64 bits or more.

Function overloading

The implicit conversion from int to unsigned has the same rank as the conversion from int to double, so the following example is ill formed:

void f(unsigned);
void f(double);
f(42); // error: ambiguous call to overloaded function

Interoperability

Many APIs (including the standard library) use unsigned types, often for misguided reasons. It is sensible to use unsigned to avoid mixed-sign operations when interacting with these APIs.

Appendix

The quoted snippet includes the expression 0 <= grade <= 100. This will first evaluate 0 <= grade, which is always true, because grade can't be negative. Then it will evaluate true <= 100, which is always true, because true is converted to the integer 1, and 1 <= 100 is true.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
2

Yes it does make a difference. In the first case you declare an array of 11 elements a variable of type "unsigned int". In the second case you declare them as ints.

When the int is on 32 bits you can have values from the following ranges

–2,147,483,648 to 2,147,483,647 for plain int

0 to 4,294,967,295 for unsigned int

You normally declare something unsigned when you don't need negative numbers and you need that extra range given by unsigned. In your case I assume that that by declaring the variables unsigned, the developer doesn't accept negative scores and grades. You basically do a statistic of how many grades between 0 and 10 were introduced at the command line. So it looks like something to simulate a school grading system, therefore you don't have negative grades. But this is my opinion after reading the code.

Take a look at this post which explains what unsigned is:

what is the unsigned datatype?

Community
  • 1
  • 1
asalic
  • 664
  • 3
  • 6
2

As the name suggests, signed integers can be negative and unsigned cannot be. If we represent an integer with N bits then for unsigned the minimum value is 0 and the maximum value is 2^(N-1). If it is a signed integer of N bits then it can take the values from -2^(N-2) to 2^(N-2)-1. This is because we need 1-bit to represent the sign +/-

Ex: signed 3-bit integer (yes there are such things)

000 = 0
001 = 1
010 = 2
011 = 3
100 = -4
101 = -3
110 = -2
111 = -1

But, for unsigned it just represents the values [0,7]. The most significant bit (MSB) in the example signifies a negative value. That is, all values where the MSB is set are negative. Hence the apparent loss of a bit in its absolute values.

It also behaves as one might expect. If you increment -1 (111) we get (1 000) but since we don't have a fourth bit it simply "falls off the end" and we are left with 000.

The same applies to subtracting 1 from 0. First take the two's complement

 111 = twos_complement(001) 

and add it to 000 which yields 111 = -1 (from the table) which is what one might expect. What happens when you increment 011(=3) yielding 100(=-4) is perhaps not what one might expect and is at odds with our normal expectations. These overflows are troublesome with fixed point arithmetic and have to be dealt with.

One other thing worth pointing out is the a signed integer can take one negative value more than it can positive which has a consequence for rounding (when using integer to represent fixed point numbers for example) but am sure that's better covered in the DSP or signal processing forums.

systemcpro
  • 856
  • 1
  • 7
  • 15