c# floating-point equality

¿Está garantizado que "(flotante) entero== entero" será igual en C#?



floating-point equality (5)

Al comparar un int y un float, el int se convierte implícitamente en un flotante. Esto garantiza que ocurra la misma pérdida de precisión, por lo que la comparación siempre será cierta. Mientras no perturbes el lanzamiento implícito o la aritmética, la igualdad debería mantenerse. Por ejemplo, si escribe esto:

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

hay un elenco implícito, por lo que es equivalente a esta función que siempre debe devolver verdadero:

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

pero si escribes esto:

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

entonces no hay más elenco implícito y la pérdida de precisión solo ocurre en el lado derecho. El resultado puede ser falso. Del mismo modo, si escribe esto:

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

entonces la pérdida de precisión podría no ser equivalente en ambos lados. El resultado puede ser falso.

Si bien "todos sabemos" que x == y puede ser problemático, donde y son valores de coma flotante, esta pregunta es un poco más específica:

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

Ahora, dado que el rango de float of es mucho más grande que el de enteros (pero la precisión es insuficiente para presentar números enteros únicos en los bordes), sería bueno que las respuestas a esta pregunta también aborden qué valores de x se pueden garantizar anteriormente. para, si se puede garantizar en absoluto.

Actualmente mi código está haciendo esta suposición (para valores relativamente pequeños de x) - Me gustaría asegurarme de que no me piquen :)

Esto fallará con "no igual: 16777217" (flotante de molde -> int):

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

Este código similar no fallará (solo int -> float); sin embargo, debido a la pérdida en la conversión, hay varios flotantes que pueden "igualar" el mismo número entero , y pueden representar un error silencioso:

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


Ejecuté este código sin una excepción lanzada:

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

Todavía estoy esperando (no completó esta prueba porque tardó demasiado, pero nunca lanzó una excepción).

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

Regresé y ejecuté su ejemplo con una advertencia, esta fue la salida:

[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); }


El siguiente experimento revela que la respuesta es que no tiene ese caso límite donde la igualdad no es verdadera

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; }

Parece razonable que las conversiones

float f = i;

y

if ((int)f != i)

debería seguir las mismas reglas Esto prueba que las conversiones int -> float y float -> int son una biyección.

NOTA: el código de experimento en realidad no prueba el caso de borde int.MaxValue porque el parámetro Parallel.For''s to es exclusivo, pero probé ese valor por separado y también pasa la prueba.


Mi comprensión de los cálculos aritméticos de coma flotante es que son manejados por la CPU, que solo determina su precisión. Por lo tanto, no existe un valor definido por encima del cual los flotadores pierden precisión.

Pensé que la arquitectura x86, por ejemplo, garantizaba un mínimo, pero me han demostrado que estaba equivocado.


Sí, la comparación siempre será cierta, independientemente del valor que tenga int .

El int se convertirá en un float para hacer la conversión, y la primera conversión en float siempre dará el mismo resultado que la segunda conversión.

Considerar:

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

Los valores de y y z siempre serán los mismos. Si la conversión pierde precisión, ambas conversiones perderán la precisión exactamente de la misma manera.

Si convierte el float nuevo a int en la comparación, eso es otro asunto.

Además, tenga en cuenta que incluso si un valor int específico convertido a float siempre da como resultado el mismo valor float , eso no significa que el valor float debe ser exclusivo para ese valor int . Hay valores int donde (float)x == (float)(x+1) sería true .