Как изменить значение числа с плавающей запятой на наименьшее значение (или близкое к нему)?

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

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

Двойник или поплавок? В зависимости от того, что у вас есть, наименьшее значение будет другим.

perimosocordiae 01.10.2008 02:34

Да, я понимаю, что заголовок и описание моего вопроса несовместимы. Я решил, что ответы могут касаться обоих случаев, что и делает принятый ответ.

Owen 01.10.2008 02:48
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
47
2
8 664
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

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

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

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

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

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

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

Вы конкретно НЕ хотите обрабатывать переполнение мантиссы, так как переполнение перейдет на показатель степени, который вам нужен.

Mike F 01.10.2008 02:40

Круто - никогда об этом не думал. Увеличение числа с плавающей запятой как целого числа сделает именно то, что нужно.

Nils Pipenbrinck 01.10.2008 02:47

Это круто :) А теперь может идиот, проголосовавший против моего ответа, сказав, пожалуйста, отменить его?

Mike F 01.10.2008 02:49

Как это будет работать, если приращение экспоненты перейдет в знаковый бит?

Greg Rogers 01.10.2008 02:51

Да, вам понадобятся особые случаи для inf и nan.

Mike F 01.10.2008 02:53

Я думаю, что к отрицательным значениям тоже нужно относиться иначе. Если вы увеличите их, результат будет более отрицательным, чем исходное значение. И, кстати, я просто дизассемблировал потом в реализации VS.NET 2008. Они делают намного больше, чем я ожидал.

Nils Pipenbrinck 01.10.2008 02:57

Нильс: Ах, это правда насчет -ve чисел.

Mike F 01.10.2008 03:08

Вам нужна дополнительная осторожность в районе 0 и -0.

David Heffernan 13.03.2015 23:52

В Visual C++ 2008 я нашел _nextafter() в float.h.

foraidt 21.05.2015 19:21
Исходный код GNU libc для nextafterf, and nextafter (с использованием только 32-битных целых чисел, поэтому он неуклюжий). These functions handle the +/-0.0 special case, negative floats, and going towards a given 2nd arg, not always +Inf. Remember, that's LGPLed code. Even though it's linked from SO, you can only copy-paste it into GPL-compatible projects.
Peter Cordes 15.02.2016 23:38

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;
}

Интересно, мог бы отрицательный голос прокомментировать, почему это не помогло ... Я, узнав о функции nextafter (), я бы предпочел ее, но если она сработает, то я считаю, что она заслуживает внимания сама по себе.

Owen 01.10.2008 02:47

Майк, какой включаемый файл / компилятор будет запускать этот запуск?

David Nehme 01.10.2008 02:55

Это полностью зависит от реализации и не переносится. Работает нормально, если у вас есть EEMMM, но если у вас есть MMMEE, вы не получите желаемого результата.

Benoit 01.10.2008 02:58

@David: Попробуйте заменить u64 на long long. @Benoit: да, он предполагает ieee754, я думаю, что в настоящее время это довольно безопасная ставка.

Mike F 01.10.2008 03:06

неопределенное поведение, нарушение строгих правил алиасинга

sellibitze 26.10.2009 19:36

@sellibitze: возможно, undefined, но на практике компиляторы C, которые не обрабатывают такого рода приведение типов, не смогут создавать реальный код. Так что это не проблема, если вы не довольны настройками оптимизации.

Zan Lynx 07.01.2010 03:06

не можете ли вы просто пропустить его через указатель void *(u64*)(void*)(&f), вы затем сообщаете компилятору, что знаете о проблемах с псевдонимом, и вы согласны с этим?

Matt Clarkson 12.12.2011 18:41

+1 При сборке x86-64 число с плавающей запятой будет храниться в регистре SSE. Тогда самый быстрый способ сделать это: _mm_cvtss_f32(_mm_castsi128_ps(_mm_add_epi32(_mm_castps_si12‌​8(_mm_set_ss(val)), _mm_set1_epi32(1)))). Обратите внимание, что cast / cvt / set_ss не генерирует никаких инструкций.

rasmus 30.01.2013 04:48

Это тоже не обрабатывает -0.

David Heffernan 13.03.2015 23:53

Это не совсем просто это. См. реализация nextafterf в glibc. Обратите внимание, как вам нужно уменьшить величину, когда x отрицателен и не равен нулю. (И обратите внимание, что целочисленные сравнения чисел FP сравнивают их как дополнение до 2, поэтому вы получите противоположный результат с двумя отрицательными числами). (реализация double nextafter то же самое, но более неуклюжий, потому что он использует только 32-битные целые числа.)

Peter Cordes 15.02.2016 18:29

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

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

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

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

#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);
}

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

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;
}

Как бы то ни было, значение, при котором стандартное приращение ++ перестает работать, составляет 9 007 199 254 740 992.

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

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

Другие вопросы по теме