Точность типов данных с плавающей точкой в ​​C ++ - PullRequest
2 голосов
/ 19 апреля 2019

Почему точность типов данных с плавающей точкой не растет пропорционально их размеру? E.g.:

std::cout << sizeof(float) << "\n";  // this gives 4 on my machine "debian 64 bit" with "gcc 6.3.0"  
std::cout << std::numeric_limits<float>::digits10  << "\n"; // gives 6

std::cout << sizeof(double) << "\n";  // gives 8
std::cout << std::numeric_limits<double>::digits10 <<  "\n"; // gives 15

std::cout << sizeof(long double) <<  "\n";  // gives 16
std::cout << std::numeric_limits<long double>::digits10  << "\n"; // gives 18

Как видите, точность double примерно вдвое больше точности float, и это имеет смысл, поскольку размер double вдвое больше, чем float.

Но это не тот же случай между double и long double, размер long double равен 128 битам, что вдвое больше, чем у 64-битных double, но его точность на три цифры больше !!

Я понятия не имею, как реализованы числа с плавающей запятой, но с рациональной точки зрения имеет ли смысл использовать 64-битную память больше только для трехзначной точности?!

Я искал вокруг, но не смог найти простой, прямой ответ. Если кто-то может объяснить, почему точность long double всего на три цифры больше, чем double, вы также можете объяснить, почему это не тот же случай, что между double и float?

И я также хочу знать, как я могу добиться большей точности, не определяя свой собственный тип данных, который, очевидно, будет зависеть от производительности?

Ответы [ 3 ]

2 голосов
/ 19 апреля 2019

«Точность» - это еще не все, что относится к значению с плавающей запятой.Это также касается величины (не уверен, что этот термин правильный!): Насколько большими (или маленькими) могут стать представленные значения?

Для этого попробуйте также напечатать max_exponent каждого типа:

std::cout << "float: " << sizeof(float) << "\n";
std::cout << std::numeric_limits<float>::digits << "\n";
std::cout << std::numeric_limits<float>::max_exponent << "\n";

std::cout << "double: " << sizeof(double) << "\n";
std::cout << std::numeric_limits<double>::digits << "\n";
std::cout << std::numeric_limits<double>::max_exponent << "\n";

std::cout << "long double: " <<  sizeof(long double) << "\n";
std::cout << std::numeric_limits<long double>::digits << "\n";
std::cout << std::numeric_limits<long double>::max_exponent << "\n";

Вывод на ideone :

float: 4
24
128
double: 8
53
1024
long double: 16
64
16384

Таким образом, не все дополнительные биты используются для представления большего количества цифр (точности), но позволяют увеличить показатель степени,Использование формулировки IEE 754 long double в большинстве случаев увеличивает диапазон экспонент , а не точность.

