Another great question was asked on the C# subreddit about integer vs. floating point division in C#. In the post, the commenter essentially asked why, if they store the result of their division in a float, does the whole calculation produce the “wrong” answer?

They had the following code.

int boardSize = 16;
float offset = ((boardSize % 2f) - 1) / -2;
Console.WriteLine(offset);

offset = ((boardSize % 2) - 1) / -2;
Console.WriteLine(offset);

The first version produced the correct answer, 0.5. The second produced an “incorrect” answer, 0. Why is this?

In order to run a calculation of any kind, you need to have consistent types in C#. In the first example, the boardSize % 2f has a floating-point operand. This causes the whole expression to be converted to floating point, which gives the correct answer.

In the second version, the whole expression is made up of integers, so we get integer calculations the whole way. Just storing the result in a float will not force the whole statement to run using floating-point operations!

Let’s look at the code the compiler gives us.

    {
      int num = 16;
      Console.WriteLine((float) (((double) num % 2.0 - 1.0) / -2.0));
      Console.WriteLine((float) ((num % 2 - 1) / -2));
    }

Check it out. The compiler automatically turns the first calculation into floating points for us. If we check the intermediate language produced, we verify this is what’s happened

   // 
    // Load the value 16 into our local variable "num"
    //
    // IL_0001: ldc.i4.s     16 // 0x10
    // IL_0003: stloc.0      // 'num [Range(Instruction(IL_0003 stloc.0)-Instruction(IL_0020 ldloc.0))]'
    // IL_0004: ldloc.0      // 'num [Range(Instruction(IL_0003 stloc.0)-Instruction(IL_0020 ldloc.0))]'
    //
    // Here is where we convert the value at the top of our stack (num)
    // to a float.
    // 
    // IL_0005: conv.r4
    //
    // Then we push floating point 2 onto the stack...
    //
    // IL_0006: ldc.r4       2
    //
    // ...and divide the two, taking the remainder and pushing it
    // onto the stack. 
    //
    // IL_000b: rem
    //
    // Then we push floating point 1 onto the stack...
    //
    // IL_000c: ldc.r4       1
    //
    // ...and subtract 1 from the remainder we found earlier, pushing
    // that value on the stack.
    //
    // IL_0011: sub
    //
    // Now we load floating point -2 onto the stack, and perform our
    // division. 
    //
    // IL_0012: ldc.r4       -2
    // IL_0017: div
    //
    // In order to print the value to screen, we need to have a local
    // variable that contains our result. The compiler generates
    // that for us.
    //
    // IL_0018: stloc.1      // V_1
    // IL_0019: ldloc.1      // V_1
    // IL_001a: call         void [System.Console]System.Console::WriteLine(float32)
    // IL_001f: nop
    //
    //
    // Now for the all-integer version. Check this out. In IL,
    // there are dedicated instructions for loading some values
    // onto the stack that are integers.
    //
    // First pull the value stored in num and put it on the stack.
    // 
    // IL_0020: ldloc.0      // 'num [Range(Instruction(IL_0003 stloc.0)-Instruction(IL_0020 ldloc.0))]'
    //
    // Load integer 2 onto the stack, perform division, take remainder.
    // This is all integer!
    //
    // IL_0021: ldc.i4.2
    // IL_0022: rem
    //
    // Then load integer 1 onto the stack, perform subtraction.
    //
    // IL_0023: ldc.i4.1
    // IL_0024: sub
    //
    // Finally our division.
    //
    // IL_0025: ldc.i4.s     -2 // 0xfe
    // IL_0027: div
    //
    // CHECK THIS OUT! In our original code, we created a variable
    // called offset, type float. So to store our integer result
    // in a floating point type, we have to cast our integer
    // back to float. 
    //
    // IL_0028: conv.r4
    // IL_0029: stloc.1      // V_1
    // IL_002a: ldloc.1      // V_1
    // IL_002b: call         void [System.Console]System.Console::WriteLine(float32)
    // IL_0030: nop
    // IL_0031: ret
    // 
    {
      int num = 16;
      Console.WriteLine((float) (((double) num % 2.0 - 1.0) / -2.0));
      Console.WriteLine((float) ((num % 2 - 1) / -2));
    }

The moral of our story?

  1. Integer operations produce less code.
  2. As soon as one operand is a float, the whole operation turns into floating point. The compiler makes sure of that.
  3. Storing your result in a float doesn’t force the expression to evaluate using floating point.

Published by Elias

Elias Puurunen is a versatile entrepreneur and President of Northern HCI Solutions Inc., an IT consulting firm which has worked with Fortune 500 companies, governments, and startups. He has spoken at conferences in Canada and the United States and has been published around the world. Part of his work led to an agreement between the Canadian Government and Siemens Canada, creating jobs and investment into green infrastructure. His company's event management app, the Tractus Event Passport connects people at conferences, seminars and symposiums across Canada. Today he is a consultant and advisor to technology firms and government organizations. He lectures at the University of Waterloo on Coding for Policy Analysis for the School of Public Policy. He is the author of Beyond Passwords: Secure Your Business, a cyber-security book for small business owners.

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: