Эпсилон с четырехкратной точностью (gcc)
Согласно Википедии, макеты различных типов данных точности
- одинарная точность: экспонента (e): 8 бит, дробь (f): 23 бита
- двойная точность: e: 11 бит, f: 52 бит
- учетверенная точность: e: 15 бит, f: 112 бит.
Я написал небольшую программу для вывода числовых ограничений для чисел float, double и long double на C++ (скомпилировано с g++)
#include<iostream>
#include<limits>
#include<string>
template<typename T>
void print(std::string name) {
std::cout << name << " (" << sizeof(T) * 8 << "): " << std::numeric_limits<T>::epsilon() << "\t" << std::numeric_limits<T>::min() << "\t" << std::numeric_limits<T>::max() << std::endl;
}
int main() {
std::cout.precision(5);
print<float>("float");
print<double>("double");
print<long double>("long double");
return 0;
}
какие выходы (я запускал его на нескольких машинах с тем же результатом)
float (32): 1.1921e-07 1.1755e-38 3.4028e+38
double (64): 2.2204e-16 2.2251e-308 1.7977e+308
long double (128): 1.0842e-19 3.3621e-4932 1.1897e+4932
Верхние пределы совпадают с 2^(2^(e-1)), а для чисел float и double эпсилон совпадает с 2^(-f). Однако для long double эпсилон должен быть примерно 1.9259e-34 по этой логике.
Кто-нибудь знает, почему это не так?
1 ответ
long double
не гарантируется, что будет реализована как четырехкратная точность IEEE-745. Справочник по C++ гласит:
long double
- тип с плавающей запятой повышенной точности. Не обязательно соответствует типам, предписанным IEEE-754. Обычно 80-битный тип с плавающей запятой x87 на архитектурах x86 и x86-64.
Если long double
реализован как 80-битная x86 с расширенной точностью, тогда epsilon2-63 = 1.0842e-19
. Это значение, которое вы получите на выходе.
Некоторые компиляторы поддерживают __float128
тип с четырехкратной точностью. В GCClong double
становится псевдонимом для __float128
если -mlong-double-128
используется параметр командной строки, а на целевых объектах x86_64 __float128
гарантированно относится к типу четверной точности IEEE (реализовано программно).
std::numeric_limits
не специализируется на __float128
. Чтобы получить значение epsilon, можно использовать следующий трюк (при условии, что машина с прямым порядком байтов):
__float128 f1 = 1, f2 = 1; // 1.q -> ...00000000
std::uint8_t u = 1;
std::memcpy(&f2, &u, 1); // 1.q + eps -> ...00000001
std::cout << double(f2 - f1); // Output: 1.9259e-34
С GCC вы можете использовать libquadmath:
#include <quadmath.h>
...
std::cout << (double)FLT128_EPSILON;
чтобы получить тот же результат.