Пол () возвращает что-то, что точно представимо? - PullRequest
10 голосов
/ 13 января 2009

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

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

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

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

Ответы [ 3 ]

14 голосов
/ 13 января 2009

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

3 голосов
/ 13 января 2009

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

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

РЕДАКТИРОВАТЬ: Возможно, вы имели в виду «теоретический результат функции floor (), то есть наибольшее целочисленное значение, меньшее или равное аргументу, не может быть представлено как int». Это, конечно, правда. Простой способ показать это для системы, где int 32-битный:

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

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

РЕДАКТИРОВАТЬ: Есть и другие интересные ситуации, чтобы рассмотреть тоже. Вот некоторый код C # и результаты - я думаю, что по крайней мере подобные вещи будут происходить в C. В 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-битные целые числа представляются в виде двойных чисел.

2 голосов
/ 11 августа 2010

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

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

...