Equation issues with mixed variable types

The following code (incorrectly) returns zero:

  int MeterFreshPulses = 150;
  int ThreadElapsedMS = 1500;
  float MeterFreshHZ = (MeterFreshPulses / ThreadElapsedMS * 1000);

Whereas this code returns the expected result:

  float MeterFreshPulses = 150;
  float ThreadElapsedMS = 1500;
  float MeterFreshHZ = (MeterFreshPulses / ThreadElapsedMS * 1000);

I can see whats probably happening is its deciding to do the division first (150/1500=0.1), store this part as an integer for some reason (int 0.1=0) then multiply it out (0*1000=0).

Surely this is a very poor way of evaluating expressions?

There are strict rules for C++ defining how the compiler handles mixed types... But I'd have to dig-deep into one of my C++ books...

Maybe someone can give you a quick summary.

There is automatic type promotion and there is something called type casting, but in this case there's probably no point in "casting" to a float instead of creating a float in the 1st place. (Again a better programmer might know when it's better to use a particular approach.)

And this will overflow a 16-bit int which goes from -32,768 to +32,767.

ThreadElapsedMS * 1000

So it's probably a good idea to add another set of parenthesis to make sure the division happens first. IMO It's better to rely on parenthesis than to remember the precedence rules. :wink:

No, it's zero.

C performs integer division when the inputs are integers. This often surprises beginners!

Try this

  int MeterFreshPulses = 150;
  int ThreadElapsedMS = 1500;
  float MeterFreshHZ = 1000.0 * MeterFreshPulses / ThreadElapsedMS;

This clearly explains this issue.

Though I stick with it being a poor method for evaluating !

It's a poor craftsman who blames his tools.

as well as C++ type conversion also consider C++ operator_precedence

It's a fast and efficient method. That's what C is all about. Integer division is faster and more efficient than floating point, especially on a CPU with no floating point hardware acceleration. If you want floating point division, you can have it, simply by making sure at least one of the inputs is a float.

What I'm getting at is that C is not clever enough to look outside of the immediate division calculation and realise that the wanted calculation is indeed a decimal, both by a float being additionally specified in the rest of the equation, and a float being assigned to the overall calculation. Clearly, speed over logic has been chosen, and I get that.

Also, I believe "ensure one input is a float" is not quite right....its only the divisor that needs to be a float.

A neater workaround I saw (apart from re-ordering your many equations, or changing all your variables to bloaty decimals) is to cast the divisor on the fly to a float, which is the approach I'm taking.

  int a;
  int b;
  float result = a / float()b;

No need to rely on belief. Test it! Let us know what you find.

This is a numeric "expression" whose result is assigned to a variable.
There is no indication anywhere in the expression ((MeterFreshPulses / ThreadElapsedMS * 1000)) that floating point calculations should be done, so it's all done in integer math (resulting in zero, which is then assigned to the float variable.)

yes, that's fine (-ish), as is:
float result = (float)a / b;
or the previously mentioned
float result = a * 1000.0 / b;
(where the use of a float constant in the expression forces the expression to be evaluated as floats)
But:
float result = a / b * 1000.0;
would not work, since a/b is still integers.

No it's logic first and foremost; speed happens to be the default in other ways, like with division. If it's two integers, don't overthink it: do integer division.

its deciding to do the division first (150/1500=0.1)

That was you. The division is first. Operator precedence rules can get complicated, but they don't come into play here. Just left to right.

The compiler trying to divine your "real" intention would probably lead to more complex problems later on. Overall, it's better for it to do exactly what you ask it to. (Optimizations can be applied if they are provably correct.)

There is no float in the original expression. (And the parentheses around the entire expression are pointless.)

and a float being assigned to the overall calculation

Maybe you need a float for the next calculation, using the result of an integer division.

Being C++ and not C, better to use the appropriate _cast, static_cast in this case. Apply it first, to avoid surprises. Optionally, less redundant with auto.

  auto MeterFreshHZ = static_cast<float>(MeterFreshPulses) / ThreadElapsedMS * 1000;

Then again, what is the hertz value used for? In the original example, the answer is exactly 100.0. If you were going to display them, would it make a difference if it was "100Hz" instead of (for example) "100.4Hz"? If not, the order matters (again)

  int MeterFreshPulses = 151;
  int ThreadElapsedMS = 1504;
  int MeterFreshHZ = 1000 * MeterFreshPulses / ThreadElapsedMS;

The multiplication results in a bigger number, which after integer division is less likely to result in a smaller-than-expected number, including zero. Then you have a different potential problem. Will the bigger number be too big and overflow (as mentioned earlier)? Just some of the details to inform your choices when programming in C/C++

One more thing: 1000.0 is actually a double; for a float, it's 1000.0f. Unless you're on AVR, where they're both 32-bit, that can sometimes make a difference

  int x = 150;
  int y = 1500;
  float f = 1e38f * x / y;
  Serial.println(f);
  f = 1e38 * x / y;
  Serial.println(f);
  Serial.println(String(f));
  Serial.println(String(1e37f));
  Serial.println(String(1e37));

prints

inf
ovf
9999999933815812980242299090605229139.32
9999999933815812980242299090605229139.32
10000000000000002220446049250313080847.26

Serial.print has a fairly low hard-coded limit before it gives up and says overflow. The String constructors don't (they call dtostrf).

How would it know what you want? Your computer cannot (yet) read your mind. Perhaps your intention in dividing one integer by another with a result that includes a decimal part is that the decimal part is ignored. C/C++ assumes you know what you are doing and that if you tell it to do a division like this then that's what you want. How annoying if that is what you want and the compiler assumes it knows best and gives you an answer you didn't want.

Let us know when you finish writing the compiler for the language you want to use, not the one you have at hand!

Who knows… maybe we've all been suffering with a poorly designed going nowhere language from the last century. And its progenitors progeny.

a7