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

В 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 должны быть точно представлены как числа типа double, а floor всегда должен возвращать это точно представленное значение.


person Jim Hunziker    schedule 13.01.2009    source источник
comment
Также ознакомьтесь с stackoverflow.com/questions/12592249 /   -  person Kristian Spangsege    schedule 26.09.2012


Ответы (3)


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

person mmx    schedule 13.01.2009
comment
Мантисса с нулевым показателем степени не охватывает никаких чисел, кроме 2 ^ (смещение экспоненты). Я думаю, вы имели в виду, что можете просто обнулить биты, которые вносят вклад в дробную часть числа, которая гарантированно будет точно представима, если показатель степени ‹= 52 или около того. MSN MSN - person MSN; 13.01.2009
comment
МСН: Я говорил теоретически для простоты. Под экспонентой я имел в виду несмещенную, а не фактическую вещь, хранящуюся в битах экспоненты. По сути, если бы я говорил о фактических битах, double имел бы мантиссу 52 бита, а не 53;) - person mmx; 13.01.2009

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

(Кроме того, ответ Мердада верен для 32-битных int. В компиляторе с 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-битные целые числа могут быть представлены как двойные.

person Jon Skeet    schedule 13.01.2009

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

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

person R.. GitHub STOP HELPING ICE    schedule 11.08.2010
comment
Нет, думаю, люди правильно истолковали мой вопрос. Я хотел знать, гарантированно ли результат (целое число в двойном) для пола будет приведен как целое число, которое он должен представлять. Я понимаю, что математика с плавающей запятой может быть неточной, и если результат операции в круглых скобках немного отличается от ожидаемого, пол может вас удивить. - person Jim Hunziker; 12.08.2010