cant get the math right

hi all!

i am 21 year old using IDE for the first time so have stumbled upon this:

unsigned int i;
void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
i=4096*(1700-1400)/400;
Serial.println(i);
}

serial monitor says 65496 while calculator says 3072

cheers,
marrc

i=4096UL*(1700UL-1400UL)/400UL

an int overflows at 32767 on atmega boards. your maths requires 1228800 to be held. Is my understanding. you might get away with an UL only after the first number.

marrc:
hi all!

i am 21 year old using IDE for the first time

No, you’ve been using it since April

an int overflows at 32767 on atmega boards.

Only if it is signed

To be fair, the OP used an unsigned int which, whilst wrong, was at least a step in the right direction

is my understanding correct that the compiler will assign the lowest possible memory to each number so it chooses a standard signed int as long as the number is less than 32767 then tries to perform the maths which overflows because a larger number is created. The unsigned int for i is not required since the answer is less than 32767. I haven't checked if you can get away with an UL for just the first number. In my head it is this value that will overflow but I might not understand the process properly.

This has been a useful question because it brings this peculiarity to attention and it is useful to know how maths is done on a uC.

pmagowan:
your maths requires 1228800 to be held. Is my understanding. you might get away with an UL only after the first number.

i am not sure how 1228800 is generated, and in which variable it is held ? why is UL needed in the first place when all the numbers used are way below 32767?
cheers,
Marrc

There are no type specifiers to the right of the assignment operator in the original code, so the arithmetic is done as an int type, however big an int happens to be.

marrc:
i am not sure how 1228800 is generated, and in which variable it is held ? why is UL needed in the first place when all the numbers used are way below 32767?
cheers,
Marrc

Because the products are not way below 32767

I see why you get 65496 (0xFFD8):

  • i=4096*(1700-1400)/400;
  • i=4096*300/400;
  • i=(int)1228800/400; // 1228800 (0x12C000) truncates to -16384 (0xC000) in a signed int
  • i=-16384/400;
  • i=(int)-40.96;
  • i=(unsigned int)-40; // 0xFFD8
  • i= 65496; // 0xFFD8

It's a combination of overflowing the 16-bit integer to get a negative value and printing a negative value as an unsigned value.

marrc:
i am not sure how 1228800 is generated, and in which variable it is held ? why is UL needed in the first place when all the numbers used are way below 32767?
cheers,
Marrc

For an intermediate result: 4096 * 300 = 1228800
However that gets stored in a 16-bit unsigned temporary variable/register, and is therefore truncated to 16 bits.

pcbbc:
For an intermediate result: 4096 * 300 = 1228800
However that gets stored in a 16-bit unsigned temporary variable/register, and is therefore truncated to 16 bits.

"16-bit signed temporary variable/register"
A numeric literal small enough to fit in a signed integer is treated as a signed integer. Since both sides of the '*' are signed integers, the result is truncated to a signed integer.
(A numeric literal outside the range of an 'int' is treated as a 'long'.)

?

i=3072;

JimEli:
?

i=3072;

Yes.

But I think we should take it as read that this is a test program to demonstrate an issue with some other code the OP has where the expression is not constant.

Let us see the path which the MCU follows to split 65496 instead of 3072 for the value of i of the following expression:

unsigned int i = 4096*(1700-1400)/400;

1. As the operands 4096, 1700, 1400, and 400 are not declared, the compiler allocates 16-bit storage locations with type int (by default).

2. Now, we have:
unsigned int i = (int)4096*((int)1700 - (int)1400)/(int)400;
i = (int)4096 * ((int)1700 - (int)1400) / (int)400;
Serial.print(i, DEC) ; //shows: 65496

//==========================================================
3. Given: unsigned int i = 4096*(1700-1400)/400

int temp = 0x1000 * ((0x06A4 - 0x578))/0x0190
==> temp = 0x1000 * 0x012C /0x0190 //0x12C000/0x0190
==> temp = 0xC000/0x0190
==> temp = 0xD8; //the remainder is discarded; how has 0xD8 come? do the signed division

(1) The upper 8-bit of temp variable is 00000000 – hope you are agreed?
(2) If you carefully look how 80x86 performs 16-bit signed division, you will observe that the when the result is fitted in the AL-register, the upper register AH is filled up with the copies of the sign bit (MSB Bit of the result stored in AL) of the 8-bit quotient. The same rule applies here.

(3) Following the above rule, the upper 8-bit of temp variable of Step-3 is filled up with eight 1s which are the copies of the sign bit of 0xD8 (1101 1000).

4. And now, we have:
int temp = 11111111 11011000
==> int temp = 0xFFD8;

So, when you work with register level codes, you first check if AH-register contains all 1s (0xFF) or not and then you interpret the result accordingly. While working with high level codes, we need to be more literate in programming.

5. Finally, the value of temp is placed into i. As a result we get:
Serial.print(i, DEC); //65496;

Why deos print() method show 65496? It is like this because, you declared i as unsigned variable; where all bits carry positive positional values:
==> 0xFFD8
==> 1111 1111 110 1000
==> 1x215 + 1x214 +, … , + 0x20
==> 32768 + 16384 +, …, +0
==> 65496.

Remember that the MCU has no knowledge about the meaning of a number. It is the user who gives meaning to his numerical patter. The MCU only knows how to move data from one place to another and knows to manipulate them using arithmetic and logical operations.

To get 3072, you need to declare your expression as shown in Post#1 (i=4096UL*(1700UL-1400UL)/400UL); where, the operands have been declared as unsigned long. Why does Pot#1 need an “unsigned long (32-bit)” data type instead of “unsigned int (16-bit)” is an issue of another analysis.