Почему std :: floor (1 - std :: numeric_limits & lt; float & gt; :: min ()) оценивается в 1 [дубликат]

здесь реализация Swift из ответа

func degreesToRadians(degrees: Double) -> Double {
    return degrees * Double.pi / 180
}

func distanceInKmBetweenEarthCoordinates(lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> Double {

    let earthRadiusKm: Double = 6371

    let dLat = degreesToRadians(degrees: lat2 - lat1)
    let dLon = degreesToRadians(degrees: lon2 - lon1)

    let lat1 = degreesToRadians(degrees: lat1)
    let lat2 = degreesToRadians(degrees: lat2)

    let a = sin(dLat/2) * sin(dLat/2) +
    sin(dLon/2) * sin(dLon/2) * cos(lat1) * cos(lat2)
    let c = 2 * atan2(sqrt(a), sqrt(1 - a))
    return earthRadiusKm * c
}
59
задан ks1322 26 December 2016 в 10:53
поделиться

4 ответа

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

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

Например, с 64-битными числами с плавающей запятой с двойной точностью по шкале x=10000000000000000 (1016) невозможно отличить между 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 в Википедии , в которой обсуждается, как эпсилон относится к ошибкам округления.

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

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

.
41
ответ дан 6502 18 August 2018 в 20:02
поделиться
  • 1
    Я предполагаю, что проблема уходит корнями в людей, использующих слово «плавающая точка». (или просто «float») для «нецелого числа в компьютере», без учета (или даже зная о) фактической плавающей природы (т. е. точности зависит от масштаба). – Paŭlo Ebermann 25 December 2016 в 12:37
  • 2
    Верный. Если кто-то собирается делать большую часть такого рода вещей, было бы неплохо потратить некоторое время на изучение концепций, стоящих за плавающей точкой. Существует ряд «неожиданных» которые могут возникнуть, особенно для незнакомого пользователя. – Hot Licks 25 December 2016 в 21:13
  • 3
    eps является неправильным именем FLT_MIN . eps является коротким для FLOAT_EPSILON, что наименьшее число, которое имеет значение при добавлении к 1.0 . Это 1 единица на последнем месте (мантиссы) для 1.0 (см. ulp ). То, что вы описываете, - это концепция epsilon и 1 ULP, но проблема заключается в eps=smallest possible value greater than zero. – Peter Cordes 26 December 2016 в 01:50
  • 4
    скорректировал это для вас с редактированием, чтобы я мог поддержать этот отличный ответ. Проверьте, нравится ли вам текст, который я добавил к вашему ответу. – Peter Cordes 26 December 2016 в 02:12

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

}
5
ответ дан Johan Lundberg 18 August 2018 в 20:02
поделиться

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

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

20
ответ дан Matteo Italia 18 August 2018 в 20:02
поделиться
  • 1
    std::numeric_limits<float>::min() - наименьшее положительное нормализованное значение . Субнормальные явления могут быть ниже. – user2357112 25 December 2016 в 10:01
  • 2
    @ user2357112: Я должен добавить оговорку в свой профиль, в котором говорится: «любое обсуждение, которое я делаю о плавающей запятой, выполняется без учета денормализованных чисел, которые безошибочно игнорируются». :-) – Matteo Italia 25 December 2016 в 16:39
  • 3
    Отсутствие субнормальности еще более уродливее. С доступными субнормалами вычитание двух неравных чисел всегда будет давать ненулевой ответ. Без субнормальных данных это не будет. – plugwash 26 December 2016 в 01:13
  • 4
    @plugwash: Да, это здорово. И это верно, даже если входы уже денормальны, потому что тогда это просто целая математика на мантиссах. – Peter Cordes 26 December 2016 в 02:18

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

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

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

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

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

87
ответ дан WhiZTiM 18 August 2018 в 20:02
поделиться
  • 1
    std::numeric_limits<float>::min() не является наименьшим положительным представимым значением; это наименьшая положительная нормализованная величина, поэтому субнормальные значения могут быть ниже. – user2357112 25 December 2016 в 10:02
  • 2
    IIRC, примерно половина всех битовых шаблонов с плавающей запятой представляет собой число с магнитудой меньше 1.0. Диапазон поля экспоненты более или менее сосредоточен вокруг 0 (представляющий множитель 2^0 = 1.0 для мантиссы), после учета смещения в том, как он кодируется, что делает сортировку битовых шаблонов FP как целых чисел. См. Превосходную серию статей Брюса Доусона о странных вещах с плавающей запятой, в том числе об этом представлении – Peter Cordes 26 December 2016 в 01:35
  • 3
    См. эту статью для оглавления в этой серии статей FP. – Peter Cordes 26 December 2016 в 01:36
  • 4
    Кроме того, +/- Infinity делает хороший второй аргумент для std::nextafter, если вы определенно хотите идти в одном направлении или в другом. Возможно, это будет быстрее, в зависимости от того, как проверка реализации для особых случаев, окружающих +/- 0,0. – Peter Cordes 26 December 2016 в 01:40
Другие вопросы по тегам:

Похожие вопросы: