Возвращает ли floor() что-то точно представимое?

В C89 floor() возвращает double. Гарантировано ли следующее?

double d = floor(3.0 + 0.5);
int x = (int) d;
assert(x == 3);

Я забочусь о том, что результат пола не может быть точно представлен в IEEE 754. Так что d получает что-то вроде 2.99999, а х заканчивается на 2.

Чтобы ответить на этот вопрос как "да", все целые числа в пределах интервала int должны быть точно представлены как двойники, а пол должен всегда возвращать точно отображаемое значение.

Ответ 1

Все целые числа могут иметь точное представление с плавающей запятой, если ваш тип с плавающей точкой поддерживает требуемые биты мантиссы. Поскольку double использует 53 бит для мантиссы, он может точно хранить все 32-битные int. В конце концов, вы можете просто установить значение как мантисса с нулевым показателем.

Ответ 2

Если результат floor() не является точно представимым, что вы ожидаете от значения d? Конечно, если у вас есть представление числа с плавающей запятой в переменной, то по определению оно точно представимо, не так ли? У вас есть представление в d...

(Кроме того, ответ Mehrdad верен для 32-битных ints. В компиляторе с 64-битным двойным и 64-битным int у вас есть больше проблем, конечно...)

EDIT: Возможно, вы имели в виду "теоретический результат floor(), т.е. наибольшее целочисленное значение, меньшее или равное аргументу, не может быть представлено как int". Это, безусловно, так. Простой способ показать это для системы, где int - 32 бита:

int max = 0x7fffffff;
double number = max;
number += 10.0;
double f = floor(number);
int oops = (int) f;

Я не могу вспомнить, что делает C при конверсиях с плавающей запятой на целочисленное переполнение... но это произойдет здесь.

EDIT: Есть и другие интересные ситуации. Здесь некоторый код С# и результаты - я бы предположил, что, по крайней мере, подобные вещи произойдут в C. В С#, double определяется как 64 бит, а значит long.

using System;
class Test
{
    static void Main()
    {
        FloorSameInteger(long.MaxValue/2);
        FloorSameInteger(long.MaxValue-2);
    }

    static void FloorSameInteger(long original)
    {
        double convertedToDouble = original;
        double flooredToDouble = Math.Floor(convertedToDouble);
        long flooredToLong = (long) flooredToDouble;

        Console.WriteLine("Original value: {0}", original);
        Console.WriteLine("Converted to double: {0}", convertedToDouble);
        Console.WriteLine("Floored (as double): {0}", flooredToDouble);
        Console.WriteLine("Converted back to long: {0}", flooredToLong);
        Console.WriteLine();
    }
}

Результаты:

Исходное значение: 4611686018427387903
Конвертировано в двойное: 4.61168601842739E + 18
Напольный (как двойной): 4.61168601842739E + 18
Преобразован обратно в длинный: 4611686018427387904

Исходное значение: 9223372036854775805
Конвертировано в двойное: 9.22337203685478E + 18
Напольный (как двойной): 9.22337203685478E + 18
Преобразован обратно в длинный: -9223372036854775808

Другими словами:

(long) floor((double) original)

не всегда совпадает с original. Это не должно удивлять - более длинные значения, чем двойные (с учетом значений NaN), и много двойников не являются целыми числами, поэтому мы не можем ожидать, что каждый длинный будет точно представленным. Тем не менее, все 32-битные целые числа представляются в виде удвоений.

Ответ 3

Я думаю, вы немного смущены тем, что хотите спросить. floor(3 + 0.5) - не очень хороший пример, потому что 3, 0.5 и их сумма точно представляются в любом реальном формате с плавающей запятой. floor(0.1 + 0.9) был бы лучшим примером, и реальный вопрос здесь заключается не в том, является ли результат floor точно представимым, но будет ли неточность чисел до вызова floor возвратным значением, отличным от того, что вы бы ожидайте, если бы все цифры были точными. В этом случае я считаю, что ответ "да", но это зависит от ваших конкретных номеров.

Я предлагаю другим критиковать этот подход, если это плохо, но одним из возможных способов обхода может быть умножение вашего числа на (1.0+0x1p-52) или что-то подобное до вызова floor (возможно, использование nextafter было бы лучше). Это может компенсировать случаи, когда ошибка в последнем двоичном месте номера приводит к тому, что он падает ниже, а не точно на целочисленное значение, но он не учитывает ошибки, которые накопились в течение нескольких операций. Если вам нужен этот уровень числовой стабильности/точности, вам нужно либо провести глубокий анализ, либо использовать библиотеку с произвольной точностью или точной математикой, которая может правильно обрабатывать ваши номера.