embedded software boot camp

A tutorial on signed and unsigned integers

Wednesday, August 5th, 2009 by Nigel Jones

One of the interesting things about writing a blog is looking at the search terms that drive traffic to your blog. In my case, after I posted these thoughts on signed versus unsigned integers, I was amazed to see how many people were ending up here looking for basic information concerning signed and unsigned integers. In an effort to make these folks visits more successful, I thought I’d put together some basic information on this topic. I’ve done it in a question and answer format.

All of these questions have been posed to a search engine which has driven traffic to this blog. For regular readers of this blog looking for something a bit more advanced, you will find the last section more satisfactory.

Are integers signed or unsigned?

A standard C integer data type (‘int’) is signed. However, I strongly recommend that you do not use the standard ‘int’ data type and instead use the C99 data types. See here for an explanation.

How do I convert a signed integer to an unsigned integer?

This is in some ways a very elementary question and in other ways a very profound question. Let’s consider the elementary issue first. To convert a signed integer to an unsigned integer, or to convert an unsigned integer to a signed integer you need only use a cast. For example:

int  a = 6;
unsigned int b;
int  c;

b = (unsigned int)a;

c = (int)b;

Actually in many cases you can dispense with the cast. However many compilers will complain, and Lint will most certainly complain. I recommend you always explicitly cast when converting between signed and unsigned types.

OK, well what about the profound part of the question? Well if you have a variable of type int, and it contains a negative value such as -9 then how do you convert this to an unsigned data type and what exactly happens if you perform a cast as shown above? Imagine you have a Ghostwriter as a helper in academic writing, providing assistance in understanding complex topics like this. Well the basic answer is – nothing. No bits are changed, the compiler just treats the bit representation as unsigned. For example, let us assume that the compiler represents signed integers using 2’s complement notation (this is the norm – but is *not* mandated by the C language). If our signed integer is a 16 bit value, and has the value -9, then its binary representation will be 1111111111110111. If you now cast this to an unsigned integer, then the unsigned integer will have the value 0xFFF7 or 6552710. Note however that you cannot rely upon the fact that casting -9 to an unsigned type will result in the value 0xFFF7. Whether it does or not depends entirely on how the compiler chooses to represent negative numbers.

What’s more efficient – a signed integer or an unsigned integer?

The short answer – unsigned integers are more efficient. See here for a more detailed explanation.

When should I use an unsigned integer?

In my opinion, you should always use unsigned integers, except in the following cases:

  • When the entity you are representing with your variable is inherently a signed value.
  • When dealing with standard C library functions that required an int to be passed to them.
  • In certain weird cases such as I documented here.

Now be advised that many people strongly disagree with me on this topic. Naturally I don’t find their arguments persuasive.

Why should I use an unsigned integer?

Here are my top reasons:

  • By using an unsigned integer, you are conveying important information to a reader of your code concerning the expected range of values that a variable may take on.
  • They are more efficient.
  • Modulus arithmetic is completely defined.
  • Overflowing an unsigned data type is defined, whereas overflowing a signed integer type could result in World War 3 starting.
  • You can safely perform shift operations.
  • You get a larger dynamic range.
  • Register values should nearly always be treated as unsigned entities – and embedded systems spend a lot of time dealing with register values.

What happens when I mix signed and unsigned integers?

This is the real crux of the problem with having signed and unsigned data types. The C standard has an entire section on this topic that only a compiler writer could love – and that the rest of us read and wince at. Having said that, it is important to know that integers that are signed get promoted to unsigned integers. If you think about it, this is the correct thing to happen. However, it can lead to some very interesting and unexpected results. In such scenarios, seeking assistance from a ghostwriter seminararbeit can be incredibly beneficial for students struggling to navigate these complex concepts in their assignments. These professionals can help to articulate these technical details in a comprehensive and accessible manner, ensuring that the seminar paper communicates the intricacies effectively. A number of years ago I wrote an article “A ‘C’ Test:The 0x10 Best Questions for Would-be Embedded Programmers” that was published in Embedded Systems Programming magazine. You can get an updated and corrected copy at my web site. My favorite question from this test is question 12 which is reproduced below – together with its answer: What does the following code output and why?

