equation data type

Hi Guys

is this a bug or am i missing something

unsigned long number;

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
number = 1.0 / 20 * 16000000; //<<<<<<<<<<<<<< 1.0
}

void loop() {
// put your main code here, to run repeatedly:
Serial.println(number);
delay(500);
}

this code works and puts out 80000
but if the 1.0 gets changed to 1 then i get a reading of 0

is this because 1/20 is a floating point number? and if so why is an unsigned long accepting a floating point number.

i cant get my head around the process the controller goes through when calculating

Arduino compiler defaults to integer math unless told otherwise.

Therefore 1/20*16000000 is 0 if you use integer math.

The default data type for constant expressions is int. The int result of int 1 divided by int 20 is zero. You changed the default to float when you used 1.0.

But a far better way to do this is:

number = 16000000UL * 1/20;

this is just for clarity, as the compiler will optimize it to:

number = 16000000UL/20;

but the style allows clear use of other fractions like

number = 16000000UL * 7/42;

This will eliminate the risk of rounding errors in floating point calculations. The rule in integer fraction calculations is multiply first, divide second.

1.0 is a float, so the calculation will be done using floats, then the final result will be converted to an unsigned long to be stored into number. By default, the calculations will be performed left-to-right. So...

As originally written:

number = 1.0 / 20 * 16000000;

1.0 is divided by 20, giving a result of 0.05. 0.05 is then multiplied by 16,000,000.0, giving 80,000.0. Once converted to unsigned long, this is still 80,000.

However, written as:

number = 1 / 20 * 16000000;

The integer value 1 is divided by 20, giving a result of 0. Anything multiplied by 0 is still 0. Of course, 16,000,000 will not fit into an integer, so even if the multiply did not result in 0, you'd still get the wrong result.

Regards,
Ray L.

Thanks guys,

as 'number' was already signed 'unsigned long', is it good practice to still use the UL.

or do you mean any number like 16000000 that wouldnt fit into an integer has to be signed with UL

joeblogs:
Thanks guys,

as 'number' was already signed 'unsigned long', is it good practice to still use the UL.

or do you mean any number like 16000000 that wouldnt fit into an integer has to be signed with UL

Any time you can do anything reasonable to document your intent, you should do it. So, in my shop, yes, use the UL type descriptor. I even like to see silent casts documented.

Best practice is don't take anything for granted. Specify or otherwise force all data types, and use parentheses in expressions to force the order of evaluation, so there is no question what your intent is, and you're not depending on your memory of the (sometime complex) rules of expression evaluation used by the compiler.

Regards,
Ray L.

the origanal formaula was me trying to figure out what was going on
in my program 'clkFreg' was the variable attached to 16000000.

const unsigned long clkFreq = 16000000; // clock speed of microcontroller

so in another part of the program i refered back to the variable for the calculation.

outputFrequency = (clkFreq/(2clkPrescale(1+i)));

do you have to put UL or enforce data type in this case
ie...
outputFrequency = (clkFreqUL/(2clkPrescale(1+i)));

is there a way to make arduino use standard math rules or do i have to read up on integer math

joeblogs:
this code works and puts out 80000
but if the 1.0 gets changed to 1 then i get a reading of 0
is this because 1/20 is a floating point number?

Yes.

joeblogs:
and if so why is an unsigned long accepting a floating point number.
i cant get my head around the process the controller goes through when calculating

This behaviour is a defined part of the C++ programming language. When you combine different integer and floating point types in an expression, there is a specified sequence of things that the compiler is supposed to do. It's called variable (or value) "promotion".

Have a read of this: Implicit conversions - cppreference.com.

Paul / Ray

does this mean that when the code is assembled instead of that variable being an unsigned long, it gets converted to a float.

Or does it (just double checking) stay an unsigned long, size just the actual conversion space gets increased.

can you point me to any good articles on how the assignment of memory works, this is day 6 so i have no idea.

Cheers

The variable is ALWAYS the type you specify. But when an expression is evaluated, some of the values may be "promoted" to another to do the calculation, then the final result converted to the type of the destination variable.

For example:

int intVar = 10;
long longVa = 5;
float floatVar = intVar * longVar;

A COPY of intVar will be "promoted" to a long, and multitplied by longVar. The result of that multiplication will be converted to a float, and stored in floatVar.

long longVar = 10;
float floatVar = 5;
int intVar = longVar * floatVar;

A COPY of longVar will be converted to a float, and multitplied by floatVar. The result of that multiplication will be converted to int, and stored in intVar.

The rules of variable conversion and promotion and the order of expression evaluation are rather complex, which is why I indicated the best approach is to explicitly force the type conversions yourself, and use parentheses to force the evaluation order you want.

Regards,
Ray L.

To follow on what other have said, consider this short program:

void setup() {
  // put your setup code here, to run once:

  int intVal = 5;          // a 2-byte bucket
  long longVal;            // a 4-byte bucket
  float floatVal;          // a 4-byte bucket

  Serial.begin(9600);
  
  longVal = intVal;               // A silent cast
  Serial.print("longVal = ");
  Serial.println(longVal);
  
  longVal = 35000L;             
  intVal = longVal;               // Uh-oh
  Serial.print("intVal = ");
  Serial.println(intVal);
  
  floatVal = 33000.0;
  intVal = floatVal;              // More uh-oh
  Serial.print("intVal = ");
  Serial.println(intVal);
  
  floatVal = 5000000000.0;
  longVal = floatVal;
  Serial.print("longVal = ");
  Serial.println(longVal);
  
}

void loop() {
}

The first assignment of intVal into longVal is an example of a silent cast. It poses no problem because you have a 2-byte bucket of data and are pouring it into a 4-byte bucket. It's called a silent cast because the compiler makes the conversion between dissimilar data types for you "behind your back".

The second assignment of longVal into intVal is also a silent cast, but doesn't work as well because you're taking 4 bytes of data and pouring it into a 2-byte bucket. This runs the risk of spilling 2 bytes of data all over the floor. The same type of issue happens when you assign floatVal into intVal. Note the value that is displayed.

The final assignment shows an instance where we are pouring 4 bytes of data into a 4 byte bucket, but still fails because of the range of values for the two variables. Note the answer displayed.

My own preference is to avoid silent casts. That is, I prefer

  longVal = (long) intVal;               // An explicit cast

because it documents that you know what you are doing and do, in fact, want to make the data type change.

UL is a suffix for a number. NEVER use it as a suffix for a variable name.