0

So I am very confused at the results I am getting. It seems that floats aren't working properly, and I don't understand why.

I was trying to read a temp sensor and convert the reading to celsius. However, when I tried to divide the value (an int between 145-165) by 1024, and multiply by 5 I would get back a float value of 0.00 Here's the code I was using

    void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(A0, INPUT);

}

void loop() {
  int sensorVal = analogRead(A0);
    Serial.print("Sensor Value: ");
    Serial.println(sensorVal);

  float voltage = ((sensorVal/1024) * 5);
    Serial.print("Voltage Value: ");
    Serial.println(voltage);

}

This would only return values of 0.00 for voltage, even though the sensor itself is working fine, returning varying values between 146 and 165 based on how much heat I apply. This didn't make any sense, so I tried it with some other values, eg.

Serial.print(float (10 / 6));

and

Serial.print(float (5 / 2));

which should have returned 1.67 and 2.50 respectively, but instead returned 1.00 and 2.00

I think it might be a settings thing, where all floats round down to an int, but that seems really weird.

Any help would be great!

  • 1
    Hint: Try float voltage = ((sensorVal/1024.0) * 5.0); – Mikael Patel Jun 11 at 22:18
  • FYI, your intermediate results are truncated, not rounded. ( 0/11 will yield 1, since the answer is ≈1.81, and it simply strips away the decimal part. By comparison, round(20.0/11.0) will give you a value of 2.0, since 1.8181 rounded rounds up to 2.) – Duncan C Jun 12 at 2:30
1

You are doing integer arithmetic. Try adding .0 to the end of your values where you need the floating point results.

For example...


  float voltage = ((sensorVal/1024.0) * 5.0);

Remember the results of analog read is an integer. So the conversion to a float in you original code only happens when the "=" is being "executed". Another possibility is to cast it to a float straight away.


  float voltage = ((((float)sensorVal)/1024.0) * 5.0);

Or more simply


  float voltage = ((float)sensorVal)/1024.0 * 5.0;
  • This helped a lot. I realized that I should have also initialized my sensorVal as a float – Mitch Ostler Jun 11 at 23:29
4

You already got a couple of perfectly good answer. I will nevertheless suggest a slightly different idiom:

float voltage = sensorVal * (5.0 / 1024);

or, maybe better:

const float V_REF = 5.0;  // at the top of the sketch

float voltage = sensorVal * (V_REF / 1024);

The reason this is slightly better than the other options is because division – even integer division – is a very expensive operation on the smaller Arduinos. By writing the conversion as in the lines above, the ratio V_REF / 1024 becomes a compile-time constant, which is evaluated by the compiler at build time. Then, only the much cheaper multiplication needs to be done by the Arduino itself, at run time. Not a big deal if you are not time constrained, but it's a low-hanging fruit in terms of optimization.

You may write 1024.0 instead of 1024 if it feels safer. I tend to keep the .0 for the voltage only, because 1024 is an integer, whereas the “5” in “5 volts” is conceptually a real number (it's an analog voltage, and not even exactly 5 V if you bother to measure it). But this is just a personal preference at the end.

  • Good point on avoiding runtime division. (voted) – Duncan C Jun 12 at 14:59
  • I must admit @edgar_bonet, I found what you said very hard to believe - after all one of the things that an optimising compilers should check for is "constant arithmetic" and "work it out for you" and just apply the result. That is, x / 1023.0 * 5.0 should be optimised as (X / 204.6) . Since I didn't believe you (sorry about that), I tried a simple program and was stunned to see that it actually executes the division of the constants in the generated assembler - unless you put that part of the expression in parenthesis. – GMc Jun 13 at 10:34
  • What was also of interest was that in my first version I simply put int reading = analogRead(A0); float result = reading / 1023.0 * 5.0; in my setup() function with nothing else. Long story short, the compiler optimised the whole block of code out of the generated assembler (because I wasn't doing anything with the result). It wasn't until I put in Serial.println(result); that the calculation was emitted by the compiler. I was going to down vote you ('cos I thought you were wrong), but after checking and found you were right - you get an upvote from me to. – GMc Jun 13 at 10:37
  • @GMc: The compiler cannot optimize x / 1023.0 * 5.0 into x * (5.0 / 1023.0) because the two expressions, although equivalent in the realm of real numbers, are not equivalent for floating point numbers. The compiler is not allowed to perform an optimization that can possibly modify the result of a computation. That is, unless you give it an option like -funsafe-math-optimizations, which is off by default. – Edgar Bonet Jun 13 at 12:16
  • @edgar_bonet thanks for the insights and the great addition to the answer I will definitely explore this further. – GMc Jun 14 at 5:22
2

GMc explained what to do, but didn't explain in detail why your code fails to give floating point results.

Most languages evaluate expressions using order of operations. C and C++ are no exception to this.

In C/C++, while the expression is being evaluated, the compiler also decides what data types to use for each part. integer-integer expressions are evaluated using integer math. If either of the parts of an expression is a larger/floating point type, both values are "promoted" to the larger/more complex type before evaluating the expression.

in your code

float voltage = ((sensorVal/1024) * 5);

The sensorVal/1024 bit is evaluate first. Since both sensorVal and 1024 are integers, the compiler uses integer math. That yields an int result, and so any fractional part is truncated and lost.

It then evaluates result*5 (where result is the result of the first part, sensorVal/1024, which is an integer.) Both of those parts are integers, so the result of that expression is an int.

Finally, you ask the compiler to store the result into a float, voltage. The compiler then promotes the integer result of the whole expression to a float, but you've lost any floating point part of your calculations.

By rewriting your line as

float voltage = ((sensorVal/1024.0) * 5.0);

The compiler does what you want. The sensorVal/1024.0 bit is now integer-float math, so the compiler "promotes" sensorVal to a float and then does float-float math. The second part is now float-float, so the result is also floating point.

float voltage = ((sensorVal/1024.0) * 5);

Would also work in this case (where 5 is an integer constant.) That works because float_result*5 is float-integer math, and the compiler will promote 5 to a floating point value and do float-float math. to get the final result. However, it's safer to use all floating point constants so you don't accidentally do part of your calculations using integer math and truncate that part before converting to floating point.

As you say, making sensorVal a float also would be a good idea. (Again, to avoid accidentally doing integer math on part of your expression and getting wrong results in the process. In this case making sensorVal a float isn't needed, but with a different expression you might wind up with integer math for part of the calculation.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.