Возвращает ли floor() что-то точно представимое?
-
22-07-2019 - |
Вопрос
В C89 функция floor() возвращает значение double.Гарантировано ли выполнение следующего?
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 всегда должен возвращать это точно представленное значение.
Решение
Все целые числа могут иметь точное представление с плавающей запятой, если ваш тип с плавающей запятой поддерживает требуемые биты мантиссы. Поскольку double
использует 53 бита для мантиссы, он может точно хранить все 32-битные int
. В конце концов, вы можете просто установить значение как мантисса с нулевым показателем.
Другие советы
Если результат floor() не совсем представим, каким, по-вашему, должно быть значение d ?Конечно, если у вас есть получил представление числа с плавающей запятой в переменной, тогда по определению оно точно представимо, не так ли?У тебя есть получил представление в 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
.Это не должно вызывать удивления - существует больше длинных значений, чем double (учитывая значения NaN), и множество double не являются целыми числами, поэтому мы не можем ожидать, что каждое long будет точно представимо.Однако все 32-битные целые числа являются представимо в виде двойников.
Я думаю, вы немного озадачены тем, что хотите спросить. floor (3 + 0.5)
не очень хороший пример, потому что 3, 0.5 и их сумма точно представлены в любом реальном формате с плавающей запятой. floor (0.1 + 0.9)
был бы лучшим примером, и реальный вопрос здесь не в том, является ли результат floor
точно представимым, а в том, неточность чисел перед вызовом floor
будет возвращено значение, отличное от ожидаемого, если бы все числа были точными. В этом случае я считаю, что ответ - да, но это во многом зависит от ваших конкретных чисел.
Я приглашаю других критиковать этот подход, если он плохой, но одним из возможных решений может быть умножение вашего числа на (1.0 + 0x1p-52)
или что-то похожее до вызова floor
(возможно, было бы лучше использовать nextafter
). Это может компенсировать случаи, когда ошибка в последнем двоичном месте числа приводит к тому, что она падает чуть ниже целого значения, а не точно, но не учитывает ошибки, которые накопились за несколько операций. Если вам нужен такой уровень числовой стабильности / точности, вам нужно либо провести глубокий анализ, либо использовать библиотеку произвольной точности или математической точности, которая может корректно обрабатывать ваши числа.