2016-12-24 4 views
59

Я хочу добавить наименьшее возможное значение поплавка в поплавок. Так, к примеру, я попытался сделать это, чтобы получить 1,0 + наименьшее возможное поплавок:Добавление наименьшего поплавка в поплавок

float result = 1.0f + std::numeric_limits<float>::min(); 

Но после того, как сделать это, я получаю следующие результаты:

(result > 1.0f) == false 
(result == 1.0f) == true 

Я использую Visual Studio 2015 Почему это происходит? Что я могу сделать, чтобы обойти это?

+27

Почему вы удивлены? Вы добавляете min, а не epsilon. –

+6

Я не понимал, что есть разница! Я предположил, что они всегда эквивалентны. Спасибо, это было полезно. – Squidy

+1

@Matteo Ответ? У меня нет действительно большой причины для этого вопроса. –

ответ

86

Если вы хотите следующее представимое значение после 1, для заголовка std::nextafter есть функция из заголовка <cmath>.

float result = std::nextafter(1.0f, 2.0f); 

Он возвращает следующее представимое значение, начиная с первого аргумента в направлении второго аргумента. Так что если вы хотите, чтобы найти следующее значение ниже 1, вы можете сделать это:

float result = std::nextafter(1.0f, 0.0f); 

Добавление наименьшее положительное представимое значение 1 не работает, потому что разница между 1 и следующей представимому значение больше, чем разница между 0 и следующим представляемым значением.

+18

'std :: numeric_limits :: min()' не является наименьшим положительным представимым значением; это наименьшая положительная нормализованная величина, поэтому субнормальные значения могут быть ниже. – user2357112

+2

IIRC, приблизительно половина всех битовых шаблонов с плавающей запятой представляет собой число с величиной меньше 1.0. Диапазон поля экспоненты более или менее сосредоточен вокруг '0' (представляющий множитель' 2^0 = 1.0' для мантиссы), после учета смещения в способе кодирования, который делает сортировку битовых шаблонов FP целыми числами фактически произведение. См. Превосходную серию статей Брюса Доусона о странных вещах с плавающей запятой, в том числе [об этом представлении] (https://randomascii.wordpress.com/2012/01/11/tricks-with-the-floating-point-format /) –

+0

См. [Эту статью] (https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/) для оглавления в этой серии статей FP. –

20

min является наименьшим ненулевое значение, что (нормированная-форма) с плавающей точкой можно предположить, то есть что-то около 2 -126 (-126 это минимально допустимый показатель для поплавка); теперь, если вы суммируете его на 1, вы все равно получите 1, так как float имеет всего 23 бита мантиссы, поэтому такое небольшое изменение не может быть представлено в таком «большом» номере (вам понадобится 126-битная мантисса, чтобы увидеть изменение суммирования 2 -126 до 1).

минимально возможное изменение 1, вместо этого, является epsilon (так называемый машинный ноль), который фактически 2 -23 - как это влияет на последний бит мантиссы.

+2

'std :: numeric_limits :: min()' является наименьшим положительным * нормированным * значением. Субнормальные явления могут быть ниже. – user2357112

+0

@ user2357112: Я должен добавить предостережение в мой профиль, в котором говорится: «любое обсуждение, которое я делаю о плавающей запятой, выполняется без учета денормализованных чисел, которые безошибочно игнорируются», - –

+2

Отсутствие субнормальных значений еще более уродливое. С доступными субнормалами вычитание двух неравных чисел всегда будет давать ненулевой ответ. Без субнормальных данных это не будет. – plugwash

40

«Проблема», которую вы наблюдаете, вызвана самой природой с арифметикой с плавающей запятой.

В FP точность зависит от масштаба; вокруг значения 1.0 точность не позволяет различать 1.0 и 1.0+min_representable, где min_representable - наименьшее возможное значение, большее нуля (даже если мы рассматриваем только наименьшее нормализованное число, std::numeric_limits<float>::min(). Наименьшая денормальность - это еще несколько порядков величины меньше).

Например, с числами с плавающей точкой двойной точности 64-разрядные IEEE754, по шкале x=10000000000000000 (10), что невозможно провести различие между x и x+1.


Тот факт, что изменения разрешения с масштабом является причиной для названия «плавающей точкой», потому что десятичная точка «плавает». Вместо этого фиксированное представление точки будет иметь фиксированное разрешение (например, с 16 двоичными цифрами ниже единиц у вас есть точность 1/65536 ~ 0,00001).

Например, в 32-битном формате с плавающей запятой IEEE754 есть один бит для знака, 8 бит для экспоненты и 31 бита для мантиссы:

floating point


Наименьшее значение eps, так что 1.0f + eps != 1.0f доступен в виде предопределенной константы как FLT_EPSILON, или std::numeric_limits<float>::epsilon. См. Также machine epsilon on Wikipedia, в котором обсуждается, как epsilon относится к ошибкам округления.

I.e. epsilon - это наименьшее значение, которое делает то, что вы ожидаете здесь, делая разницу при добавлении к 1.0.

Более общая версия этого (для чисел, отличных от 1.0) называется 1 единицей на последнем месте (мантиссы). См. Статью ULP article Википедии.

+5

Я полагаю, что проблема связана с людьми, использующими слово «плавающая точка» (или просто «float») для «нецелочисленного числа на компьютере», не учитывая (или даже не зная о) фактического плавающего характера (т. Е. Точность зависит от по шкале). –

+0

Исправить. Если кто-то собирается делать большую часть такого рода вещей, было бы неплохо потратить некоторое время на изучение концепций, стоящих за плавающей точкой. Существует ряд «неожиданных» эффектов, которые могут возникнуть, особенно для незнакомого пользователя. –

+2

** 'eps' - неправильное имя для FLT_MIN **. 'eps' является коротким для' FLOAT_EPSILON', который является [наименьшим числом, которое имеет значение при добавлении к '1.0'] (https://en.wikipedia.org/wiki/Machine_epsilon#Variant_definitions). Это 1 единица на последнем месте (мантиссы) для '1.0' (см. [Ulp] (https://en.wikipedia.org/wiki/Unit_in_the_last_place)). То, что вы описываете, - это концепция epsilon и 1 ULP, но проблема заключается в том, что «eps = наименьшее возможное значение больше нуля». –

5

Чтобы увеличить/уменьшить значение с плавающей запятой на минимально возможную сумму, используйте nextafter в направлении +/- infinity().

Если вы используете next_after(x,std::numeric_limits::max()), результат будет неправильным в случае, если x - бесконечность.

#include <iostream> 
#include <limits> 
#include <cmath> 

template<typename T> 
T next_above(const T& v){ 
    return std::nextafter(1.0,std::numeric_limits<T>::infinity()) ; 
} 
template<typename T> 
T next_below(const T& v){ 
    return std::nextafter(1.0,-std::numeric_limits<T>::infinity()) ; 
} 

int main(){ 
    std::cout << next_below(1.0) - 1.0<< std::endl; // gives eps 
    std::cout << next_above(1.0) - 1.0<< std::endl; // gives ~ -eps/2 

    // Note: 
    std::cout << std::nextafter(std::numeric_limits<double>::infinity(), 
    std::numeric_limits<double>::infinity()) << std::endl; // gives inf 
    std::cout << std::nextafter(std::numeric_limits<double>::infinity(), 
    std::numeric_limits<double>::max()) << std::endl; // gives 1.79769e+308 

} 
Смежные вопросы