6 years, 6 months ago.

Possible bug with printf and large unsigned integers

I have noticed that when printing (over the serial port back to a PC) large 32-bit unsigned integers, printf doesn't seem to handle large values above the signed integer maximum correctly. Or am I missing something?

Example code:

#include "mbed.h"
Serial usb(USBTX, USBRX);
int main() {
  unsigned int t = 0x7FFFFFFC;
  while(1) {
    wait(0.5);
    usb.printf("%X %d %f %f\n", t, t, (float)t, (double)t);
    t++;
  }
}

Example code results:

7FFFFFFC 2147483644 2147483648.000000 2147483644.000000

7FFFFFFD 2147483645 2147483648.000000 2147483645.000000

7FFFFFFE 2147483646 2147483648.000000 2147483646.000000

7FFFFFFF 2147483647 2147483648.000000 2147483647.000000

80000000 -2147483648 2147483648.000000 2147483648.000000

80000001 -2147483647 2147483648.000000 2147483649.000000

80000002 -2147483646 2147483648.000000 2147483650.000000

80000003 -2147483645 2147483648.000000 2147483651.000000

NB: The reason the float format value doesn't appear to change is due to it not having sufficient resolution to show all 32 bits of the integer, consequently the value displayed only changes every 256 counts.

Regards, Mike.

1 Answer

6 years, 6 months ago.

Hello Mike,

Rather than using d, try to use u as specifier to print unsigned integers:

usb.printf("%X %u %f %f\n", t, t, (float)t, (double)t);

Accepted Answer

Also in the interests of of code portability if you need 32 bits you should use long unsigned int or uint32_t rather than unsigned int which on some platforms will only be 16 bits. Similarly you should technically also use %lu in the printf (and %lx for hex output and %lf for the double)

posted by Andy A 24 May 2018

Thanks Zoltan, in my ignorance I didn't know about the %u format specifier.

I went on to check printf with unsigned long long (uint64) and discovered the following behaviour... - %X works upto 2^32, anything higher wraps around to zero. - %d seems to output only the value from the top 32 bits of the 64. - %u does the same as %X except with decimal numbers. - (FLOAT)uint64 returns zero. - (DOUBLE)uint64 behaves well (albeit with limited resolution).

Regards, Mike.

posted by Mike Pheysey 24 May 2018

The ARM chips are 32 bits which means that integer operations will generally be 32 bits unless otherwise specified meaning %d will work with up to 32 bits.

However the c standard requires that integers be at least 16 bits with no upper limit. Exact implementation is up to the compiler. Generally int will be implemented as the native size on that processor since that will give the best performance so it could be 16, 32 or 64 bits.

So code that uses an int or int format specifiers in a printf for 32 bit values will work fine on a 32 bit ARM chip but then cause errors if compiled for some other devices. On a 64 bit processor %d would probably work for a 64 bit value.

While you may not have any plans to ever move this particular code to a different processor it's a good habit to always explicitly state the size of an integer when it matters. e.g. if you want a counter that rolls over at 2^16 then use a uint16_t, if you need 32 bits use a uint32_t. If you need a variable to count through the indexes of an array the for speed use int unless it could potentially have more than 16,000 entries.

posted by Andy A 25 May 2018