Почему добавление 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;
}
Это задание.
Ответы
Ответ 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
. Я не знаю, имеет ли С# какой-либо эквивалент, но если это так, я бы ожидал, что он будет иметь подобное имя.