Почему добавление double.epsilon к значению приводит к тому же значению, совершенно равному?

у меня есть unit-тест, границы тестирования:

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

Я думал, что просто опрокину 90.0 через край, добавив к нему минимально возможный положительный двойной, но теперь исключение не брошено, любая идея почему?

при отладке я вижу, что top входит как 90, когда он должен быть 90.00000000.... что-то.

EDIT: Я должен был подумать немного жестче,90+Double.Epsilon потеряет свое разрешение. Кажется, лучший способ пойти, это сделать немного меняющийся.

устранение:

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

Это делает работу.

4 ответов


Per документация Double.Epsilon:

значение свойства Epsilon отражает наименьшее положительное значение Double значение, значимое в числовых операциях или сравнениях , если значение Double экземпляр равен нулю.

(выделено мной.)

добавление его в 90.0 не приводит к "следующему наименьшему значению после 90.0", это просто дает 90.0 снова.


Double.Epsilon наименьшее положительное представимое значение. Только потому, что он представим сам по себе, не означает, что это наименьшее значение между любыми другое представимое значение и следующее Самое высокое.

представьте, что у вас есть система для представления только целых чисел. Вы можете представить любое целое число до 5 значащих цифр вместе со шкалой (например, в диапазоне 1-100).

таким образом, эти значения точно представимы, для пример

  • 12345 (цифры=12345, масштаб = 0)
  • 12345000 (цифры=12345, масштаб = 3)

в этой системе значение "Эпсилон" будет равно 1... но если вы добавите 1 к 12345000, вы все равно получите 12345000, потому что система не может представить точный результат 12345001.

теперь примените ту же логику к double, со всеми его тонкостями, и вы получаете гораздо меньший Эпсилон, но тот же общий принцип: значение, которое отличается от нуля, но все равно может не иметь никакого значения при добавлении к большим числам.

обратите внимание, что гораздо бОльшие значения имеют то же свойство - например, если x очень большой double, потом x + 1 вполне может быть равен x потому что разрыв между двумя" смежными " двойниками становится больше 2, поскольку значения становятся большими.


Потому Что Двойной.Эпсилон-это "наименьшее заметное изменение" (грубо говоря) в двойном количестве.

.. но это!--5-->не значит что он будет иметь любой эффект, когда вы его используете.

Как вы знаете, поплавки / двойники различаются по их разрешению в зависимости от величины vlue, который они содержат. Например, искусственный:

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

Если эти резолюции были такой, Эпсилон будет 0.001, Так как это наименьшее возможное изменение. Но каков был бы ожидаемый результат 1000000 + 0.001 в такой системе?


в C99 и C++ функция, которая делает то, что вы пытались сделать, называется nextafter и math.h. Я не знаю, имеет ли C# какой-либо эквивалент, но если это так, я ожидал бы, что у него будет аналогичное имя.