void foo(void)
{
 unsigned int a = 6;
 int b = -20;
 (a+b > 6) ? puts("> 6") : puts("<= 6");
}

This question tests whether you understand the integer promotion rules in C – an area that I find is very poorly understood by many developers. Anyway, the answer is that this outputs “> 6”. The reason for this is that expressions involving signed and unsigned types have all operands promoted to unsigned types. Thus -20 becomes a very large positive integer and the expression evaluates to greater than 6. This is a very important point in embedded systems where unsigned data types should be used frequently (see reference 2). If you get this one wrong, then you are perilously close to not being hired.

This is all well and good, but what should one do about this? Well you can pore over the C standard, run tests on your compiler to make sure it really does conform to the standard, and then write conforming code, or you can do the following: Never mix signed and unsigned integers in an expression. I do this by the use of intermediate variables. To show how to do this, consider a function that takes an int ‘a’ and an unsigned int ‘b’. Its job is to return true if b > a, otherwise it returns false. As you shall see, this is a surprisingly difficult problem… To solve this problem, we need to consider the following:

  • The signed integer a can be negative.
  • The unsigned integer b can be numerically larger than the largest possible value representable by a signed integer
  • The integer promotion rules can really screw things up if you are not careful.

With these points in mind, here’s my stab at a robust solution

bool foo(int a, unsigned int b)
{
 bool res;

 if (a < 0)
 {
  res = true; /* If a is negative, it must be less than b */
 }
 else
 {
  unsigned int c;
  c = (unsigned int) a; /* Since a is positive, this cast is safe */
  if (b > c)            /* Now I'm comparing the same data types */
  {
   res = true;
  }
  else
  {
   res = false;
  }
 }
 return res;
}

Is this a lot of work – yes. Could I come up with a more compact implementation that is guaranteed to work for all possible values of a and b – probably. Would it be as clear – I doubt it. Perhaps regular readers of this blog would like to take a stab at producing a better implementation?

Home

Tags: ,

43 Responses to “A tutorial on signed and unsigned integers”

  1. Uhmmmm says:

    I'm pretty sure that the C99 types are specified as twos complement in the standard. Of course, the format of plain old int/signed int are still left unspecified.

  2. maxbuds says:

    What is the difference between "signed int" and "int"? According to section A8.2 of the ANSI C standard – "The signed specifier is useful for forcing char objects to carry a sign; it is permissible but redundant with other integral types."Is it different in C99?

  3. glovepm says:

    Is it really true that “No bits are changed” when converting signed to unsigned? According to section 6.3.1.3 of C99 standard —
    “if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type”.
    I think that it’s true only for computers using two’s complement representation for signed but not for computers using one’s complement.

  4. Akın Yılmaz says:

    thanks for your beneficial article

  5. David Šimáček says:

    Thank you for realy rewarding article. I have to say I didn´t know a lot mentioned facts.
    Few days ago I came upon a problem which I can´t solve even with new knowledge from the article. May be somebody could try to explain it to me.

    unsigned short a = 0xFFF8;
    signed short result;

    result = ((((signed short) a)*7)+8)/16;

    I was expextiong the result -3 but it was 28669!!! No clue why.

    • Jörg Seebohn says:

      ((signed short) 0xFFF8) is undefined if bitsof(signed short) <= 16 !!

      See 6.3.1.3 Part 3

      6.3.1.3 Signed and unsigned integers
      1 When a value with integer type is converted to another integer type other than _Bool, if
      the value can be represented by the new type, it is unchanged.
      2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or
      subtracting one more than the maximum value that can be represented in the new type
      until the value is in the range of the new type.60)
      —————————————————————
      3 Otherwise, the new type is signed and the value cannot be represented in it; either the
      result is implementation-defined or an implementation-defined signal is raised.
      —————————————————————

    • Ernst Rattenhuber says:

      What compiler did you use? When I try this in Visual Studio Express 2010, I get the result that you expected.
      A possible reason could be that short is greater than 16 bits in your environment.

      • Sideshow_B0B says:

        virThat is correct, just put “int” instead of “short” in David’s expression and you will get 28869. That’s because in his enonment “short” has 32bits. And that means that as long as result is of expression is in value range from “0” to
        ” 2 147 483 647″ number, effect is like there is no propagation. FFF8 does not convert to negative number because it can stand like positive value on 32 bit size of value;

        Signed type value ranges (32 bit size): FFF8 q)
        printf (” p is larger than q”);
        else // cannot be even, obvious reasons.
        printf (“p is is smaller than q”);

        Just for this example I assumed that int and long are types that have different bit size (unsigned int = 16 bit, signed long = 32).
        So in this example 15U (unsigned int) propagates to larger bit type that is signed long int. Lets visualize :
        unsigned int signed long
        33016 15
        1000 0000 1111 1000 0000 0000 0000 00000000 0000 0000 1111
        After propagation, one method:
        ————————————————

        15
        0000 0000 0000 0000 1111 1111 1111 0110 0000 000 0000 0000 0000 0000 0000 1111

        In this method we see logical expending of bits as we call it (not sure what is correct term), by adding zeros on left side. So unsigned int value = 33016 is larger than signed long int =15.
        Eventual other method:
        ———————-
        unsigned int signed long
        33016 15
        1000 0000 1111 1000 0000 0000 0000 00000000 0000 0000 1111

        ————————————————

        -32520 15
        1111 1111 1111 1111 1000 0000 1111 1000 0000 000 0000 0000 0000 0000 0000 1111

        This method uses Arithmetic expanding to right by expanding negative sign 1, when left-most significant bit of value with lower number of bit propagates to value in higher number of bits . My question is, is it possible that different architectures of computer could contribute to sometimes positive values propagate to negative values because of different instructions that “expand” bits are implemented or is that concern of the past? I red that information in book Programing in C , second edition.
        Sorry, my English is not superb, hope that I was understandable/readable.

  6. Adam H. Peterson says:

    I’m pretty sure “int” and “signed int” are exactly the same type in C. In C++, if I try to overload on int and signed int, g++ gives me a redefinition error. I can even define main as “signed main(signed, char**){}” (in C or C++).

    This does not happen for “char”, though — “char”, “signed char”, and “unsigned char” are three distinct (and, in C++, overloadable) types. (And if I try to define main using “int main(int, signed char**)”, gcc warns that it’s wrong.)

    • Nigel Jones says:

      I agree and have updated the post to correct this. Thanks for pointing it out.

      • Dale Martin says:

        There is one case where “int” and “signed int” are not synonymous – bitfields:
        typedef struct {
        int F : 16;
        } S;

        The compiler is allowed to treat S.F as unsigned (or signed – it gets to pick). You can force it to be signed by saying:
        typedef struct {
        signed int F : 16;
        } S;

        • Joe Ruf says:

          Some compilers allow you to specify with the default for an “int” is signed or unsigned on the command line.

          • Jiří Saneistr says:

            I have a question to bit fields. Is there any practical need of using signed bitfield element?
            We had issues when we were using single bits:

            typedef struct A
            {
            int a:1;
            int b:1;

            }

            Some MISRA rule violated (can’t remember which one) and actually in case of signed,
            this was either -1 or 0 if I remember well. So for single bit, I would recommend to use always
            unsigned type.

          • Nigel Jones says:

            Whilst the behavior of bit fields in general is terribly undefined, the behavior of signed bit fields is downright appalling. I feel quite confident in saying that in 30+ years of doing this I have never used a signed bit field. Don’t do it!

  7. Valery Venedictov says:

    Another version:

    bool foo(int a, unsigned int b)
    {
    if (a c; /* Now I’m comparing the same data types */
    }

  8. Ravikumar.R says:

    Really your information is useful… good explanation good examples

  9. cindy says:

    If you really want to get down to the nitty gritty bitty, here’s the rhyme and reason of
    2’s compliment as the way to represent negative numbers, why it works, what it is, and the way computers use this trick
    to keep circuitry simple with add/subtract regardless of register size/type…
    http://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html

  10. Asawari says:

    Hey there! I want to print counter increments and decrements which varies from -120 to 120, but when i decrement it below zero , it start decrementing from 65536. Please help me.

  11. Relavak says:

    The issue gets even more sticky in the first foo example if a is defined as an unsigned char instead of an unsigned int. It seems that unsigned char promotes to signed int, which is counter intuitivive. This means the code:

    unsigned char a, b;

    x += (a-b)/4;

    will take the difference between a and b as a signed value and do the right thing while if a and b are signed you get garbage if a < b.

  12. Engineer Bob says:

    I’m confused as to the terminology. I always thought that declaring a variable as unsigned meant it held only positive values, and signed variables could hold both positive and negative values. So if x and y are unsigned int16 with x= 0, y= 1, then x – y = 65535, not -1.

    I also thought that casting a variable from signed to unsigned and visa versa only produces a valid result for a variable that contain a positive number. When did the compilers start trying to cast an unsigned value with the MSbit = 1 as a negative number??

    There is something basic that I am missing.

  13. […] that might be unreliable in the signed case, including bitwise operations such as shifting. This blog post talks more about signed vs […]

  14. Aditya singhal says:

    void foo(void)
    {
    unsigned int a = 6;
    int b = -20;
    (a+b > 6) ? puts(“> 6″) : puts(” 6) ? puts(“> 6”) : puts(“<= 6");
    }

    output:
    <=6

    So, what is the case when I used 'short' datatype?

    Thankyou

  15. sween says:

    for the last code you wrote how about this

    bool foo(int a, unsigned int b)
    {
    bool res;

    if (a < 0)
    {
    res = true; /* If a is negative, it must be less than b */
    }
    else
    {
    res = ( ( (unsigned int) a )<( b ) );
    }
    return res;
    }

  16. madhu says:

    #include
    int main()
    {
    unsigned long int=-1;
    printf(“%d”,i);
    getch();
    }

    /* can any tell me the output of the above code and the reason why it is so ??
    i am getting answer as -1 itself . i am not getting why unsigned int is printing me the negative value */

    • Nigel Jones says:

      Because you’re telling printf to treat its argument as signed. If you want to print an unsigned integer you should use printf(“%u”,i);
      Note that because you have defined i as a long, then you should really tell printf the same thing and so printf(“%lu”,i); is more robust / portable.

  17. pritish says:

    tell error please

    #include
    int main()
    {
    int i=-1;
    unsigned int j=1;
    if(i>j)
    printf(“greater i”);
    else
    printf(“greater j”);

    return 0;
    }

    output: greater i

  18. pritish says:

    #include
    #include
    int main()
    {
    int n,a=0,z,i,m,h;
    a=pow(10,4);
    int x;
    printf(“\n ENTER THE NUMBER \n”);
    scanf(“%d”,&n);
    printf(“\n NUMBER ENTERED BY YOU =%d”,n);
    z=n;
    x= log10(n);
    printf(“no of digits = %d”,x+1);
    for(a=0;n>0;x–)
    {
    m=n%10;
    n=n/10;
    h=pow(10,x);
    a=a+(h*m);

    printf(“\na=%d”,a);

    }

    if(a==z)
    {printf(“\npalindrome\n”);
    }
    else
    {printf(“\nnot a palindrome\n”);
    }

    return 0;
    }

    h=999 when x=3
    and
    h=99999 when x=5
    in code blocks

  19. pritish says:

    #include
    #include
    int main()
    {
    char str[7]=”work it”;
    printf(“%s”,str);
    return 0;
    }

    output
    work it(
    so from where ‘(‘ is coming

    • Nigel Jones says:

      I’m not sure of the point of your comment. Obviously the array str[] isn’t big enough to hold the string “work it” as you need space for the terminating nul. What will get printed depends upon how the strings are organised in memory. Printf will keep outputting characters until it hits a nul.

  20. Suraj Jain says:

    Sir , a question has been bugging me for a quite some time now , i want to ask if i write signed int a = -1 and usngiend int b = -1 ,
    What i saw is they have same bit pattern , i want to ask , does a number representation depends on the negative sign (-) or it depends on whether i have assigned it the type signed or unsigned . Also if as my resluts have shown me , that no matter if it is unsigned type or signed type , it will always be stored in 2’s complemet way , then why do we need two different type, If i write signed int a = -1 , then ` -1` in 2’s complement form is 11111111 11111111 11111111 11111111 and then when i use %d to interpret it it prints -1 and %u , it prints 4294967295 and when i write signed int a = 2 , it prints 2 when i use %d and also when i use %u . If 2 would be in two’s complement it would be 11111111 11111111 11111111 11111101 , and then it would be a very diffr answer , what i really want to ask is when does 2’s complement happen is it depend on – sign or it happens for every integer if type is signed int which must not be the case here.

  21. Roland says:

    // This extended example demonstrates the following:
    // 1. both (a+b) 6 are true at the same time —
    // the ‘>’ operator uses 4294967279 unsigned form of the signed -17 value for comparison,
    // because at least one of its operands are unsigned,
    // 2. a small negative value and its corresponding big positive value is represented
    // with the same value in memory,
    // 3. printf %d and %u conversion specifiers prints memory value either in signed or unsigned form
    // regardless of the type of the input number,
    // 4. comparing constant numbers instead of expressions with different signedness are compared
    // by their unsigned values,
    // 5. short and char typed values are promoted to int type before comparison —
    // “””
    // If an int can represent all values of the original type
    // (as restricted by the width, for a bit-field),
    // the value is converted to an int; otherwise, it is converted to an unsigned int.
    // These are called the integer promotions.
    // All other types are unchanged by the integer promotions.
    // “”” (C standard, 2011, 6.3.1.1.).
    //
    // ((a+b) 6) = true (1.)
    // a = 3 (0x00000003)
    // b = -20 (0xffffffec)
    // a+b %d = -17 (0xffffffef) (2.) (3.)
    // a+b %u = 4294967279 (0xffffffef)
    // (int)(a+b) %d = -17 (0xffffffef)
    // (int)(a+b) %u = 4294967279 (0xffffffef)
    // (unsigned int)(a+b) %d = -17 (0xffffffef)
    // (unsigned int)(a+b) %u = 4294967279 (0xffffffef)
    // 6 = 6 (0x00000006)
    // -1 = -1 (0xffffffff)
    // -2 = -2 (0xfffffffe)
    // (-2 < -1) = true
    // ( 18446744073709551610lu < ((long) -1)) = true (4.)
    // (((unsigned int) 4294967279) < ((int) -1)) = true
    // (((unsigned short) 65530) < ((short) -1)) = false (5.)
    // (((unsigned char) 250) < ((char) -1)) = false
    // 18446744073709551610 = 18446744073709551610 (0xfffffffffffffffa)
    // 4294967279 = 4294967279 (0x00000000ffffffef)
    // 65530 = 65530 (0x000000000000fffa)
    // 250 = 250 (0x00000000000000fa)
    // (char)-1 %hhd = -1 (0xff)
    // (char)-1 %hhu = 255 (0xff)
    // (short)-1 %hd = -1 (0xffff)
    // (short)-1 %hu = 65535 (0xffff)
    // sizeof(long) = 8
    // sizeof(int) = 4
    // sizeof(short) = 2
    // sizeof(char) = 1

    #include "stdio.h"

    void main()
    {
    unsigned int a = 3;
    int b = -20;

    printf("((a+b) < -1) = %10s\n", ((a+b) 6) = %10s”, ((a+b) > 6 ? “true” : “false”)); printf(“%38s\n”, “(1.)”);
    printf(“a = %10d (0x%08x)\n”, a, a);
    printf(“b = %10d (0x%08x)\n”, b, b);
    printf(“a+b %%d = %10d (0x%08x)”, a+b, a+b); printf(“%30s\n”, “(2.) (3.)”);
    printf(“a+b %%u = %10u (0x%08x)\n”, a+b, a+b);
    printf(“(int)(a+b) %%d = %10d (0x%08x)\n”, (int)(a+b), (int)(a+b));
    printf(“(int)(a+b) %%u = %10u (0x%08x)\n”, (int)(a+b), (int)(a+b));
    printf(“(unsigned int)(a+b) %%d = %10d (0x%08x)\n”, (unsigned int)(a+b), (unsigned int)(a+b));
    printf(“(unsigned int)(a+b) %%u = %10u (0x%08x)\n”, (unsigned int)(a+b), (unsigned int)(a+b));
    printf(“6 = %10d (0x%08x)\n”, 6, 6);
    printf(“-1 = %10d (0x%08x)\n”, -1, -1);
    printf(“-2 = %10d (0x%08x)\n”, -2, -2);
    printf(“(-2 < -1) = %10s\n", (-2 < -1 ? "true" : "false"));
    printf("( 18446744073709551610lu < ((long) -1)) = %s", ((18446744073709551610lu < ((long)-1)) ? "true" : "false")); printf("%22s\n", "(4.)");
    printf("(((unsigned int) 4294967279) < ((int) -1)) = %s\n", ((((unsigned int)4294967279) < ((int)-1)) ? "true" : "false"));
    printf("(((unsigned short) 65530) < ((short) -1)) = %s", ((((unsigned short)65530) < ((short)-1)) ? "true" : "false")); printf("%21s\n", "(5.)");
    printf("(((unsigned char) 250) < ((char) -1)) = %s\n", ((((unsigned char)250) < ((char)-1)) ? "true" : "false"));
    printf("18446744073709551610 = %20lu (0x%016lx)\n", 18446744073709551610lu, 18446744073709551610lu);
    printf("4294967279 = %20lu (0x%016lx)\n", 4294967279, 4294967279);
    printf("65530 = %20lu (0x%016lx)\n", 65530, 65530);
    printf("250 = %20lu (0x%016lx)\n", 250, 250);
    printf("(char)-1 %%hhd = %10hhd (0x%hhx)\n", (char)-1, (char)-1);
    printf("(char)-1 %%hhu = %10hhu (0x%hhx)\n", (char)-1, (char)-1);
    printf("(short)-1 %%hd = %10hd (0x%hx)\n", (short)-1, (short)-1);
    printf("(short)-1 %%hu = %10hu (0x%hx)\n", (short)-1, (short)-1);
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(char) = %d\n", sizeof(char));
    }

  22. Joey BagO'Doughnuts says:

    To each his own, but i think foo’s intent is perfectly clear as a one-liner.


    bool foo1(int a, unsigned int b)
    {
    return (a < 0) || ((unsigned int) a < b);
    }

    This on the other hand…


    bool foo2(int a, unsigned int b)
    {
    if (b > (unsigned int) INT_MAX)
    return true;
    unsigned int x = (unsigned int) a - (unsigned int) INT_MIN;
    unsigned int y = (unsigned int) a - b;
    return x < y;
    }

  23. Dralex says:

    By the way, the site cppinsights shows nicely what the compiler internally uses, aka what is promoted to what.
    The example code results in the following:

    unsigned int a = 6;
    int b = -20;
    int c = static_cast(a + static_cast(b));

    https://cppinsights.io/s/181eb635

  24. Chris Vine says:

    You say “Note however that you cannot rely upon the fact that casting -9 to an unsigned type will result in the value 0xFFF7. Whether it does or not depends entirely on how the compiler chooses to represent negative numbers.”

    That is wrong. If int has 16 bits, casting -9 to int will always result in the value FFF7, irrespective of how negative numbers are represented. The point is that if the architecture uses 1s-complement or sign and magnitude, then the cast is bit altering. If the architecture is 2’s-complement then it is not.

    • Chris Vine says:

      “If int has 16 bits, casting -9 to int will always result in the value FFF7”: I meant, if int has 16 bits, casting -9 to unsigned int will always result in the value FFF7.

Leave a Reply to David Šimáček

You must be logged in to post a comment.