¿Por qué añadir doble.epsilon a un resultado de valor en el mismo valor, perfectamente igual?


Tengo una prueba unitaria, límites de prueba:

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void CreateExtent_InvalidTop_ShouldThrowArgumentOutOfRangeException()
{
    var invalidTop = 90.0 + Double.Epsilon;
    new Extent(invalidTop, 0.0, 0.0, 0.0);
}

public static readonly double MAX_LAT = 90.0;

public Extent(double top, double right, double bottom, double left)
{
    if (top > GeoConstants.MAX_LAT)
        throw new ArgumentOutOfRangeException("top"); // not hit
}

Pensé que simplemente inclinaría el 90.0 sobre el borde agregando el mínimo posible doble positivo, pero ahora la excepción no se lanza, ¿alguna idea de por qué?

Al depurar, veo que la parte superior viene como 90, cuando debería ser 90.00000000.... algo.

EDITAR: Debería haber pensado un poco más, 90+Double.Epsilon perderá su resolución. Parece que la mejor manera de ir es hacer un poco cambiante.

SOLUCIÓN:

[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void CreateExtent_InvalidTop_ShouldThrowArgumentOutOfRangeException()
{
    var invalidTop = Utility.IncrementTiny(90); // 90.000000000000014
    // var sameAsEpsilon = Utility.IncrementTiny(0);
    new Extent(invalidTop, 0, 0, 0);
}

/// <summary>
/// Increment a double-precision number by the smallest amount possible
/// </summary>
/// <param name="number">double-precision number</param>
/// <returns>incremented number</returns>
public static double IncrementTiny(double number)
{
    #region SANITY CHECKS
    if (Double.IsNaN(number) || Double.IsInfinity(number))
        throw new ArgumentOutOfRangeException("number");
    #endregion

    var bits = BitConverter.DoubleToInt64Bits(number);

    // if negative then go opposite way
    if (number > 0)
        return BitConverter.Int64BitsToDouble(bits + 1);
    else if (number < 0)
        return BitConverter.Int64BitsToDouble(bits - 1);
    else
        return Double.Epsilon;
}

/// <summary>
/// Decrement a double-precision number by the smallest amount possible
/// </summary>
/// <param name="number">double-precision number</param>
/// <returns>decremented number</returns>
public static double DecrementTiny(double number)
{
    #region SANITY CHECKS
    if (Double.IsNaN(number) || Double.IsInfinity(number))
        throw new ArgumentOutOfRangeException("number");
    #endregion

    var bits = BitConverter.DoubleToInt64Bits(number);

    // if negative then go opposite way
    if (number > 0)
        return BitConverter.Int64BitsToDouble(bits - 1);
    else if (number < 0)
        return BitConverter.Int64BitsToDouble(bits + 1);
    else
        return 0 - Double.Epsilon;
}

Esto hace el trabajo.

Author: sprocket12, 2014-12-16

4 answers

Por la documentación de Double.Epsilon:

El valor de la propiedad Epsilon refleja el positivo más pequeño Double valor que es significativo en operaciones numéricas o comparaciones cuando el valor de la instancia Double es cero.

(Énfasis mío.)

Agregarlo a 90.0 no produce "el siguiente valor más pequeño después de 90.0", esto solo produce 90.0 nuevamente.

 26
Author: Jeroen Mostert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-16 14:10:49

Double.Epsilon es el valor representable positivo más pequeño. Solo porque es representable por sí solo no significa que sea el valor más pequeño entre cualquier otro valor representable y el siguiente más alto.

Imagine que tiene un sistema para representar sólo números enteros. Puede representar cualquier número entero a 5 figuras significativas, junto con una escala (por ejemplo, en el rango 1-100).

Así que estos valores son exactamente representables, por ejemplo

  • 12345 (dígitos=12345, escala = 0)
  • 12345000 (dígitos=12345, escala = 3)

En ese sistema, el valor "epsilon" sería 1... pero si agregas 1 a 12345000 aún terminarías con 12345000 porque el sistema no podría representar el resultado exacto de 12345001.

Ahora aplica la misma lógica a double, con todas sus complejidades, y obtienes un epsilon mucho más pequeño, pero el mismo principio general: un valor que es distinto de cero, pero aún puede terminar sin hacer ninguna diferencia cuando se agrega a mayor numero.

Tenga en cuenta que los valores mucho más grandes tienen la misma propiedad también - por ejemplo, si x es un double muy grande, entonces x + 1 bien puede ser igual a x porque el espacio entre dos dobles "adyacentes" se convierte en más de 2 a medida que los valores se hacen grandes.

 21
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-16 14:13:10

Porque Doble.Epsilon es el "cambio notable más pequeño" (vagamente hablando) en un número doble.

.. pero esto no significa que tendrá algún efecto cuando lo uses.

Como usted sabe, los flotadores/dobles varían en su resolución dependiendo de la magnitud del vlue que contienen. Por ejemplo, artificial:

  • ...
  • -100 -> +-0.1
  • -10 -> +-0.01
  • 0 -> +-0.001
  • 10 -> +-0.01
  • 100 -> +-0.1
  • ...

Si las resoluciones fueran así, el Epsilon sería 0.001, ya que es el cambio más pequeño posible. Pero, ¿cuál sería el resultado esperado de 1000000 + 0.001 en tal sistema?

 1
Author: quetzalcoatl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-16 14:19:33

En C99 y C++, la función que hace lo que estabas tratando de hacer se llama nextafter y está en math.h. No sé si C# tiene algún equivalente, pero si lo tiene, esperaría que tuviera un nombre similar.

 1
Author: zwol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-16 21:03:47