Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Integer arithmetic produces a strange result (rounding after division?)

Tags:

c

Using gcc version 4.8.4 on Linux, short is 16 bit and int is 32 bit.

#include "stdio.h"
int main( void ){
  unsigned short u = 0xaabb;
  unsigned int   v = 0xaabb;
  printf ("%08x %08x\n", u, (unsigned short)((u*0x10001)/0x100));
  printf ("%08x %08x\n", v, (unsigned short)((v*0x10001)/0x100));
  return 0;
}

Result:

0000aabb 0000bbab
0000aabb 0000bbaa

This can be varied, e.g., by dividing with 0x10, which produces a similar result (+1) for the first case. The effect does not occur if the byte truncated by /0x100 is less than 0x80. Machine code for the first case (short u) looks as if some rounding (addition of 0xFF) is intended.

  1. What is the reason for the result or is it a bug?
  2. What is the result for other compilers?
like image 504
laune Avatar asked Feb 23 '16 13:02

laune


2 Answers

A literal like 0x10001 will be of type int (if it can fit inside an int, which is true in this case). int is a signed type.

Since the variable u is a small integer type, it gets integer promoted to int whenever used in an expression.

0xaabb * 0x10001 would supposedly give the result 0xAABBAABB. However, that result is too large to fit inside an int on a 32 bit two's complement system, where the largest number for an int is 0x7FFFFFFF. So you get an overflow on a signed integer and therefore invoke undefined behavior - anything can happen.

Never use signed integers when doing any form of binary arithmetic!

Furthermore, the final cast to (unsigned short) is futile, because printf argument promotes the passed value to int anyhow. Which is strictly speaking incorrect too, because %x means that printf expects an unsigned int.

To avoid all trouble with the unpredictable and limited default integer types in C, use stdint.h instead. Also, using unsigned int literals solves a lot of implicit type promotion bugs.

Example:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main( void ){
  uint16_t u = 0xaabb;
  uint16_t v = 0xaabb;
  printf ("%08" PRIx16 " %08" PRIx16 "\n", u, (uint16_t)(u*0x10001u/0x100u));
  printf ("%08" PRIx16 " %08" PRIx16 "\n", v, (uint16_t)(v*0x10001u/0x100u));
  return 0;
}

(This code will have argument promotion too, but by using the PRIx16 format specifier, you tell printf that it is now the compiler's business to make the code work, regardless of what type promotions that might be present in the function call.)

like image 118
Lundin Avatar answered Sep 25 '22 21:09

Lundin


Usual arithmetic conversions at play.

u is converted to int before multiplication. Since int is signed it behaves differently on division.

printf("%08x\n", (u*0x10001)/0x100);
printf("%08x\n", (v*0x10001)/0x100);

Returns

ffaabbab
00aabbaa

Strictly speaking multiplication overflow on signed integer is already undefined behaviour, so result is invalid even before the division.

like image 23
user694733 Avatar answered Sep 24 '22 21:09

user694733