18

While "we all know" that x == y can be problematic, where x and y are floating point values, this question is a bit more specific:

int x = random.Next(SOME_UPPER_LIMIT);
float r = x;
// Is the following ALWAYS true?    
r == x

Now, since the range of float of is much larger than that of integers (but the precision is insufficient to uniquely present integers at the edges), it would be nice if responses to this question also addressed which values of x the above can be guaranteed for, if it can be guaranteed at all.


Currently my code is making this assumption (for relatively small values of x) - I would like to make sure that I won't get bitten :)


This will fail with "not equal: 16777217" (cast float -> int):

for (int i = 0; i < int.MaxValue; i++) {
   float f = i;
   if ((int)f != i) throw new Exception("not equal " + i);
}

This similar code will not fail (only int -> float); however, due to loss in the conversion, there are several floats that can "equal" the same integer, and may represent a silent bug:

for (int i = 0; i < int.MaxValue; i++) {
   float f = i;
   if (f != i) throw new Exception("not equal " + i);
}
  • 5
    Loop through `Int32.MinValue` to `Int32.MaxValue`, comparing the results of the cast every time. Collect the cases where the comparison is false and you have an answer (for your architecture at least). – Oded Sep 27 '12 at 19:59
  • 2
    @pst: don't think there is *any* generic correct answer on this question, honestly. Assumption "always" would never work on different machines, so it will never be *always*. If, naturally, we are talking about positive answer here. – Tigran Sep 27 '12 at 20:01

5 Answers5

13

Yes, the comparison will always be true, whatever value the int is.

The int will be converted to a float to do the conversion, and the first conversion to float will always give the same result as the second conversion.

Consider:

int x = [any integer value];
float y = x;
float z = x;

The values of y and z will always be the same. If the conversion loses precision, both conversions will lose the precision in exactly the same way.

If you convert the float back to int to to the comparison, that's another matter.


Also, note that even if a specific int value converted to float always results in the same float value, that doesn't mean that the float value has to be unique for that int value. There are int values where (float)x == (float)(x+1) would be true.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Thanks. That is precisely the step/logic I was missing in my head: `y = x`, `z = x`, therefor `y == z`. It makes much more sense now. –  Sep 27 '12 at 20:11
  • 1
    Just note that this is language specific. Some languages may not require the result of converting an integer to a float to be consistent. (For example, one may be done in a register with additional precision and one may be done in memory. When the memory result is fetched and increased to register length, the lost precision may cause them to miscompare.) – David Schwartz Sep 27 '12 at 20:29
  • +1. Detailed float format can be looked at http://en.wikipedia.org/wiki/IEEE_754-2008. My understanding is `int`-> `float` conversion it is roughly (`value & 0xFFFFFD00`) so ints will be grouped into clusters of ~512 values that are equal to the same float value. – Alexei Levenkov Sep 27 '12 at 21:14
  • 1
    @AlexeiLevenkov: That is so for very large integer values. Integers up to seven digits can be represented exactly as a float, then they are grouped in larger clusters the larger the integer values are. – Guffa Jan 08 '13 at 16:27
5

The following experiment reveals that the answer is you do not have that edge case where equality is not true

    static void Main(string[] args)
    {
        Parallel.For(int.MinValue, int.MaxValue, (x) =>
        {
            float r = x;
            // Is the following ALWAYS true?    
            bool equal = r == x;
            if (!equal) Console.WriteLine("Unequal: " + x);                
        });

        Console.WriteLine("Done");
        Console.ReadKey();

        return;
}

It seems reasonable that the conversions

float f = i;

and

if ((int)f != i)

should follow the same rules. This proves that int -> float and float -> int conversions are a bijection.

NOTE: the experiment code actually doesn't test the edge case int.MaxValue because Parallel.For's to parameter is exclusive, but I tested that value separately and it also passes the test.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
5

When comparing an int and a float, the int is implicitly cast to a float. This ensures the same loss of precision happens, and so the comparison will happen to always be true. As long as you don't disturb the implicit cast or do arithmetic, the equality should hold. For example, if you write this:

bool AlwaysTrue(int i) {
    return i == (float)i;
}

there is an implicit cast, so it's equivalent to this function that should always return true:

bool AlwaysTrue(int i) {
    return (float)i == (float)i;
}

but if you write this:

bool SometimesTrue(int i) {
    return i == (int)(float)i;
}

then there is no more implicit cast and the loss of precision only happens on the right side. The result may be false. Similarly, if you write this:

bool SometimesTrue(int i) {
    return 1 + i == 1 + (float)i;
}

then the loss of precision might not be equivalent on both sides. The result may be false.

Craig Gidney
  • 17,763
  • 5
  • 68
  • 136
  • Yes, thank you for pointing out the inverse is not always true. Also that range-case with the addition is fun. –  Sep 27 '12 at 20:13
3

I ran this code without an exception being thrown:

for (int x = Int16.MinValue; x < Int16.MaxValue; x++)
{
 float r = x;
 if (r != x)
 {
  throw new Exception("Failed at: " + x);
 }
}

Still waiting on (didn't complete this test because it took too long, never threw an exception though while running):

for (long x = Int64.MinValue; x < Int64.MaxValue; x++)
{
 float r = x;
 if (r != x)
 {
  throw new Exception("Failed at: " + x);
 }
}

Went back and ran your example with a caveat, this was the output:

[Exception: not equal 16777217 ?= 1.677722E+07 ?= 16777216]

for (int i = 0; i < int.MaxValue; i++)
{
 float f = i;
 if ((int)f != i) throw new Exception("not equal " + i + " ?= " + f + " ?= " + (int)f);
}
Travis J
  • 81,153
  • 41
  • 202
  • 273
  • Heh. Good luck waiting :) Guffa's answer should put your heart at rest. –  Sep 27 '12 at 20:14
  • @pst - Yeah, I stopped waiting lol, that might take a **very** long time for this machine to run. See my edit for an extended output of your cast back to int from float. – Travis J Sep 27 '12 at 20:17
0

My understanding of floating point arithmetic calculations is that they are handled by the CPU, which solely determines your precision. Therefore there is no definite value above which floats lose precision.

I had thought that the x86 architecture, for instance, guaranteed a minimum, but I've been proven wrong.

Nick Vaccaro
  • 5,428
  • 6
  • 38
  • 60
  • I'd be interested in hearing why I'm wrong, if I'm wrong. Downvote and escape! – Nick Vaccaro Sep 27 '12 at 20:16
  • 2
    The behavior of C# is specified by a standards document. A C# implementation must comply with the standard, regardless of the target processor model. If the processor does not provide instructions corresponding to the C# requirements, then the C# implementation must deliver the required results in some other way, either by using native instructions for the bulk of the work but “fixing up” results with additional tests and instructions or by emulating the floating-point operations at a bit level—whatever is necessary. Further, unlike C, C# specifies specific ranges for int and float. – Eric Postpischil Sep 27 '12 at 21:24
  • @EricPostpischil Float too, eh? Thanks for the knowledge drop! – Nick Vaccaro Sep 28 '12 at 12:47