mirror of
https://github.com/Nekrolm/ubbook.git
synced 2026-06-09 13:14:18 +03:00
100 lines
5.7 KiB
Markdown
100 lines
5.7 KiB
Markdown
# Числа с плавающей точкой
|
||
|
||
С `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
|