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





Проверьте свой файл math.h. Если вам повезло, у вас есть определенные функции nextafter и nextafterf. Они делают именно то, что вы хотите, портативно и независимо от платформы и являются частью стандарта C99.
Другой способ сделать это (может быть запасным решением) - разложить поплавок на мантиссу и экспоненту. Увеличить легко: просто добавьте единицу к мантиссе. Если вы получаете переполнение, вы должны справиться с этим, увеличивая показатель степени. Декремент работает точно так же.
РЕДАКТИРОВАТЬ: Как указано в комментариях, достаточно просто увеличить число с плавающей запятой в его двоичном представлении. Переполнение мантиссы увеличит показатель степени, и это именно то, что мы хотим.
В двух словах, это то же самое, что и nextafter.
Однако это не будет полностью переносимым. Вам придется иметь дело с порядком байтов и тем фактом, что не все машины имеют IEEE float (хорошо - последняя причина более академическая).
Также обработка NAN и бесконечных чисел может быть немного сложной. Вы не можете просто увеличить их, поскольку они по определению не являются числами.
Вы конкретно НЕ хотите обрабатывать переполнение мантиссы, так как переполнение перейдет на показатель степени, который вам нужен.
Круто - никогда об этом не думал. Увеличение числа с плавающей запятой как целого числа сделает именно то, что нужно.
Это круто :) А теперь может идиот, проголосовавший против моего ответа, сказав, пожалуйста, отменить его?
Как это будет работать, если приращение экспоненты перейдет в знаковый бит?
Да, вам понадобятся особые случаи для inf и nan.
Я думаю, что к отрицательным значениям тоже нужно относиться иначе. Если вы увеличите их, результат будет более отрицательным, чем исходное значение. И, кстати, я просто дизассемблировал потом в реализации VS.NET 2008. Они делают намного больше, чем я ожидал.
Нильс: Ах, это правда насчет -ve чисел.
Вам нужна дополнительная осторожность в районе 0 и -0.
В Visual C++ 2008 я нашел _nextafter() в float.h.
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.
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 (), я бы предпочел ее, но если она сработает, то я считаю, что она заслуживает внимания сама по себе.
Майк, какой включаемый файл / компилятор будет запускать этот запуск?
Это полностью зависит от реализации и не переносится. Работает нормально, если у вас есть EEMMM, но если у вас есть MMMEE, вы не получите желаемого результата.
@David: Попробуйте заменить u64 на long long. @Benoit: да, он предполагает ieee754, я думаю, что в настоящее время это довольно безопасная ставка.
неопределенное поведение, нарушение строгих правил алиасинга
@sellibitze: возможно, undefined, но на практике компиляторы C, которые не обрабатывают такого рода приведение типов, не смогут создавать реальный код. Так что это не проблема, если вы не довольны настройками оптимизации.
не можете ли вы просто пропустить его через указатель void *(u64*)(void*)(&f), вы затем сообщаете компилятору, что знаете о проблемах с псевдонимом, и вы согласны с этим?
+1 При сборке x86-64 число с плавающей запятой будет храниться в регистре SSE. Тогда самый быстрый способ сделать это: _mm_cvtss_f32(_mm_castsi128_ps(_mm_add_epi32(_mm_castps_si128(_mm_set_ss(val)), _mm_set1_epi32(1)))). Обратите внимание, что cast / cvt / set_ss не генерирует никаких инструкций.
Это тоже не обрабатывает -0.
Это не совсем просто это. См. реализация nextafterf в glibc. Обратите внимание, как вам нужно уменьшить величину, когда x отрицателен и не равен нулю. (И обратите внимание, что целочисленные сравнения чисел FP сравнивают их как дополнение до 2, поэтому вы получите противоположный результат с двумя отрицательными числами). (реализация double nextafter то же самое, но более неуклюжий, потому что он использует только 32-битные целые числа.)
В абсолютном выражении наименьшая сумма, которую вы можете добавить к значению с плавающей запятой, чтобы сделать новое уникальное значение, будет зависеть от текущей величины значения; это будет машина эпсилон типа, умноженное на текущий показатель степени.
Проверьте Спецификация 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. Если да, то вам повезло.
Двойник или поплавок? В зависимости от того, что у вас есть, наименьшее значение будет другим.