Формат, показанный на моем примере ideone выше, показывает (вероятно,* "x86 формат расширенной точности" , который назначает 1 бит для целочисленной части, 63 бита для дробной части (вместе 64 цифры) и 15 бит (2 ^ (15-1) = 16384, 1 битиспользуется для знака экспоненты) для экспоненты.

Обратите внимание, что стандарт C ++ требует, чтобы long double был как минимум столь же точным, как double, поэтому long double может быть синонимом double, показанный формат расширенной точности x86 (скорее всего) или лучше (AFAIK только GCC на PowerPC).

И я также хочу знать, как можно повысить точность, не определяя свои собственные данныетип, который, очевидно, будет зависеть от производительности?

Вам нужно либо написать его самостоятельно (конечно, опыт обучения, лучше всего не делать для производственного кода), либо использовать библиотеку, например GNU MPFR или Boost.Multiprecision .

1 голос
/ 20 апреля 2019

В вашем вопросе много неверных предположений

Во-первых, в C ++ нет требований относительно размеров типов. Стандарт только предписывает минимальную точность каждого типа и что ...

... Тип double обеспечивает, по крайней мере, такую ​​же точность, как float, а тип long double обеспечивает, по крайней мере, такую ​​же точность, как double. Набор значений типа float является подмножеством набора значений типа double; набор значений типа double является подмножеством набора значений типа long double. Представление значений типов с плавающей запятой определяется реализацией.

http://www.open -std.org / ОТК1 / SC22 / wg21 / документы / документы / 2012 / n3337.pdf

Большинство современных реализаций отображают float и double в формат одинарной и двойной точности IEEE-754, поскольку аппаратная поддержка для них является основной. Однако long double не имеет такой широкой поддержки, потому что немногие люди нуждаются в более высокой точности, чем двойные, а оборудование для них стоит намного дороже. Поэтому некоторые платформы отображают его на IEEE-754 с двойной точностью, то есть так же, как double. Некоторые другие отображают его в 80-битном формате расширенной точности IEEE 754 , если базовое оборудование поддерживает его. В противном случае long double будет представлен double-double арифметическим или IEEE-754 с четверной точностью

Кроме того, точность также не масштабируется линейно до количества бит в типе . Легко видеть, что double в более чем в два раза точнее, чем float, и в 8 раз шире, чем float, несмотря на то, что в два раза больше места для хранения, потому что он имеет 53 бита и по сравнению с 24 в float и еще 3 экспонентных бита. Типы также могут иметь представления ловушек или биты заполнения, поэтому разные типы могут иметь разные диапазоны, даже если они имеют одинаковый размер и принадлежат к одной и той же категории (целочисленная или с плавающей запятой)

Итак, важная вещь здесь std::numeric_limits<long double>::digits. Если вы напечатаете это, вы увидите, что long double имеет 64 бита значения, что на 11 бит больше, чем double. Посмотри вживую . Это означает, что ваш компилятор использует 80-битную расширенную точность для long double, остальное - просто байтов заполнения , чтобы сохранить выравнивание. На самом деле gcc имеет различные опции , которые изменят ваш вывод:

  • -malign-double и -mno-align-double для управления выравниванием long double
  • -m96bit-long-double и -m128bit-long-double для изменения размера заполнения
  • -mlong-double-64, -mlong-double-80 и -mlong-double-128 для управления базовой long double реализацией

Изменяя параметры, вы получите следующие результаты для long double

Вы получите размер = 10, если отключите заполнение, но это приведет к снижению производительности из-за смещения

В PowerPC вы также можете видеть те же явления при изменении формата с плавающей запятой . С -mabi=ibmlongdouble (арифметика двойных двойных чисел, которая используется по умолчанию) вы получите (размер, цифры10, цифры2) = (16, 31, 106), но с -mabi=ieeelongdouble кортеж станет (16, 33, 113)

Для получения дополнительной информации вы должны прочитать https://en.wikipedia.org/wiki/Long_double

И я также хочу знать, как я могу получить более высокую точность, не определяя свой собственный тип данных

Ключевое слово для поиска - арифметика произвольной точности . Существуют различные библиотеки для этого, которые вы можете найти в списке арифметических программ произвольной точности . Вы можете найти больше информации в тегах , или

1 голос
/ 20 апреля 2019

Стандарт C ++ не устанавливает фиксированных требований для типов с плавающей запятой, за исключением некоторых минимальных уровней, которым они должны соответствовать.

Вероятно, используемая вами реализация C ++ предназначена для процессора Intel.Помимо распространенных базовых 32-битных и 64-битных двоичных форматов с плавающей запятой IEEE-754, Intel имеет 80-битный формат.Ваша реализация C ++, вероятно, использует это для long double.

. 80-битный формат Intel имеет на 11 бит больше для значения, чем 64-битный double формат.(На самом деле он использует 64, где в формате double используется 52, но один из них зарезервирован для явного начального 1.)еще три десятичных цифры.

80-битный формат (который составляет десять байтов) преимущественно выровнен с кратными 16 байтов, поэтому шесть байтов заполнения включены, чтобы сделать размер long double кратным 16 байтам.

...