Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will the scope of floating point variables affect their values?

If we execute the following C# code on a console application, we will get a message as The sums are Not equal.

If we execute it after uncommenting the line System.Console.WriteLine(), we will get a message as The sums are equal.

    static void Main(string[] args)     {         float f = Sum(0.1f, 0.2f);         float g = Sum(0.1f, 0.2f);          //System.Console.WriteLine("f = " + f + " and g = " + g);          if (f == g)         {             System.Console.WriteLine("The sums are equal");         }         else         {             System.Console.WriteLine("The sums are Not equal");         }     }      static float Sum(float a, float b)     {         System.Console.WriteLine(a + b);         return a + b;     } 

What is the actual reason for this behavior?

like image 555
Jogi Joseph George Avatar asked Jun 20 '14 06:06

Jogi Joseph George


People also ask

Why are floating-point numbers inaccurate?

Floating-point decimal values generally do not have an exact binary representation due to how the CPU represents floating point data. For this reason, you may experience a loss of precision, and some floating-point operations may produce unexpected results.

What every developer should know about floating-point?

Almost every language has a floating-point datatype; computers from PCs to supercomputers have floating-point accelerators; most compilers will be called upon to compile floating-point algorithms from time to time; and virtually every operating system must respond to floating-point exceptions such as overflow.

How do floating-point numbers work?

Decimal floating-point numbers usually take the form of scientific notation with an explicit point always between the 1st and 2nd digits. The exponent is either written explicitly including the base, or an e is used to separate it from the significand.

Why is it called floating-point?

Floating point numbers get their name from the way the decimal point can "float" to any position necessary. Due to this, in computer science, floating point numbers are often referred to as floats. Other common types of numbers in computer science are integers, short, and long.


2 Answers

It's not related to scope. It's the combination of the stack dynamics and floating point handling. Some knowledge of compilers will help make this counterintuitive behavior clear.

When the Console.WriteLine is commented, the values f and g are on the evaluation stack and stay there until after you've passed the equality test in your Main method.

When Console.Writeline is not commented, the values f and g are moved from the evaluation stack to the call stack at the moment of the invocation, to be restored to the evaluation stack when Console.WriteLine returns. And your comparison if (f == g) is done afterwards. Some rounding can occur during this storing of values to the call stack and some information can be lost.

In the scenario where you do invoke Console.WriteLine, the f and the g in the comparison test are not the same values. They've been copied and restored to a format that has different rules on precision and rounding, by the virtual machine.

In your particular code, when the invocation of Console.WriteLine is commented, the evaluation stack is never stored to the call stack and no rounding occurs. Because it is permitted for implementations of the platform to provide improved precision on the evaluation stack, this discrepancy can arise.

EDIT What we're hitting in this case is allowed by the CLI specification. In section I.12.1.3 it reads:

Storage locations for floating-point numbers (statics, array elements, and fields of classes) are of fixed size. The supported storage sizes are float32 and float64. Everywhere else (on the evaluation stack, as arguments, as return types, and as local variables) 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 float32or float64, but its value can be represented internally with additional range and/or precision. The size of the internal floating-point representation is implementation-dependent, can vary, and shall have precision at least as great as that of the variable or expression being represented.

The keywords from this quote are "implementation-dependent" and "can vary". In the OP's case, we see his implementation does indeed vary.

Non-strictfp floating point arithmetic in the Java platform also has a related issue, for more info check also my answer to Will floating point operations on the JVM give the same results on all platforms?

like image 104
Mishax Avatar answered Oct 13 '22 18:10

Mishax


What is the actual reason for this behaviour?

I can't provide details for exactly what's going on in this specific case, but I understand the general problem, and why using Console.WriteLine can change things.

As we saw in your previous post, sometimes operations are performed on floating point types at a higher precision than the one specified in the variable type. For local variables, that can include how the value is stored in memory during the execution of a method.

I suspect that in your case:

  • the Sum method is being inlined (but see later)
  • the sum itself is being performed with greater precision than the 32-bit float you'd expect
  • the value of one of the variables (f say) is being stored in a high-precision register
    • for this variable, the "more precise" result is being stored directly
  • the value of the other variable (g) is being stored on the stack as a 32-bit value
    • for this variable, the "more precise" result is being reduced to 32 bits
  • when the comparison is performed, the variable on the stack is being promoted to a higher-precision value and compared with the other higher-precision value, and the difference is due to one of them having previously lost information and the other not

When you uncomment the Console.WriteLine statement, I'm guessing that (for whatever reason) forces both variables to be stored in their "proper" 32-bit precision, so they're both being treated the same way.

This hypothesis is all somewhat messed up by the fact that adding

[MethodImpl(MethodImplOptions.NoInlining)] 

... does not change the result as far as I can see. I may be doing something else wrong along those lines though.

Really, we should look at the assembly code which is executing - I don't have the time to do that now, unfortunately.

like image 43
Jon Skeet Avatar answered Oct 13 '22 18:10

Jon Skeet