Подтвердить что ты не робот

Получите следующий минимальный двойной номер

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

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

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

Итак, каков алгоритм или код, необходимые для:

NextSmallest(Double d) < d

... всегда истинно.

4b9b3361

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