bug? initialization fails using expression

I'm hesitant to properly report this as a bug as it seems so basic I must be misunderstanding something. But...

The following code seems to not initialize the variable "test3" properly:

unsigned long test1 = 300000;
unsigned long test2 = 60000 * 5;
unsigned long test3 = 1000 * 60 * 5; 

void setup() { 

  Serial.begin(9600);
  Serial.print("test1 ");
  Serial.println(test1);
  Serial.print("test2 ");
  Serial.println(test2);
  Serial.print("test3 ");
  Serial.println(test3);

} 

void loop() {
  
}

Results in the output:

test1 300000
test2 300000
test3 4294939616

I'm using version 0022 on a Mac running OS X 10.6.7 and a classic Diecimila

What happens if you use:

unsigned long test3 = (unsigned long) 1000 * (unsigned long) 60 * (unsigned long) 5;

?

Initialization was proper. You just didn't understand what happened:

1000*60=60000 this overflows a signed integer but let's go on: 60000*5=300000=0x493E0 this overflows a signed integer again so the extra digit 4 is lost and only last 4 hex digits 0x93E0 are kept: Then the number 0x93E0, being treated as signed integer, is assigned to text3, extending its sign all the way making 0xFFFF93E0. Now type this number in your calculator and see what you get.

[quote author=Professor Chaos link=topic=64635.msg471394#msg471394 date=1308696427] What happens if you use:

unsigned long test3 = (unsigned long) 1000 * (unsigned long) 60 * (unsigned long) 5;

? [/quote]

1000UL*60*5. There is no need to type case constants, just declaring them to be unsigned long will do.

Math on an expressions in a C statement is done using the "int" type, unless one of the constants is too big to be an int. ( Since all of the constants you used are short enough to be "int"s (16 bits), they are, and the results are truncated as others have described.

To make a constant be a long, even when it is short enough to be an int, suffix it with an "L", or cast to a long:

unsigned long test2 = 60000L * 5;
unsigned long test3 = (long)1000 * 60 * 5;

You only have to hit one of the constants to fix things.

This sort of things is pretty common. I see why so many compilers default to "int" being the same size as "long"

westfw: ... You only have to hit one of the constants to fix things.

Actually that is not always true. Compilers don't necessarily look at all of the constants the entire expression before deciding whether to do int arithmetic or long int arithmetic. The compiler may take it a term at a time and do the promotion when it finds it necessary. So, if doesn't need long arithmetic for the first two terms, it might cheerfully allow int overflow before promoting the partial sum to a long in order to do the next operation.

// Evaluation of an expression is sometimes done a term
// at a time with ints.  In cases like this, the decision
// to promote int results to a long int is apparently
// not made until the compiler sees that one of the
// is a long int.
//
// davekw7x

void setup()
{
    Serial.begin(9600);
    long x = 200L * 201 * 202; // Could use (long)200 with same results 
    long y = 201 * 200L * 202;
    long z = 202 * 201 * 200L;
    delay(1000);
    Serial.print("x = ");Serial.println(x);
    Serial.print("y = ");Serial.println(y);
    Serial.print("z = ");Serial.println(z);
}

void loop(){}

/*
  Output with avr-gcc version 4.3.4 on my
  Linux system:
  
x = 8120400
y = 8120400
z = -4986800

*/

Output:


x = 8120400
y = 8120400
z = -4986800

Bottom line: If you have expressions like these, whose result can be larger than an int, or any of whose intermediate terms can be larger than an int, the safest way to write them is to cast all of the terms to longs, as Professor Chaos showed us in the first response on this thread. Of course, he could have used a UL suffix on the constants instead of type casting to (unsigned long); the results would be the same.

Regards,

Dave

Footnote: For the calculation of z in my example, this compiler apparently first performs a 16-bit multiplication of the first two 16-bit ints. Due to overflow, this gives a value of of -24934. Then, it promotes this to a long int (with the same value) so that it can be multiplied by 200L. The result it obtained is -4986800.

Finally, note that in C and C++ (and therefore in Arduino) there is absolutely no guarantee that expressions are evaluated left-to-right. So please don't be fooled into thinking that it will always work if you simply put the cast on the first term. I mean it "just happens" to work on this example, but it's not guaranteed. Really.

Hey, my solution always works due to the small values of the second and third number ;)

OK, I get it, thanks...I generally don't have type problems with variables and executing code, but haven't been thinking about typing for math done by the compiler itself...I've gotten soft using mostly Matlab anymore...