2014-12-16 3 views
21

У меня есть модульное тестирование, тестирование границы:Почему добавление double.epsilon к значению приводит к тому же значению, совершенно равному?

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

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

+1

Двойная точность - это неприятный бизнес, но при сравнении максимального отклонения между A и B это «Double.Epsilon», поэтому вы, вероятно, не набросились на него очень и очень небольшим отрывом. – jessehouwing

+0

См. Http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre – Habib

+0

Интересная статья здесь: http://www.johndcook.com/blog/2012/01/05/double-epsilon-dbl_epsilon/ TL, DR - «Double.Epsilon» не так полезен, как вы думаете! » – AAT

ответ

25

the documentation of Double.Epsilon Per:

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

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

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

+1

В любой ситуации нет ни одного номера. 'double' имеет максимум 15 цифр точности, поэтому крошечное число, которое может быть добавлено к 1 и результат« другого »' двойника', не может быть добавлено к 1 000 000 и по-прежнему получит «другую» «двойную». –

+0

@ Мухаммад: Это вопрос сам по себе. См. Http://stackoverflow.com/questions/155378/how-to-alter-a-float-by-its-smallest-increment-or-close-to-it и http://stackoverflow.com/questions/14278248/find-the-float-just-below-a-value. –

1

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

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

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

  • ...
  • -100 -> + -0,1
  • -10 -> + -0,01
  • 0 -> + -0,001
  • 10 -> + -0,01
  • 100 -> + -0.1
  • ...

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

+1

«Double.Epsilon - это наименьшее заметное изменение в двойном номере» Не совсем верно - это самый маленький двойной _greater, чем 0_, который может быть представлен как «double».Нет значения 'double', которое можно вычесть из 1.0, что приведет к тому, что ответ будет равен« double.Epsilon » –

+1

@DStanley: Спасибо, добавлено разъяснение. Я никогда не хотел объяснять эту часть, просто хотел уделить внимание читателям этой теме. – quetzalcoatl

21

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

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

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

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

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

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

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

+1

@ BrainSlugs83: Где вы видите 'double.Epsilon == 0d', возвращающий true? Это не для меня. –

+0

вы правы! - Я клянусь, что это произошло минуту назад в тесте Console.WriteLine, но это определенно не сейчас - слишком поздно, чтобы отредактировать этот комментарий, поэтому я удалил его - не хочу никого путать. Двойная проверка [IEE754] (https://en.wikipedia.org/wiki/IEEE_754-1985#Comparing_floating-point_numbers) прямо сейчас, чтобы получить голову прямо. – BrainSlugs83

1

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