здесь реализация 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
}
«Проблема», которую вы наблюдаете, связана с самой природой арифметики с плавающей запятой.
В 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 в Википедии
. Чтобы увеличить / уменьшить значение с плавающей запятой на минимально возможную величину, используйте 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
}
min
- наименьшее ненулевое значение, которое может допускать float (нормализованная форма), то есть что-то около 2-126 (-126 - минимально допустимый показатель для поплавка); теперь, если вы суммируете его на 1, вы все равно получите 1, так как float
имеет всего 23 бита мантиссы, поэтому такое маленькое изменение не может быть представлено в таком «большом» номере (вам понадобится 126-битная мантисса чтобы увидеть смену изменения от 2-126 до 1).
Минимальное возможное изменение на 1 вместо epsilon
(так называемый машинный эпсилон), который фактически равен 2-23 - как это влияет на последний бит мантиссы.
std::numeric_limits<float>::min()
- наименьшее положительное нормализованное значение i>. Субнормальные явления могут быть ниже.
– user2357112
25 December 2016 в 10:01
Если вы хотите следующее представимое значение после 1, то из заголовка <cmath>
будет функция, называемая std::nextafter
.
float result = std::nextafter(1.0f, 2.0f);
Возвращает следующее представляемое значение, начиная с первого аргумента в направлении второго аргумента. Поэтому, если вы хотите найти следующее значение ниже 1, вы можете сделать это:
float result = std::nextafter(1.0f, 0.0f);
Добавление наименьшего положительного отображаемого значения в 1 не работает, потому что разница между 1 и следующим представляемым значением равна больше, чем разница между 0 и следующим представимым значением.
std::numeric_limits<float>::min()
не является наименьшим положительным представимым значением; это наименьшая положительная нормализованная величина, поэтому субнормальные значения могут быть ниже.
– user2357112
25 December 2016 в 10:02
1.0
. Диапазон поля экспоненты более или менее сосредоточен вокруг 0
(представляющий множитель 2^0 = 1.0
для мантиссы), после учета смещения в том, как он кодируется, что делает сортировку битовых шаблонов FP как целых чисел. См. Превосходную серию статей Брюса Доусона о странных вещах с плавающей запятой, в том числе об этом представлении
– Peter Cordes
26 December 2016 в 01:35
std::nextafter
, если вы определенно хотите идти в одном направлении или в другом. Возможно, это будет быстрее, в зависимости от того, как проверка реализации для особых случаев, окружающих +/- 0,0.
– Peter Cordes
26 December 2016 в 01:40
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