Q:

Error in simple float calculations

I am having trouble with:
int q = 150;
float s = 0.7f;
float z = q*s;
int z1 = (int) (q*s);
int z2 = (int) z;

This results in 

z1 being int with value 104
z2 being int with value 105

Can anyone explain this? I do not understand these results.

To avoid closing, I (René Vogt) add this information:

q*s results in a float of value 105.0f (or maybe 104.999999, but a string represention ends up as 105).
so z is a float of 105

The question now is, why (int)z results in 105, but (int)(q*s) results in 104? I can reproduce this on my machine (i7, Win10, VS2015, .NET4.6.1)

And the IL code:
// Initialisation
// q = 150
ldc.i4 0x96  
stloc.0
// s = 0.7f
ldc.r4 0.69999999 
stloc.1

// calculating z 
ldloc.0 // load q
conv.r4 // convert to float
ldloc.1 // load s
mul     // q*s
stloc.2 // store float result to z

// calulating z1
ldloc.0 // load q
conv.r4 // convert to float
ldloc.1 // load s
mul     // q*s
conv.i4 // convert to int
stloc.3 // store int result in z1 => 104!

// calculating z2
ldloc.2 // loading float z
conv.i4 // converting to int
stloc.s // write to z2 (last local variable -> "s" as stack address)
        // => 105

So I think the only difference between z1 and z2 is that for z2 the intermediate float result gets written from the register to the local variable's (z) storage place. But how would that affect the result?

A:

The number 0.7 cannot be represented exactly by a float, instead the value of s is closer to 0.699999988079071044921875.
The int value of q will be converted to a float, as this can be represented directly it stays as 150.
If you multiply the two together you won't get 105 exactly:

q = 150
  s = 0.699999988079071044921875
  q * s = 104.999998211861 

Now refer to the relevant part in the CLI Spec (ECMA-335) §12.1.3:

When a floating-point value whose internal representation has greater range and/or precision than its nominal type is put in a storage location, it is automatically coerced to the type of the storage location. This can involve 
  a loss of precision or the creation of an out-of-range value (NaN, +infinity, or -infinity). However, the value might be retained in the internal representation for future use, if it is reloaded from the storage location without having been modified. It is the responsibility of the compiler to ensure that the retained value is still valid at the time of a subsequent load, taking into account the effects of aliasing and other execution threads (see memory model (§12.6)). This freedom to carry extra precision is not permitted, however, following the execution of an explicit conversion (conv.r4 or conv.r8), at which time the internal representation must be exactly representable in the associated type.

So q * s results in a value with higher precision than float can handle. When storing this directly to an int:
var z1 = (int)(q * s);

The value is never coerced to the type float, but directly cast to int and thereby truncated to 104.  
In all other examples the value was cast to or stored in a float and therefore converted to the nearest possible float value, which is 105.

