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