Добавление наименьшего поплавка в поплавок
Я хочу добавить наименьшее возможное значение 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
}