2010-02-26 11 views
8

Tengo una rutina simple que calcula la relación de aspecto a partir de un valor de punto flotante. Por lo tanto, para el valor 1.77777779, la rutina devuelve la cadena "16: 9". He probado esto en mi máquina y funciona bien.¿Por qué este cálculo de punto flotante da resultados diferentes en máquinas diferentes?

La rutina se da como:

public string AspectRatioAsString(float f) 
    { 
     bool carryon = true; 
     int index = 0; 
     double roundedUpValue = 0; 
     while (carryon) 
     { 
      index++; 
      float upper = index * f; 

      roundedUpValue = Math.Ceiling(upper); 

      if (roundedUpValue - upper <= (double)0.1 || index > 20) 
      { 
       carryon = false; 
      } 
     } 

     return roundedUpValue + ":" + index; 
    } 

Ahora en otra máquina, consigo resultados completamente diferentes. Entonces en mi máquina, 1.77777779 da "16: 9" pero en otra máquina obtengo "38:21".

+0

¿Alguna razón por la que está aprobando el asterisco como un flotador en lugar de un doble? –

+0

@Oded - lo siento, borré mi comentario, decidí promocionarlo a una respuesta. – ChrisF

+0

Consejo rápido: (doble) 0.1 es un poco desperdicio. Deberías reemplazarlo por 0.1 (que por defecto es doble) o 0.1d si quieres ser explícito. No estoy seguro de si el compilador lo convierte en un doble, pero esto podría ahorrarle un molde ... –

Respuesta

24

Aquí hay un fragmento interesante de la especificación C#, de la sección 4.1.6:

operaciones de coma flotante pueden realizaron con mayor precisión que el tipo de resultado de la operación. Para ejemplo, algunas arquitecturas de hardware apoyan una o escribe de punto flotante “extendida” “de largo doble” con una mayor gama y precisión que el tipo doble, e implícitamente realizar todas las operaciones de punto flotante utilizando este tipo superior de precisión . Sólo al coste excesivo en el rendimiento se puede hacer este tipo de arquitecturas hardware para realizar operaciones de punto flotante con menos precisión, y en lugar de requiere una implementación que renunciar a tanto el rendimiento y la precisión, C# permite un mayor tipo de precisión que deben utilizado para todas las operaciones de punto flotante . Además de entregar más resultados precisos de , esto raramente tiene ningún efecto mensurable .

Es posible que este sea uno de los "efectos medibles" gracias a esa llamada a Techo. Tomando el techo de un número de punto flotante, como han notado otros, aumenta una diferencia de 0.000000002 en nueve órdenes de magnitud porque convierte 15.99999999 en 16 y 16.00000001 en 17. Dos números que difieren ligeramente antes de la operación difieren enormemente después; la pequeña diferencia puede explicarse por el hecho de que diferentes máquinas pueden tener más o menos "precisión extra" en sus operaciones de punto flotante.

algunos temas relacionados:

Para abordar el problema concreto de cómo calcular una relación de aspecto de un flotador: Me posiblemente resolver esto de una manera completamente diferente. Me gustaría hacer una tabla como la siguiente:

struct Ratio 
{ 
    public int X { get; private set; } 
    public int Y { get; private set; } 
    public Ratio (int x, int y) : this() 
    { 
     this.X = x; 
     this.Y = y; 
    } 
    public double AsDouble() { return (double)X/(double)Y; } 
} 

Ratio[] commonRatios = { 
    new Ratio(16, 9), 
    new Ratio(4, 3), 
    // ... and so on, maybe the few hundred most common ratios here. 
    // since you are pinning results to be less than 20, there cannot possibly 
    // be more than a few hundred. 
}; 

y ahora su implementación es

public string AspectRatioAsString(double ratio)  
{ 
    var results = from commonRatio in commonRatios 
        select new { 
         Ratio = commonRatio, 
         Diff = Math.Abs(ratio - commonRatio.AsDouble())}; 

    var smallestResult = results.Min(x=>x.Diff); 

    return String.Format("{0}:{1}", smallestResult.Ratio.X, smallestResult.Ratio.Y); 
} 

Aviso cómo el código ahora se parece mucho a la operación que está intentando realizar: a partir de esta lista de común ratios, elija el que minimice la diferencia entre la relación dada y la razón común.

7

No utilizaría los números de coma flotante a menos que realmente tuviera que hacerlo. Son demasiado propensos a este tipo de cosas debido a errores de redondeo.

¿Se puede cambiar el código para que funcione en doble precisión? (decimal sería excesivo). Si haces esto, ¿brinda resultados más consistentes?

En cuanto a por qué es diferente en diferentes máquinas, ¿cuáles son las diferencias entre las dos máquinas?

  • 32 bit vs 64 bit?
  • Windows 7 vs Vista vs XP?
  • procesador Intel vs AMD? (gracias Oded)

Algo como esto podría ser la causa.

+0

@Chris: sí, una es una máquina de 64 bits y la otra donde los resultados no son correctos es una máquina de 32 bits. windows 7, 32 bit es windows xp. Ambos son procesadores intel. Cambiaré a double y usaré math.Round como sugirieron otros. –

+0

@JD - el 32bit frente al 64bit podría explicar el problema, con el 32bit siendo menos preciso. – ChrisF

+0

@Chris: Lo que es realmente extraño en la máquina de 32 bits de otro desarrollador, ejecutar la rutina a través de la depuración (en el IDE), el resultado es correcto. Sin embargo, fuera del IDE los resultados son incorrectos. –

5

Pruebe Math.Round en lugar de Math.Ceiling. Si terminas con 16.0000001 y redondeas, descartarás esa respuesta incorrectamente.

Varios Otras propuestas:

  • dobles son mejores que los flotadores.
  • (double) 0.1 elenco no es necesario.
  • Podría querer lanzar una excepción si no puede averiguar cuál es la relación de aspecto.
  • Si regresa inmediatamente después de encontrar la respuesta, puede deshacerse de la variable carryon.
  • Una comprobación quizás más precisa sería calcular la relación de aspecto para cada conjetura y compararla con la entrada.

Revisada (no probado):

public string AspectRatioAsString(double ratio) 
{ 
    for (int height = 1; height <= 20; ++height) 
    { 
     int width = (int) Math.Round(height * ratio); 
     double guess = (double) width/height; 

     if (Math.Abs(guess - ratio) <= 0.01) 
     { 
      return width + ":" + height; 
     } 
    } 

    throw ArgumentException("Invalid aspect ratio", "ratio"); 
} 
2

Cuando el índice es de 9, que se puede esperar para obtener algo así como superior = 16.0000001 15.9999999 o superior =. El que obtenga dependerá del error de redondeo, que puede diferir en diferentes máquinas. Cuando es 15.999999, roundedUpValue - upper <= 0.1 es verdadero y el ciclo finaliza. Cuando es 16.0000001, roundedUpValue - upper <= 0.1 es falso y el ciclo continúa hasta que llega al index > 20.

En su lugar, tal vez deberías intentar redondear el número entero superior al más cercano y verificar si el valor absoluto de su diferencia de ese número entero es pequeño. En otras palabras, use algo como if (Math.Abs(Math.Round(upper) - upper) <= (double)0.0001 || index > 20)

+0

Gracias. Solo probando ahora. Debería haber sido un poco más cuidadoso y haberlo probado más a fondo. –

Cuestiones relacionadas