Неожиданное поведение Math.Floor(double) и Math.Ceiling(double)
Этот вопрос касается порога, при котором Math.Floor(double)
и Math.Ceiling(double)
решают дать вам предыдущее или следующее целочисленное значение. Меня беспокоило, что порог, похоже, не имеет ничего общего с Double.Epsilon
, который является наименьшим значением, которое может быть представлено двойным. Например:
double x = 3.0;
Console.WriteLine( Math.Floor( x - Double.Epsilon ) ); // expected 2, got 3
Console.WriteLine( Math.Ceiling( x + Double.Epsilon) ); // expected 4, got 3
Даже умножение Double.Epsilon
на справедливый бит не помогло:
Console.WriteLine( Math.Floor( x - Double.Epsilon*1000 ) ); // expected 2, got 3
Console.WriteLine( Math.Ceiling( x + Double.Epsilon*1000) ); // expected 4, got 3
С некоторыми экспериментами я смог определить, что порог где-то около 2.2E-16, что очень мало, но VASTLY больше, чем Double.Epsilon
.
Причина этого вопроса в том, что я пытался вычислить число цифр в числе с помощью формулы var digits = Math.Floor( Math.Log( n, 10 ) ) + 1
. Эта формула не работает для n=1000
(которую я случайно наткнулся случайно), потому что Math.Log( 1000, 10 )
возвращает число, равное 4.44E-16 от его фактического значения. (Позже я обнаружил, что встроенный Math.Log10(double)
обеспечивает гораздо более точные результаты.)
Если порог не должен быть привязан к Double.Epsilon
или, если нет, не должен быть документирован порог (я не мог найти упоминания об этом в официальной документации MSDN)?
Ответы
Ответ 1
Если порог не должен быть привязан к Double.Epsilon
Нет.
Представимые двойники неравномерно распределены по действительным числам. Ближе к нулю существует множество отображаемых значений. Но чем дальше от нуля вы получаете, тем дальше друг от друга представляются двойники. Для очень больших чисел даже добавление 1 к двойнику не даст вам нового значения.
Поэтому порог, который вы ищете, зависит от того, насколько велика ваша цифра. Это не константа.
Ответ 2
Значение Double.Epsilon
равно 4.94065645841247e-324. Добавление или вычитание этого значения до 3 результатов в 3, из-за того, как работает плавающая точка.
A double
имеет 53 бит мантиссы, поэтому наименьшее значение, которое вы можете добавить, которое будет иметь какое-либо влияние, будет примерно на 2 ^ 53 раз меньше вашей переменной. Так что вокруг примерно 1е-16 звучит примерно справа (порядок величины).
Итак, чтобы ответить на ваш вопрос: нет "порога"; floor
и ceil
просто действуют на свой аргумент точно так, как вы ожидали.
Ответ 3
Это будет размахивать руками, а не ссылками на технические характеристики, но я надеюсь, что мое "интуитивное объяснение" вам подходит.
Epsilon представляет наименьшую величину, которая может быть представлена, отличная от нуля. Учитывая мантиссою и показателем двойника, это будет крайне крошечным, - подумайте 10 ^ -324. Там находится более трехсот нулей между десятичной точкой и первой ненулевой цифрой.
Однако Double
представляет примерно 14-15 цифр точности. Это все еще оставляет 310 цифр нулей между Epsilon и целыми числами.
Double
фиксируются на определенную длину бита. Если вам действительно нужны произвольные вычисления точности, вместо этого вы должны использовать библиотеку с произвольной точностью. И будьте готовы к тому, чтобы он был значительно медленнее - для представления всех 325 цифр, которые необходимы для хранения числа, такого как 2+epsilon
, потребуется примерно в 75 раз больше места на диске. Это хранилище не является бесплатным, и вычисление с ним, конечно, не может идти с полной скоростью процессора.