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.
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.
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".
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.
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.