Как изменить поплавок на наименьший приращение (или близко к нему)?
У меня есть значение double
f
и мне хотелось бы немного подтолкнуть его немного больше (или меньше), чтобы получить новое значение, которое будет как можно ближе к оригиналу, но все же строго больше (или меньше) оригинала.
Он не должен быть близок к последнему биту - более важно, что любое изменение, которое я делаю, гарантированно производит другое значение, а не назад к оригиналу.
Ответы
Ответ 1
Проверьте файл math.h. Если вам повезет, у вас есть nextafter
и nextafterf
. Они делают именно то, что вы хотите, с помощью портативного и независимого от платформы способа и являетесь частью стандарта C99.
Еще один способ сделать это (может быть откатным решением) - разложить ваш float на часть мантиссы и экспонента. Приращение легко: просто добавьте его к мантиссе. Если вы получаете переполнение, вам нужно справиться с этим, увеличивая показатель экспоненты. Decrementing работает одинаково.
EDIT. Как указано в комментариях, достаточно просто увеличить приращение в нем двоичного представления. Мантисса-переполнение увеличит экспоненту, и это именно то, что мы хотим.
Это в двух словах то же самое, что и в дальнейшем.
Это не будет полностью переносимым. Вам придется иметь дело с endianess и тем фактом, что не все машины имеют IEEE-поплавки (ok - последняя причина более академична).
Также обработка NAN и бесконечности может быть немного сложной. Вы не можете просто увеличивать их, поскольку они по определению не являются числами.
Ответ 2
u64 &x = *(u64*)(&f);
x++;
Да, серьезно.
Изменить: Как заметил кто-то, это не касается должностей -ve, Inf, Nan или переполнения. Более безопасная версия выше
u64 &x = *(u64*)(&f);
if( ((x>>52) & 2047) != 2047 ) //if exponent is all 1 then f is a nan or inf.
{
x += f>0 ? 1 : -1;
}
Ответ 3
В абсолютном выражении наименьшая сумма, которую вы можете добавить к значению с плавающей запятой для создания нового отдельного значения, будет зависеть от текущей величины значения; это будет тип машина epsilon, умноженная на текущий показатель.
Ознакомьтесь с спецификацией IEEE для представления с плавающей запятой. Самый простой способ - переосмыслить значение как целочисленный тип, добавить 1, а затем проверить (если вам интересно), что вы не перевернули знак или не создали NaN, изучив биты знака и экспоненты.
В качестве альтернативы вы можете использовать frexp для получения текущей мантиссы и экспоненты и, следовательно, вычислить значение для добавления.
Ответ 4
Мне нужно было сделать то же самое и придумал этот код:
double DoubleIncrement(double value)
{
int exponent;
double mantissa = frexp(value, &exponent);
if(mantissa == 0)
return DBL_MIN;
mantissa += DBL_EPSILON/2.0f;
value = ldexp(mantissa, exponent);
return value;
}
Ответ 5
Для чего это стоит, значение, для которого стандартное приращение ++ перестает функционировать, составляет 9 007 199,254,740,992.
Ответ 6
Это может быть не совсем то, что вы хотите, но вы все равно можете найти numeric_limits. В частности, члены min() и epsilon().
Я не считаю, что что-то вроде mydouble + numeric_limits:: epsilon() будет делать то, что вы хотите, если mydouble уже близко к epsilon. Если да, то вам повезло.
Ответ 7
Я нашел этот код некоторое время назад, возможно, это поможет вам определить наименьшее, что вы можете подтолкнуть к нему, а затем просто увеличите его на это значение. К сожалению, я не могу вспомнить ссылку для этого кода:
#include <stdio.h>
int main()
{
/* two numbers to work with */
double number1, number2; // result of calculation
double result;
int counter; // loop counter and accuracy check
number1 = 1.0;
number2 = 1.0;
counter = 0;
while (number1 + number2 != number1) {
++counter;
number2 = number2 / 10;
}
printf("%2d digits accuracy in calculations\n", counter);
number2 = 1.0;
counter = 0;
while (1) {
result = number1 + number2;
if (result == number1)
break;
++counter;
number2 = number2 / 10.0;
}
printf("%2d digits accuracy in storage\n", counter );
return (0);
}