Files
ubbook/numeric/floats.md
T
2022-03-20 12:49:46 +03:00

100 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Числа с плавающей точкой
С `float` и `double` в принципе всегда все сложно. Особенно в C++.
Стандарт C++ не требует следования стандарту IEEE 754, потому деление на ноль в вещественных числах также считается неопределенным поведением, несмотря на то, что
по IEEE 754 выражение `x/0.0` определяется как `-INF`, `NaN`, или `INF` в зависимости от знака числа `x` (`NaN` для нуля).
Сравнение вещественных чисел — излюбленная головная боль.
Выражение `x == y` фактически является кривым побитовым сравнением для чисел с плавающей точкой, по особенному работающее со случаями `-0.0` и `+0.0`, и `NaN`.
О существовании этого и `!=` операторов для вещественных чисел стоит забыть и никогда не вспоминать.
Для побитового сравнения нужно использовать `memcmp`.
Для сравнения чисел — приближенные варианты вида `std::abs(x - y) < EPS`, где `EPS` — какое-то абсолютное или вычисляемое на основе `x` и `y` значение. А также различные манипуляции с [`ULP`](https://en.wikipedia.org/wiki/Unit_in_the_last_place) сравниваемых чисел.
Так как стандарт C++ не форсирует IEEE 754,
проверки на `x == NaN` через его свойство `(x != x) == true` могут быть убраны компилятором, как заведомо ложные. Проверять нужно с помощью предназначенных для этого
функций `std::isnan`.
Поддерживается или нет IEEE 754 можно проверить с помощью предопределенной константы
`std::numeric_limits<FloatType>::is_iec559`
Сужающие преобразования из `float` в знаковые или беззнаковые целые могут повлечь неопределенное поведение, если значение непредставимо в целочисленном типе. Никаких обрезок по модулю `2^N` не предполагается.
```C++
constexpr uint16_t x = 1234567.0; // CE, undefined behavior
```
Обратное преобразование, из целочисленных типов во `float`/`double`, также имеет свои подвохи, не связанные с неопределенным поведением: большие по абсолютной величине целые числа [теряют точность](https://godbolt.org/z/xnr5rMGKf)
```C++
static_assert(
static_cast<float>(std::numeric_limits<int>::max()) ==
static_cast<float>(static_cast<long long>(std::numeric_limits<int>::max()) + 1) // OK
);
static_assert(
static_cast<double>((1LL << 53) - 1) ==
static_cast<double>(1LL << 53) // fire!
);
static_assert(
static_cast<double>((1LL << 54) - 1) ==
static_cast<double>(1LL << 54) // OK
);
static_assert(
static_cast<double>((1LL << 55) - 1) ==
static_cast<double>(1LL << 55) // OK
);
static_assert(
static_cast<double>((1LL << 56) - 1) ==
static_cast<double>(1LL << 56) // OK
);
```
В качестве домашнего задания читателю предлагается самостоятельно сформулировать, почему никогда нельзя хранить деньги в типах с плавающей запятой.
### Плавающая точка и шаблоны
Вещественные числа до C++20 нельзя было использовать в качестве параметров-значений в шаблонах. Теперь же можно. Правда, ожидать, что вы насчитаете в run-time и в compile-time одно и то же — [не стоит](https://godbolt.org/z/q55891).
Для простой параметризации типов константами этот механизм вполне можно использовать без опасений. Однако строить на них паттерн-матчинг с выбором специализаций шаблонов крайне [не рекомендуется](https://godbolt.org/z/cGf9h94cn):
```C++
template <double x>
struct X {
static constexpr double val = x;
};
template <>
struct X<+0.> {
static constexpr double val = 1.0;
};
template <>
struct X<-0.> {
static constexpr double val = -1.0;
};
int main() {
constexpr double a = -3.0;
constexpr double b = 3.0;
std::cout << X<a + b>::val << "\n"; // печатает +1
std::cout << X<-1.0 * (a + b)>::val << "\n"; // печатает -1
static_assert(a + b == -1.0 * (a + b)); // ok
}
```
По тем же причинам ни в одном языке программирования не рекомендуется использовать значения с плавающей точкой
в качестве ключей ассоциативных массивов.
### Полезные ссылки
1. https://en.cppreference.com/w/cpp/numeric/math/isnan
2. https://bitbashing.io/comparing-floats.html
3. https://diego.assencio.com/?index=67e5393c40a627818513f9bcacd6a70d