Cast and overflow doubt

Hi everyone.
I was reading about casting and overflow and I tried the next simple code:

byte number = 10;
byte number2 = 30;
int resultado;

void setup() {
  Serial.begin(9600);
  resultado = number2*number;
  Serial.println(resultado);
}

void loop() {}

As I know, overflow should have occured due to 10*30 overflows data byte (44) as this calculation occurs before the assignment operation. But this didn't happen (It doesn't matter my int result variable).
I got 300 on Serial monitor.

BUT... in this code:

int number = 100;
long result;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  result = number*1000; // 1000 is an int
  Serial.println(result);
}

void loop() {}

Overflow occurs. On serial monitor I got -31072 not 100000. (again, It doesn't matter my long variable)

The same case but different result... why?
Many thanks for your help.

How big is the range of an int ?
That’s an important question, along with whether it’s signed or unsigned.

The compiler has a few tricks up its sleeve.
The Arduino Uno has a 8-bit microcontroller, but the compiler defaults to 16-bit signed integer.
The multiplication byte*byte is done with integers.
I don't even know how to force the compiler to use a byte multiplication. The result of the multiplication can be cast, but the multiplication is still with integers.

There are a few more of these tricks. A value, for example 20000, is default a signed integer. However, a longer value is accepted if that is put into a certain variable.

int i = 20000;     // normal, the 20000 is okay for 16-bit integer
long l = 200000;   // accepted, even if the 200000 is too large for the default integer
long l2 = 200000L; // prefered, the 'L' tells the compiler that it is a long.

The c++ spec states this (so won’t let you do byte based arithmetic operation)

If the operand passed to an arithmetic operator is integral type, then before any other action, the operand undergoes integral promotion

And integral promotion states

Integral promotion

In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable. This conversion always preserves the value.

The following implicit conversions are classified as integral promotions:

  • signed char or signed short can be converted to int;
  • unsigned char or unsigned short can be converted to int if it can hold its entire value range, and unsigned int otherwise;
1 Like

The int is 2 bytes most likely in your experiment, so integral promotion only promotes the result to 2 bytes and you get overflow

Nope. The "assignment" occurs at compile-time. The compiler recognizes your code has no side effects and reduces it to two load constant instructions...

 5aa:	6c e2       	ldi	r22, 0x2C	; 44
 5ac:	71 e0       	ldi	r23, 0x01	; 1

I can't speak to the standard but it is possible to convince avr-gcc to perform byte based arithmetic. The trick is to add lots of typecasts. For this...

  for (number=1; number <= 10; ++number) {
    for (number2=1; number2 <= 30; ++number2) {
      resultado = (byte)((byte)number2 * (byte)number);
      Serial.println(resultado);
    }
  }

...there is a single one-byte multiply...

      resultado = (byte)((byte)number2 * (byte)number);
 5de:	68 9f       	mul	r22, r24
 5e0:	60 2d       	mov	r22, r0
 5e2:	11 24       	eor	r1, r1
 5e4:	70 e0       	ldi	r23, 0x00	; 0

I know that also works for comparisons, addition, and subtraction. However, it is a dangerous path. Miss a single typecast, the value is promoted to signed, and trouble.

1 Like

1++

I dunno. The compiler is allowed to optimize in ways that produce the same results, and it does...

int main() {
  uint8_t a, b, c;

  a = PINB;
   0:	83 b1       	in	r24, 0x03	; 3
  b = PINB;
   2:	93 b1       	in	r25, 0x03	; 3
  c = a + b;    // Addition of two 8bit values, 8bit result
   4:	98 0f       	add	r25, r24
  PORTB = c;
   6:	95 b9       	out	0x05, r25	; 5
  b = PINB;
   8:	93 b1       	in	r25, 0x03	; 3
  c = a - b;      // subtraction of two 8bit values, 8bit result
   a:	28 2f       	mov	r18, r24
   c:	29 1b       	sub	r18, r25
  PORTB = c;
   e:	25 b9       	out	0x05, r18	; 5
  c = a * b;      // multiplication of two 8bit values, 8bit result
  10:	89 9f       	mul	r24, r25
  12:	80 2d       	mov	r24, r0
  14:	11 24       	eor	r1, r1
  PORTB = c;
  16:	85 b9       	out	0x05, r24	; 5

Toss a few ifs with range comparisons in there.

(That's what I remember being the trouble. :grin:)

(But, it's been ... well ... more than a day so the compiler could easily have improved.)

Oh yeah. Probably if you use an expression result instead of assigning it to another byte variable, you defeat the optimization...

if (b > a + 23)
  20:	90 e0       	ldi	r25, 0x00	; 0
  22:	47 96       	adiw	r24, 0x17	; 23
  24:	38 17       	cp	r19, r24
  26:	19 06       	cpc	r1, r25
  28:	01 f0       	breq	.+0      	; 0x2a <main+0x2a>
  2a:	04 f0       	brlt	.+0      	; 0x2c <main+0x2c>
    PORTD = c;
  2c:	2b b9       	out	0x0b, r18	; 11

That was it. If my recollection is correct, with enough typecasts, it's possible to get comparisons to also be byte sized.

@Coding_Badly
Interesting - I had never taken the time to look at the generated assembly code

@westfw
.using ports probably adds some more optimization rules as they are likely declared volatile

I guess the lesson is be careful when you deal with bytes :slight_smile:

1 Like