whats is OOP up-arrow binary Arduino hints and tips - Signed vs Unsigned numbers - 3
Dual arduinos in control panel hdr

Signed and unsigned numbers


What do signed and unsigned mean? In computer terms they refer to integer values. That is, a value with no fractional part. For example:

  • Integer values: 1, 292374, -17, 0, 25
  • Non-integer values: 1.1, ½ 3.1416

An unsigned integer can represent values 0 and higher.

A signed integer value respresents values negative and positive (and zero).

In C/C++ variales are limited as the magnitude of the numbers that can be represented in variables of a given size. Check out the binary hints and tips for how numbers are represented.

  • unsigned char (8 bits): 0 to 255
  • unsigned short (16 bits): 0 to 65,535
  • unsigned long (32 bits): 0 to 4,294,967,295
What happens if you a variable of type uint8_t (typede unsigned char ubyte8_t is defined somewhere in arduino.h which is sourced into sketch compiles automatically) has a value of 255 and 1 is added to it?

It overflows and wraps around to 0. Internally the cpu will generate a carry status when performing this arithmetic indicating the overflow. If we were programming in assembler (machine language) we would have access to the carry status bit. However in C/C++ (and most other high level languages) we don't have direct access to the cpu status bits, including carry).

If you are writing software to control a rocket and use a 16 bits to represent the rocket's speed, you better be VERY sure the rocket won't ever go faster than 65535 mph because if it goes 1 mph faster, the value will wrap/roll over to 0, probably causing all the navigational computations to go haywire sending the rocket the wrong way. Oops....

Back to signed values. How in tarnation does a computer represent negative numbers?

The answer is something called two's complement arithmetic.

Suppose a 4 bit variable has a value of 1111 and you add 1 to it. It wraps around with a result of 0000.

What happens if you have a value of 0000 and subtract 1 from it? In unsigned arithmetic the answer is 1111. Another type of carry occured - in grade school when the teach subtraction this is called a borrow. And yes, the cpu's internal carry bit would be set by this subtraction.

What if we set (roughly) half of the values that can be represented aside to be negative numbers?

Any signed value where the msb (most significant bit) is '1' is considered NEGATIVE.

    
       binary   decimal
       ------   -------
        0111       7
        0110       6
        0101       5
        0100       4
        0011       3
        0010       2
        0001       1
        0000       0
        1111      -1
        1110      -2
        1101      -3
        1100      -4
        1011      -5
        1010      -6
        1001      -7
        1000      -8
    

A two's complement value of width bits can represent integer values from 2(width-1)-1 to -2(width-1)

  • 8 bits: 127 to -128
  • 16 bits: 32,767 to -32,768
  • 32 bits: 2,147,483,647 to -2,147,483,648
What happens if we have a signed 8 bit variable set to 127 (the max positive value) and we add 1 to it?

With unsigned values wrapping around returns us to 0. With signed values we wrap around to the most negative number. This can result in unexpected behavior. Let's look at an example involving a microsecond timer.

    
       long   timeval;  // signed 32 bit variable
       long   testtime; // signed 32 bit variable
       
       timevalue = micros(); // get the current microsecond count
       
       if (timevalue < testtime) {
         ...
       }
    

This will behave as expected until micros() (which is declared to return an unsigned long value) returns a number larger than 2,147,483,647. Because timevalue number is a signed variable, its value is interpreted as a negative number!

As Robin might have observed to Batman, "Holy intermittent failures, Batman!" Or perhaps not so intermittant, after roughly 2147 seconds of operation (or 36 minutes) things will likely go seriously wrong. So be sure, when testing your programs to run them for more than 36 minutes!

One more thing about micros() and millis() returning unsigned 32 bit values. Remember the code where we're looking to see if the period since a save start time has exceeded a specified duration (look in bcsjTimer.cpp)?

    
       unsigned long starttime;
       unsigned long duration;
       
       if (micros() - starttime > duration) {
         // our time period has elapsed
       }
    

What happens if micros() (or millis()) wraps around after starttime is saved? Don't we get a negative number?

Let's use our trivial 4 bit arithmetic to mmake this easier to understand...

    
       Let micros() return 1 (binary 0001)
       Let starttime be   13 (binary 1101)
                                    ------
       subtracting we get            0100
    

Which is the expected duration of 4 microseconds since starttime. 13, 14, 15, 0, 1

Using unsigned numbers our duration computation works even if micros() wraps around to what wiould seem to be a time previous to starttime!

This works as long as the duration is less than half the resolution of variables! So keep those microsecond time values under 36 minutes and you should be OK!

Would this work if starttime and duration were signed values? Why or why not?

Neat!

To sum up:

  • Choose signed (char, short, int, long) types when you need to represent negative integers.
  • Choose unsigned (uint8_t, uint16_t, uint32_t, unsigned char, unsigned int, unsigned logn) when you need to represent numbers that will ALWAYS be positive.
  • Choose a variable size that will hold all possible values that may be assigned to it. For example: a uint8_t is good for reading the value of an Arduiono pin with digitalRead() but terrible for a time value.