Подтвердить что ты не робот

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

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

[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 по краю, добавив к нему минимально возможный положительный двойной, но теперь исключение не выбрасывается, любая идея почему?

При отладке я вижу, что вершина входит как 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;
}

Это задание.

4b9b3361

Ответ 1

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

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

(Подчеркните мой.)

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

Ответ 2

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

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

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

  • 12345 (цифры = 12345, шкала = 0)
  • 12345000 (цифры = 12345, шкала = 3)

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

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

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

Ответ 3

Поскольку Double.Epsilon является "наименьшим заметным изменением" (слабо говоря) в двойном числе.

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

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

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

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

Ответ 4

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