37

Can any of you explain why does this happen?

static void Main()
{
    const float xScaleStart = 0.5f;
    const float xScaleStop = 4.0f;
    const float xScaleInterval = 0.1f;
    const float xScaleAmplitude = xScaleStop - xScaleStart;

    const float xScaleSizeC = xScaleAmplitude / xScaleInterval;

    float xScaleSize = xScaleAmplitude / xScaleInterval;

    Console.WriteLine(">const float {0}, (int){1}", xScaleSizeC, (int)xScaleSizeC);

    Console.WriteLine(">      float {0}, (int){1}", xScaleSize, (int)xScaleSize);

    Console.ReadLine();
}

Output:

>const float 35, (int)34
>      float 35, (int)35

I know that the binary representation of 0.1 is actually 0.09999990463256835937, though why does this happen using 'const float' and not with 'float'? Is this considered a compiler bug?

For the record, the code compiles into:

private static void Main(string[] args)
{
    float xScaleSize = 35f;
    Console.WriteLine(">const float {0}, (int){1}", 35f, 34);
    Console.WriteLine(">      float {0}, (int){1}", xScaleSize, (int)xScaleSize);
    Console.ReadLine();
}
joaojose
  • 273
  • 3
  • 7
  • Thank you Ulugbek for the compiled code – joaojose Jun 03 '14 at 13:39
  • 3
    I will confirm that this does not always happen. Using monodevelop and Unity3d (runs a flavor of mono) both results are 35. – NPSF3000 Jun 03 '14 at 13:45
  • maybe a duplicate, or at least related: [Why differs floating-point precision in C# when separated by parantheses and when separated by statements?](https://stackoverflow.com/questions/2491161/why-differs-floating-point-precision-in-c-sharp-when-separated-by-parantheses-an) – sloth Jun 03 '14 at 13:59
  • Related, especially as the answer is the same: [Get float constant to be the same the runtime result (and VB.NET)](http://stackoverflow.com/q/19593234/256431) – Mark Hurd Jun 05 '14 at 02:45

2 Answers2

17

The "Why" of this will basically boil down to the fact that frequently, when working with float data, an internal representation may be used that has more precision than is specified for float or double. This is explicitly catered for in the Virtual Execution System (VES) Spec (section 12 of Partition I):

floating-point numbers are represented using an internal floating-point type. In each such instance, the nominal type of the variable or expression is either float32 or float64, but its value can be represented internally with additional range and/or precision

And then later we have:

The use of an internal representation that is wider than float32 or float64 can cause differences in computational results when a developer makes seemingly unrelated modifications to their code, the result of which can be that a value is spilled from the internal representation (e.g., in a register) to a location on the stack.

Now, according to the C# language specification:

The compile-time evaluation of constant expressions uses the same rules as run-time evaluation of non-constant expressions, except that where run-time evaluation would have thrown an exception, compile-time evaluation causes a compile-time error to occur.

But as we observe above, the rules actually allow more precision to be used at times, and when this enhanced precision is used isn't actually under our direct control.


And obviously, in different circumstances, the results could have been precisely the opposite of what you observed - the compiler may have dropped to lower precision and the runtime could have maintained higher precision instead.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • 1
    Also relevant from the C# spec: *Floating-point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an “extended” or “long double” floating-point type with greater range and precision than the double type, and implicitly perform all floating-point operations using this higher precision type.* `xScaleSizeC` is actually `34.99999947845936532075938885100185871124267578125` (at least on my hardware), and that explains why it gets truncated to `34` when cast to `int`. – sloth Jun 03 '14 at 13:58
  • @sloth - yes, that looks like the "translation" of the VES specification into C# terms. I went with the VES one just because I'm more familiar with it. – Damien_The_Unbeliever Jun 03 '14 at 14:00
  • I can see xScaleSizeC as 35f in the decompiled code with dotPeek. I just get that value if I have it as double... – joaojose Jun 03 '14 at 14:14
  • @joaojose Have a look at [this](http://www.yoda.arachsys.com/csharp/DoubleConverter.cs) class which will give you a string representation of the exact decimal value of `xScaleSizeC`. – sloth Jun 03 '14 at 14:23
  • @sloth I get your answer and you are right, though I don't get why does the compiler "rounds" a 34.9999 float to 35f? – joaojose Jun 03 '14 at 14:37
  • 1
    @joaojose A float has only a 7-digit precision. `34.9999` is fine and can be represented, also `34.99999`. But `34,9999994784594` can't be represented using only 32-bits, so it gets rounded to nearest, which is `35.00000`. – sloth Jun 03 '14 at 14:53
1

I can't say this is a duplicate question since in here --> Eric Postpischil comment

explained something very similar regarding int's and const int's.

The main idea is that the division of two constants calculated by the compiler before generating the code and not in run-time, BUT in this specific case whenever the compiler does this it performs the calculations in double format. Thus the xScaleSizeC is basically equals 34.9999... so when it getting cast in run-time to int it becomes 34.

Community
  • 1
  • 1
Skalik
  • 46
  • 2
  • 3
    @joaojose as explained in Damiens answer, the calculation of `xScaleSizeC` at hardware level is done with more precision than .Net's `float` type can represent. So the const `xScaleSizeC` is actually `34.999999478...`, and when forced into .Net's `float` type, you lose the precision and the value gets rounded to `35`, and when cast into `int`, the decimal values get truncated, resulting in `34`. You can see it yourself when using a type with higher precision, like `double`: `xScaleSizeC` won't get rounded up and is represented by `34.999999478...`. – sloth Jun 03 '14 at 14:27