Why, when entering the number 2 in e_power_enter, does the program display a 19 in the MessageBox, not 20? (engine.power type float)

int k; engine.power = float.Parse(e_power_enter.Text) / 100; k = (int)(engine.power * 1000); MessageBox.Show(Convert.ToString(k)); 
  • And what is the actual value in e_power_enter.Text , for which the result is 19, not 20? - Regent
  • 2
    @ Alex78191: But my 2017th studio issued 19, what am I doing wrong? - VladD
  • one
    That is why so, it is clear. But it is not clear what affects the result. - VladD
  • one
    @ Alex78191 you are very polite in this community - ZOOM SMASH
  • one
    @ Alex78191 learn to compile: D - Alexey Shimansky

1 answer 1

The fact is that float / double in C # (as well as in Java, C ++, etc.) are binary fractions . They are represented internally as a set of integer (mantissa) and a power of two (of order). The number 2 can be represented as a binary fraction, but 0.02 is not, since it is equal to 1/50 (and in the denominator not only powers of two). Therefore, the number 0.02 cannot be exactly represented as a float .

What is contained in the variable engine.power ? It contains the binary fraction closest to 0.02. Check it with this code:

 float enginepower = 2f / 100; Console.WriteLine(enginepower * 100 - 2); 

It gives out not 0, as one would expect, but -4.470348E-08 (on my machine).

This means that the engine.power value engine.power actually a little less than two. When multiplying by 1000, the result will be slightly less than 20, the cast to int drop the fractional part, and the result will be equal to 19.


What to do? There are several options.

  1. Instead of dropping a fractional part unstable to small errors

     (int)(engine.power * 1000) 

    use much more robust rounding :

     (int)Math.Round(engine.power * 1000) 
  2. If you encounter similar problems often, it makes sense to switch to the decimal data type, in which the numbers inside are stored as decimal , not binary fractions. Note that operations with this data type are slower, since there is no native support for processors.


Advanced investigation , with digging in assembly exhaust and specifications.

On my machine, this is the code:

 float f = 0.02f; float ff = f * 1000; int k = (int)(ff); 

calculates a k value of 20, and here it is:

 float f = 0.02f; int k = (int)(f * 1000); 

- 19. (This is in Debug mode; in Release mode, both versions of the text produce the same assembly code and the same result - 19.) Investigate why this is so.

The assembled JIT code is as follows:

  ; float f = 0.02f; mov dword ptr [ebp-40h],3CA3D70Ah ; 32bit f = 0.02f ; float ff = f * 1000; fld dword ptr [ebp-40h] ; extend f to 80bit prec and push fmul dword ptr ds:[1453D00h] ; multiply by 32bit 1000f fstp dword ptr [ebp-44h] ; pop and convert 80bit result to 32 bit -> ff ; int k = (int)(ff); fld dword ptr [ebp-44h] ; extend ff to 80bit prec and push fstp qword ptr [ebp-50h] ; pop and convert to 64bit -> double temp movsd xmm0,mmword ptr [ebp-50h] ; extend temp to 128bit and copy to xmm0 cvttsd2si eax,xmm0 ; truncate to 32bit int eax mov dword ptr [ebp-48h],eax ; store to k 

and

  ; float f = 0.02f; mov dword ptr [ebp-40h],3CA3D70Ah ; 32bit f = 0.02f ; int k = (int)(f * 1000); fld dword ptr [ebp-40h] ; extend f to 80bit prec and push fmul dword ptr ds:[1393CF4h] ; multiply by 32bit 1000f fstp qword ptr [ebp-4Ch] ; pop and convert to 64bit -> double temp movsd xmm0,mmword ptr [ebp-4Ch] ; extend temp to 128bit and copy to xmm0 cvttsd2si eax,xmm0 ; truncate to 32bit int eax mov dword ptr [ebp-44h],eax ; store to k 

This can be conventionally written as:

 float32 f = 2f / 100; float80 r1 = f; r1 *= float32(1000f); float32 ff = r1; r1 = ff; // <-- тут потеря точности float64 temp = r1; float128 r2 = temp; int32 k = (int32)r2; 

and

 float32 f = 2f / 100; float80 r1 = f; r1 *= float32(1000f); float64 temp = r1; float128 r2 = temp; int32 k = (int32)r2; 

The difference, as we see, is that for calculating an intermediate result, the value of f * 1000 is truncated to 32-bit precision, and then loaded back into the 80-bit register, and from there through the 64-bit value is loaded into the XMM register. In the second variant of the code, the intermediate result is not stored, the code does not trim the value, and a more accurate 80-bit value is trimmed to 64 bits, which leads to a difference in the result.


Explicit permission for such different computation paths is in the language specification, §4.1.6 Floating Point Types:

Than the result of the operation. For example, it can be noted that it can be used for example.

More on the topic: Strange behavior when casting a float to int in C # (especially the top answer).

  • Eh, if you could explain why with the introduction of the additional variable we get the expected value ... screencast.com/t/vXjvnhKxG :) - MihailPw
  • one
    @ AGS17 is not a temporary variable at all. Optimizations will kill your temporary variable. See: Console.WriteLine(Convert.ToString((int)float.Parse("2") / 100f * 1000f)); this code will output 20. - Nikita
  • one
    Here is the answer to the explanation of such results. - Regent
  • one
    @VladD in the end, you can say so. The first rule: never use a direct conversion of a fractional number to an int. The second rule: never never use a direct conversion of a fractional number to an int. You can also recall different results between double d1 = 6.2f * 10; and float f = 6.2f; double d2 = f * 10; float f = 6.2f; double d2 = f * 10; . - Regent
  • one
    @Regent: Yeah, truncate doesn't have much sense, because rounding / presentation errors can easily result from >= n to < n . - VladD