Добавление наименьшего поплавка в поплавок

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

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

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

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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


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

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

плавающая точка


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

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

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

Ответ 3

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

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

Ответ 4

Чтобы увеличить/уменьшить значение с плавающей запятой на минимально возможную величину, используйте 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

}