Получите следующий минимальный двойной номер
Как часть unit test, мне нужно проверить некоторые граничные условия. Один метод принимает аргумент System.Double
.
Есть ли способ получить следующее минимальное двойное значение? (т.е. уменьшение мантиссы на 1 единицу)?
Я рассматривал использование Double.Epsilon
, но это ненадежно, так как это только наименьшая дельта от нуля и поэтому не работает для больших значений (т.е. 9999999999 - Double.Epsilon == 9999999999
).
Итак, каков алгоритм или код, необходимые для:
NextSmallest(Double d) < d
... всегда истинно.
Ответы
Ответ 1
Если ваши номера конечны, вы можете использовать несколько удобных методов в классе BitConverter
:
long bits = BitConverter.DoubleToInt64Bits(value);
if (value > 0)
return BitConverter.Int64BitsToDouble(bits - 1);
else if (value < 0)
return BitConverter.Int64BitsToDouble(bits + 1);
else
return -double.Epsilon;
Форматы IEEE-754 были сконструированы таким образом, что биты, составляющие показатель экспоненты и мантиссы, образуют целое число, которое имеет тот же порядок, что и числа с плавающей запятой. Таким образом, чтобы получить наибольшее меньшее число, вы можете вычесть его из этого числа, если значение положительное, и вы можете добавить его, если значение отрицательное.
Основная причина этого заключается в том, что ведущий бит мантиссы не сохраняется. Если ваша мантисса - это все нули, то ваш номер равен двум. Если вы вычтите 1 из комбинации экспоненты/мантиссы, вы получите все, и вам придется заимствовать из битов экспоненты. Другими словами: вам нужно уменьшить экспоненту, что именно мы хотим.
Ответ 2
Страница Википедии о плавающей запятой с двойной точностью находится здесь: http://en.wikipedia.org/wiki/Double_precision_floating-point_format
Для удовольствия я написал некоторый код, чтобы разбить двоичное представление формата double
, уменьшает мантиссу и результирует результирующий двойной. Из-за неявного бита в мантиссе мы должны проверить его и соответственно изменить экспоненту, и он может выйти из пределов.
Здесь код:
public static double PrevDouble(double src)
{
// check for special values:
if (double.IsInfinity(src) || double.IsNaN(src))
return src;
if (src == 0)
return -double.MinValue;
// get bytes from double
byte[] srcbytes = System.BitConverter.GetBytes(src);
// extract components
byte sign = (byte)(srcbytes[7] & 0x80);
ulong exp = ((((ulong)srcbytes[7]) & 0x7F) << 4) + (((ulong)srcbytes[6] >> 4) & 0x0F);
ulong mant = ((ulong)1 << 52) | (((ulong)srcbytes[6] & 0x0F) << 48) | (((ulong)srcbytes[5]) << 40) | (((ulong)srcbytes[4]) << 32) | (((ulong)srcbytes[3]) << 24) | (((ulong)srcbytes[2]) << 16) | (((ulong)srcbytes[1]) << 8) | ((ulong)srcbytes[0]);
// decrement mantissa
--mant;
// check if implied bit has been removed and shift if so
if ((mant & ((ulong)1 << 52)) == 0)
{
mant <<= 1;
exp--;
}
// build byte representation of modified value
byte[] bytes = new byte[8];
bytes[7] = (byte)((ulong)sign | ((exp >> 4) & 0x7F));
bytes[6] = (byte)((((ulong)exp & 0x0F) << 4) | ((mant >> 48) & 0x0F));
bytes[5] = (byte)((mant >> 40) & 0xFF);
bytes[4] = (byte)((mant >> 32) & 0xFF);
bytes[3] = (byte)((mant >> 24) & 0xFF);
bytes[2] = (byte)((mant >> 16) & 0xFF);
bytes[1] = (byte)((mant >> 8) & 0xFF);
bytes[0] = (byte)(mant & 0xFF);
// convert back to double and return
double res = System.BitConverter.ToDouble(bytes, 0);
return res;
}
Все из которых дает вам значение, отличное от начального значения, с помощью изменения младшего бита мантиссы... в теории:)
Здесь тест:
public static Main(string[] args)
{
double test = 1.0/3;
double prev = PrevDouble(test);
Console.WriteLine("{0:r}, {1:r}, {2:r}", test, prev, test - prev);
}
Дает следующие результаты на моем ПК:
0.33333333333333331, 0.33333333333333326, 5.5511151231257827E-17
Разница есть, но, вероятно, ниже порога округления. Выражение test == prev
оценивается как false, хотя и существует реальная разница, как показано выше:)