<true
>для всех типов арифметики (целое число, плавающая и фиксированная точка), для которых<std::numeric_limits<T>::numeric_limits
>является специализированным.
Типичным тестом является
if (std::numeric_limits<T>::is_specialized == false)
{
std::cout << "type " << typeid(T).name() << " is not specialized for std::numeric_limits!" << std::endl;
}
Как правило,<numeric_limits<T>::is_specialized
>является<true
>для всех<T
>, где постоянные члены<numeric_limits
>времени компиляции действительно известны во время компиляции и не изменяются во время выполнения. Например, типы с плавающей запятой с переменной точностью выполнения, такие как<mpfr_float
>, не имеют специализации<numeric_limits
>, поскольку невозможно определить всех участников во время компиляции. В отличие от этого, точность типа<mpfr_float_50
>известна во время компиляции, и поэтомуимеет специализацию<numeric_limits
>.
Обратите внимание, что не все константы и функции<std::numeric_limits
>являются значимыми для всех определяемых пользователем типов (UDT), таких как десятичные и двоичные типы многоточности, представленные здесь. Более подробная информация об этом приводится в разделах ниже.
Для типов с плавающей точкой ∞ определяется везде, где это возможно, но ясно, что бесконечность бессмысленна для __arbitrary_precision arithmetic backends, и есть один тип с плавающей точкой (GMP<mpf_t
>, см.gmp_float), который вообще не имеет понятия бесконечности или NaN.
Типичный тест на реализацию бесконечности
if(std::numeric_limits<T>::has_infinity)
{
std::cout << std::numeric_limits<T>::infinity() << std::endl;
}
Использование подобных тестов настоятельно рекомендуется для улучшения переносимости.
Если бэкэнд переключается на тип, который не поддерживает бесконечность, то без таких проверок будут проблемы.
<std::numeric_limits<T>::is_signed==
true
>Если подпись<T
>.
Для встроенных бинарных типов знак удерживается в одном бите, но для других типов (cpp_dec_float и cpp_bin_float) он может быть отдельным элементом хранения, обычно<bool
>.
<std::numeric_limits<T>::is_exact==
true
>Если тип Т использует точные представления.
Это<true
>для всех целых типов и<false
>для типов с плавающей запятой.
Обсуждается полезное определение.
ISO/IEC 10967-1, Языковая независимая арифметика, отмеченная C++ Стандарт определяет
A floating-point type F shall be a finite subset of [real].
Важное практическое различие заключается в том, что все целые числа (до 26) могут храниться точно.
Рациональныетипы, использующие два целых типа, также точны.
Типы плавающих точекне могут хранить все действительные значения(те, что в наборе ℜ)точно. Например, 0,5 может храниться точно в двоичной плавающей точке, но 0,1 не может. То, что хранится, является ближайшей репрезентативной реальной ценностью, то есть округленной до ближайшей.
Типы фиксированных точек (обычно десятичные) также определяются как точные, поскольку они хранят только фиксированную точность, поэтому полцента или пенни (или меньше) не могут храниться. Результаты вычислений округляются вверх или вниз, точно так же, как результат целочисленного деления, хранящегося как целочисленный результат.
Есть несколько предложенийдобавить поддержку десятичных плавающих точек на C++.
Децимальный ТР.
А такжеC++ Бинарная арифметика с фиксированной точкой.
<std::numeric_limits<T>::is_bounded==
true
>если набор значений, представленный типом<T
>, является конечным.
Это<true
>для всех встроенных целочисленных, фиксированных и плавающих типов и для большинства многоточных типов.
Это только<false
>для нескольких типов произвольной точности, таких как<cpp_int
>.
Рациональные и фиксированные представления являются точными, но не целыми.
<std::numeric_limits<T>::is_modulo
>определяется как<true
>, если добавление двух положительных значений типа T может дать результат меньше, чем любое из значений.
<is_modulo==
true
>означает, что тип не переполняется, а, например, «окружается» до нуля, при добавлении одного к значению<max()
>.
Для большинства встроенных целочисленных типов<std::numeric_limits<>::is_modulo
>составляет<true
>.
<bool
>Это единственное исключение.
Поведение модуля иногда полезно, но также может быть неожиданным, а иногда и нежелательным.
Переполнение подписанных целых чисел может быть особенно неожиданным, что может привести к изменению знака.
Повышаю. Многоточный целочисленный тип<cpp_int
>не является модулем, поскольку в качестве __arbitrary_precision типов он расширяется, чтобы удерживать любое значение, которое позволяют ресурсы машины.
Однако фиксированная точностьcpp_intможет быть модульной, если они не контролируются (то есть они ведут себя так же, как встроенные в целые числа), но не если они проверяются (переток вызывает исключение).
Встроенные и многоточные типы плавающих точек обычно не являются модулем.
По возможности, перелив<std::numeric_limits<>::infinity()
>при условии<std::numeric_limits<>::has_infinity
==true
>.
Константа<std::numeric_limits<T>::radix
>возвращает либо 2 (для встроенных и двоичных типов), либо 10 (для десятичных типов).
Число<radix
>цифр, которые представлены без изменения:
- Для целочисленных типов числонезнаковых битовв значении.
- Для плавающих типов числорадиксовых цифрв значении.
Значения включают любой неявный бит, так, например, для вездесущего<double
>, использующего 64 битаIEEE binary64,<digits
>== 53, даже если в представлении есть только 52 действительных бита значащего. Значение<digits
>отражает тот факт, что существует один неявный бит, который всегда устанавливается на 1.
Начало. Многоточные двоичные типы не используют неявный бит, поэтому элемент<digits
>отражает, сколько именно битов точности было запрошено:
typedef number<cpp_bin_float<53, digit_base_2> > float64;
typedef number<cpp_bin_float<113, digit_base_2> > float128;
std::numeric_limits<float64>::digits == 53.
std::numeric_limits<float128>::digits == 113.
Для наиболее распространенного случая<radix
==2
>,<std::numeric_limits<T>::digits
>является число битов в представлении, не считая ни одного бита знака.
Для десятичного целого типа, когда<radix
==10
>, это число десятичных цифр.
Константа<std::numeric_limits<T>::digits10
>возвращает число десятичных цифр, которые могут быть представлены без изменения или потери.
Например,<numeric_limits<unsignedchar>::digits10
>равно 2.
Это несколько непостижимое определение означает, что<unsigned
char
>может содержать десятичные значения<0..99
>без потери точности или точности, обычно от усечения.
Если бы определение было 3, это означало бы, что он может содержать 0,999, но, как мы все знаем, 8-битный<unsigned
char
>может содержать только 0,255, а попытка сохранить 256 или более будет включать потерю или изменение.
Таким образом, для ограниченных целых чиселна одно меньше, чем число десятичных цифр, необходимо отображать наибольшее целое число<std::numeric_limits<T>::max()
>. Это значение может использоваться для прогнозирования ширины компоновки, необходимой для
std::cout
<< std::setw(std::numeric_limits<short>::digits10 +1 +1)
<< std::showpos << (std::numeric_limits<short>::max)()
<< std::endl
<< std::setw(std::numeric_limits<short>::digits10 +1 +1)
<< (std::numeric_limits<short>::min)() << std::endl;
Например,<unsignedshort
>часто хранится в 16 битах, поэтому максимальное значение составляет 0xFFFF или 65535.
std::cout
<< std::setw(std::numeric_limits<unsigned short>::digits10 +1 +1)
<< std::showpos << (std::numeric_limits<unsigned short>::max)()
<< std::endl
<< std::setw(std::numeric_limits<unsigned short>::digits10 +1 +1)
<< (std::numeric_limits<unsigned short>::min)() << std::endl;
Для ограниченных типов плавающих точек, если мы создаем<double
>со значением<digits10
>(обычно 15) десятичных цифр,<1e15
>или<1000000000000000
>:
std::cout.precision(std::numeric_limits<double>::max_digits10);
double d = 1e15;
double dp1 = d+1;
std::cout << d << "\n" << dp1 << std::endl;
std::cout << dp1 - d << std::endl;
И мы можем увеличить это значение до<1000000000000001
>, как и ожидалось, и показать разницу.
Но если мы попытаемся повторить это с более чем 99 цифрами,
std::cout.precision(std::numeric_limits<double>::max_digits10);
double d = 1e16;
double dp1 = d+1;
std::cout << d << "\n" << dp1 << std::endl;
std::cout << dp1 - d << std::endl;
Затем мы обнаруживаем, что когда мы добавляем один, это не имеет никакого эффекта, и дисплей показывает, что есть потеря точности. См.Потеря значения или ошибка отмены.
Так<digits10
>число десятичных знаковгарантируетправильность.
Например, «круглые столы»<double
>:
- Если десятичная строка с максимумом<
digits10
>( == 15) значащими десятичными цифрами преобразуется в<double
>, а затем преобразуется обратно в то же число значащих десятичных цифр, то окончательная строка будет соответствовать исходной строке с 15 десятичными цифрами.
- Если число<
double
>с плавающей точкой преобразуется в десятичную строку с по меньшей мере 17 десятичными цифрами и затем преобразуется обратно в<double
>, то результат будет двоичным, идентичным исходному значению<double
>.
Для большинства целей вам, скорее всего, понадобится<std::numeric_limits<>::max_digits10
>, число десятичных цифр, которые гарантируют, что изменение одного наименее значимого бита (ULP) создает другую десятичную строку цифр.
Для наиболее распространенного типа<double
>с плавающей точкой<max_digits10
>является<digits10+2
>, но вы должны использовать C++11<max_digits10
>, где это возможно (см.ниже).
<std::numeric_limits<T>::max_digits10
>было добавлено для плавающей точки, потому что<digits10
>десятичных цифр недостаточно, чтобы показать изменение по меньшей мере значительного бита (ULP), придавая загадочные дисплеи, такие как
0.666666666666667 != 0.666666666666667
От неудачи до «круглого пути», например:
double write = 2./3;
double read = 0;
std::stringstream s;
s.precision(std::numeric_limits<double>::digits10);
s << write;
s >> read;
if(read != write)
{
std::cout << std::setprecision(std::numeric_limits<double>::digits10)
<< read << " != " << write << std::endl;
}
Если вы хотите убедиться, что изменение одного наименее значимого бита (ULP) производит другую десятичную строку цифр, то<max_digits10
>- это точность использования.
Например:
double pi = boost::math::double_constants::pi;
std::cout.precision(std::numeric_limits<double>::max_digits10);
std::cout << pi << std::endl;
будет отображаться π с максимально возможной точностью с использованием<double
>.
Для более точного типа:
using namespace boost::multiprecision;
typedef number<cpp_dec_float<50> > cpp_dec_float_50;
using boost::multiprecision::cpp_dec_float_50;
cpp_dec_float_50 pi = boost::math::constants::pi<cpp_dec_float_50>();
std::cout.precision(std::numeric_limits<cpp_dec_float_50>::max_digits10);
std::cout << pi << std::endl;
Для целых типов<max_digits10
>зависит от реализации, но обычно<digits10
+2
>. Это выходная ширина поля, требуемая для максимального значения типа T<std::numeric_limits<T>::max()
>, включая знак и пространство.
Это позволит создать аккуратные колонны.
std::cout << std::setw(std::numeric_limits<int>::max_digits10) ...
Дополнительные две или три наименее значимые цифры являются «шумными» и могут быть «мусорными», но если вы хотите «объехать» - распечатать значение в виде десятичной строки и прочитать его обратно - (чаще всего во время сериализации и десериализации) вы должны использовать<os.precision(std::numeric_limits<T>::max_digits10)
>.
![[Note]](/img/note.png) |
Note |
Для Microsoft Visual Studio 2010<std::numeric_limits<float>::max_digits10 >ошибочно определяется как 8. Должно быть 9. |
![[Note]](/img/note.png) |
Note |
Для Microsoft Visual Studio до 2013 года и формата по умолчанию, небольшой диапазон значений примерно от 0,0001 до 0,004, с значениями экспоненты от 3F2 до 3F6, неправильно вводится одним наименее значительным битом, вероятно, каждое третье значение значимо.
Обратный путь — это использование научного или экспоненциального формата<<<std::scientific >.
|
![[Note]](/img/note.png) |
Note |
BOOST_NO_CXX11_NUMERIC_LIMITS является подходящим макросом для определения того, реализован ли<std::numeric_limits<float>::max_digits10 >на какой-либо платформе. |
Если<max_digits10
>недоступен, вы должны использовать формулу Каханадля плавающей точки типа Т.
В C++ уравнения для того, что Кахан (на стр. 4) описывает как «по крайней мере» и «по большей части»:
static long double const log10Two = 0.30102999566398119521373889472449L;
static_cast<int>(floor((significand_digits - 1) * log10Two));
static_cast<int>(ceil(1 + significand_digits * log10Two));
К сожалению, они не могут быть оценены (по крайней мере, на C++03) в.время компиляции. Вместо этого часто используется следующее выражение.
max_digits10 = 2 + std::numeric_limits<T>::digits * 3010U/10000U;
Часто фактические значения вычисляются для макросов пределов С:
#define FLT_MAXDIG10 (2+FLT_MANT_DIG * 3010U/10000U)
#define DBL_MAXDIG10 (2+ (DBL_MANT_DIG * 3010U)/10000U)
#define LDBL_MAXDIG10 (2+ (LDBL_MANT_DIG * 3010U)/10000U)
Коэффициент 3010U/10000U составляетlog10(2) = 0,3010, который может быть оценен во время компиляции, используя только<short
unsignedint
>s, чтобы быть желательным<const
>или<constexpr
>(и обычно также<static
>).
Усиленные макросы позволяют делать это портативно, см.BOOST_CONSTEXPR_OR_CONST или BOOST_STATIC_CONSTEXPR.
(См. такжеRichard P. Brent and Paul Zimmerman, Modern Computer ArithmeticEquation 3.8 на стр. 116).
Например, быть переносимым (включая отдельные платформы) для типа<T
>, где<T
>может быть:<float
>,<double
>,<long
double
>,<128-bitquadtype
>,<cpp_bin_float_50
>...
typedef float T;
#if defined BOOST_NO_CXX11_NUMERIC_LIMITS
std::cout.precision(max_digits10<T>());
#else
#if(_MSC_VER <= 1600)
std::cout.precision(max_digits10<T>());
#else
std::cout.precision(std::numeric_limits<T>::max_digits10);
#endif
#endif
std::cout << "std::cout.precision(max_digits10) = " << std::cout.precision() << std::endl;
double x = 1.2345678901234567889;
std::cout << "x = " << x << std::endl;
которые должны выводить:
std::cout.precision(max_digits10) = 9
x = 1.23456789
Стиль округления определяет, как обрабатывается результат операций с плавающей запятой, когда результат не может быть точно представленв значении. Могут быть предусмотрены различные режимы округления:
- округление до ближайшего вверх или вниз (по умолчанию для типов с плавающей запятой).
- Округление (к положительной бесконечности).
- Округление вниз (к отрицательной бесконечности).
- округление к нулю (целые типы).
- без закругления (если десятичный радикс).
- Режим округления не является определяемым.
Для целых типов<std::numeric_limits<T>::round_style
>всегда стремится к нулю.
std::numeric_limits<T>::round_style == std::round_to_zero;
Десятичный тип,<cpp_dec_float
>раундов без определенного направления, то есть он вообще не округляется. И поскольку есть несколько защитных цифр, это не совсем то же самое, что усечение (кругом к нулю).
Для типов с плавающей точкой нормально округляться до ближайшей.
std::numeric_limits<T>::round_style == std::round_to_nearest;
См. функцию<std::numeric_limits<T>::round_error
>для максимальной ошибки (в ULP), которую может вызвать округление.
<true
>если потеря точности обнаруживается какденормализацияпотеря, а не как неточный результат.
Всегда<false
>для целых типов.
<false
>для всех типов, не имеющих<has_denorm
>==<std::denorm_present
>.
Денормализованные значенияпредставляют собой представления с переменным числом экспонентных битов, которые могут допускать постепенный отток, так что, если тип T равен<double
>.
std::numeric_limits<T>::denorm_min() < std::numeric_limits<T>::min()
Тип может иметь любое из следующих<enum
float_denorm_style
>значений:
- <
std::denorm_absent
>, если он не допускает денормализованных значений. (Всегда используется для всех целочисленных и точных типов).
- <
std::denorm_present
>, если тип с плавающей точкой допускает денормализованные значения.
- <
std::denorm_indeterminate
>, если не определено во время компиляции.
<boolstd::numeric_limits<T>::tinyness_before
>
<true
>если тип может определить, что значение слишком мало, чтобы его можно было представить как нормализованное значение, прежде чем округлять его.
Как правило, это верно для<is_iec559
>встроенных типов с плавающей запятой, но неверно для целых типов.
Стандартно совместимые реализации IEEE 754 с плавающей точкой могут обнаруживать поток с плавающей точкой в три заранее определенных момента:
- После вычисления результата с абсолютным значением, меньшим<
std::numeric_limits<T>::min()
>, такая реализация обнаруживаеткрохотность перед округлением(например, UltraSparc).
- После округления результата до<
std::numeric_limits<T>::digits
>битов, если результат является крошечным, такая реализация обнаруживаеткрошечность после округления(например, SuperSparc).
- Если преобразование округленного крошечного результата в субнормальную форму привело к потере точности, такая реализация обнаруживаетпотерю денормы.