2013-03-11 5 views
21

Как часть единичного теста, мне нужно проверить некоторые граничные условия. Один метод принимает аргумент System.Double.Получить следующий минимальный двойной номер

Есть ли способ получить следующее-самое маленькое двойное значение? (т. е. уменьшение мантиссы на 1 единицу)?

Я рассматривал использование Double.Epsilon, но это ненадежно, так как это только наименьшая дельта от нуля, и поэтому не работает для больших значений (т. Е. 9999999999 - Double.Epsilon == 9999999999).

Так что алгоритм или код, необходимый, например, что:

NextSmallest(Double d) < d 

... всегда верно.

+0

Как насчет того, если вы просто разделите на 10 – Hogan

+4

Думаю, на ваш вопрос был дан ответ: http://stackoverflow.com/a/2283565/1715579. –

ответ

14

Если ваши номера являются конечными, вы можете использовать несколько удобных методов в 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 из комбинации экспоненты/мантиссы, вы получите все, и вам придется заимствовать из битов экспоненты. Другими словами: вы должны уменьшить экспоненту, что мы и хотим.

+0

Спасибо, я закончил тем, что использовал этот подход, и это сработало для меня. – Dai

+0

А как насчет' NaN'? Нам не нужен специальный случай для них? – m93a

3

Страница Википедии о двойной точности с плавающей точкой здесь: http://en.wikipedia.org/wiki/Double_precision_floating-point_format

Для удовольствия я написал код, чтобы вырваться из двоичного представления формата double, вычитает мантиссы и recomposes полученного двойной. Из-за неявного бита в мантиссе мы должны проверить его и соответственно изменить экспоненту, и он может выйти из строя вблизи пределов.

Вот код:

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 оценивается как ложное, хотя и существует реальная разница, как показано выше :)

+2

Я бы предложил изменить строку форматирования на '' {0: r}, {1: r}, {2: r} "', чтобы распечатать дубликаты в формате «roundtrippable», который покажет разницу. Он печатает: «0.33333333333333331, 0.33333333333333326, 5.5511151231257827E-17 " – porges

+0

Спасибо @Porges, я исправлю ответ :) – Corey

Смежные вопросы