Как изменить поплавок наименьшим шагом (или близким к нему)? - PullRequest
46 голосов
/ 01 октября 2008

У меня есть double значение f, и я хотел бы получить способ немного увеличить его (или уменьшить), чтобы получить новое значение, которое будет максимально приближено к исходному, но все же будет строго больше (или меньше чем) оригинал.

Это не должно быть близко к последнему биту - более важно, чтобы любое изменение, которое я делаю, гарантированно давало другое значение, а не возвращалось к исходному.

Ответы [ 7 ]

61 голосов
/ 01 октября 2008

Проверьте ваш файл math.h. Если вам повезет, у вас определены функции nextafter и nextafterf. Они делают именно то, что вам нужно, независимо от платформы и платформы, и являются частью стандарта C99.

Другой способ сделать это (может быть запасным решением) - разложить ваш поплавок на мантиссу и экспонентную часть. Увеличивать легко: просто добавьте один к мантиссе. Если вы получаете переполнение, вы должны справиться с этим, увеличивая показатель степени. Декрементирование работает так же.

РЕДАКТИРОВАТЬ : Как указано в комментариях, достаточно просто увеличить число с плавающей запятой в его двоичном представлении. Переполнение мантиссы будет увеличивать показатель степени, и это именно то, что мы хотим.

Это в двух словах то же самое, что делает nextafter.

Это не будет полностью переносимым, хотя. Вам придется иметь дело с порядком байтов и тем фактом, что не все машины имеют IEEE-числа (хорошо - последняя причина более академическая).

Также обработка NAN и бесконечности может быть немного сложнее. Вы не можете просто увеличивать их, поскольку они по определению не числа.

23 голосов
/ 01 октября 2008
u64 &x = *(u64*)(&f);
x++;

Да, серьезно.

Редактировать: Как кто-то указал, это не работает с -ve числами, Inf, Nan или переполнением должным образом. Более безопасная версия выше -

u64 &x = *(u64*)(&f);
if( ((x>>52) & 2047) != 2047 )    //if exponent is all 1's then f is a nan or inf.
{
    x += f>0 ? 1 : -1;
}
4 голосов
/ 01 октября 2008

В абсолютном выражении наименьшее количество, которое вы можете добавить к значению с плавающей запятой, чтобы создать новое отдельное значение, будет зависеть от текущей величины значения; это будет машина типа epsilon , умноженная на текущий показатель степени.

Проверьте IEEE spec для представления с плавающей запятой. Простейшим способом было бы переосмыслить значение как целочисленный тип, добавить 1, а затем проверить (если вам важно), что вы не перевернули знак или не сгенерировали NaN, изучив биты знака и экспоненты.

В качестве альтернативы, вы можете использовать frexp , чтобы получить текущую мантиссу и показатель степени и, следовательно, рассчитать добавляемое значение.

3 голосов
/ 01 октября 2008

Мне нужно было сделать то же самое и придумать этот код:

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;
}
1 голос
/ 01 октября 2008

Для чего бы это ни стоило, значение, для которого стандартное увеличение ++ перестает функционировать, составляет 9 007 199 254 740 992.

0 голосов
/ 01 октября 2008

Это может быть не совсем то, что вы хотите, но вы все равно можете найти numeric_limits в использовании. В частности, члены min () и epsilon ().

Я не верю, что что-то вроде mydouble + numeric_limits :: epsilon () будет делать то, что вы хотите, если mydouble уже близко к epsilon. Если это так, то вам повезло.

0 голосов
/ 01 октября 2008

Я нашел этот код некоторое время назад, возможно, он поможет вам определить наименьшее, что вы можете увеличить, а затем просто увеличить его на это значение. К сожалению, я не могу вспомнить ссылку на этот код:

#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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...