Даже если вы не занимаетесь написанием научных или инженерных приложений, вам все равно придется работать с числами. Эта глава содержит решения проблем, часто возникающих при работе с числовыми типами С++.
Некоторые из рецептов содержат методики преобразования из числовых типов в тип
string
и обратно чисел, представленных в различных форматах (шестнадцатеричном, с плавающей точкой или экспоненциальном). Самостоятельное написание кода для таких преобразований утомительно и требует времени, так что я показываю возможности стандартной библиотеки или одной из библиотек Boost, облегчающие выполнение этих задач. Также имеется несколько рецептов по работе исключительно с числовыми типами: безопасное преобразование между ними, сравнение чисел с плавающей точкой с граничными значениями и поиск минимального и максимального значений.
Рецепты в этой главе предоставляют решения некоторых общих проблем, с которыми обычно сталкиваются при работе с числами в С++, но они не пытаются решать проблем, специфичных для конкретных приложений. При написании научного или инженерного приложения вам также следует взглянуть на главу 11, которая содержит рецепты ко многим общим научным и инженерным алгоритмам.
Имеются числа в строковом формате, и вам требуется преобразовать их в числовой тип, такой как
int
или float
.
Это можно сделать двумя способами — с помощью функций стандартной библиотеки или с помощью класса
lexical_cast
из Boost (написанного Кевлином Хенни (Kevlin Henney) Функции стандартной библиотеки неуклюжи и небезопасны, но они стандартны, и в некоторых случаях потребуются именно они, так что в первом решении я представлю именно их. lexical_cast
более безопасен, проще в использовании и интереснее, так что я представляю его в обсуждении.
Функции
strtol
, strtod
и strtoul
, определенные в
, преобразуют символьные строки, ограниченные нулем, в long int
, double
или unsigned long
. Они могут использоваться для преобразования чисел, представленных в виде строк с любым основанием, в числовые типы. Код примера 3.1 демонстрирует функцию hex2int
, которая предназначена для преобразования шестнадцатиричной строки в long
.
Пример 3.1. Преобразование числовых строк в числа
#include
#include
#include
using namespace std;
long hex2int(const string& hexStr) {
char *offset;
if (hexStr.length( ) > 2) {
if (hexStr[0] == '0' && hexStr[1] == 'x') {
return strtol(hexStr.c_str(), &offset, 0);
}
}
return strtol(hexStr.c_str( ), &offset, 16);
}
int main() {
string str1 = "0x12AB";
cout << hex2int(str1) << endl;
string str2 = "12AB";
cout << hex2int(str2) << endl;
string str3 = "0AFG";
cout << hex2int(str3) << endl;
}
Вот вывод этой программы.
4779
4779
0
Первые две строки содержат шестнадцатеричное число 12AB. Первая из них содержит префикс
0x
, а вторая — нет. Третья строка не содержит правильного шестнадцатеричного числа. В этом случае функция просто возвращает 0.
Некоторые люди склонны писать свои собственные функции для преобразования шестнадцатеричных чисел в целочисленные форматы. Но зачем изобретать колесо? Стандартная библиотека уже предоставляет эту функциональность. Пример 3.1 представляет собой функцию-оболочку, упрощающую вызов
strtol
. Функция strtol
— это старая функция библиотеки С, и она требует от вас передачи указателя на завершающуюся нулем строку, а так же адрес еще одного указателя на строку. Этот второй указатель получает адрес, на котором обработка строки завершилась. Однако в C++ большинство людей предпочитает работать с более мощным классом string
, а не со старыми указателями на символьные строки. Поэтому функция hex2int
принимает параметр типа string
.
Функция
strtol
несколько странна в том, что она позволяет использовать два разных метода указания основания 16: 16 можно передать как третий параметр функции, а можно в качестве основания передать 0, но предварить строку символами 0x
(точно также, как это делается для обозначения шестнадцатеричных чисел в коде, но только помните, что в случае с strtol
передается строка).
Пример 3.1 позволяет использовать оба метода. При передаче строки вида
0x12AB
функция обнаружит 0x
и передаст ее непосредственно в strtol
, в качестве третьего параметра передав 0. В противном случае функция передаст строку, в качестве третьего параметра передав 16.
strtol
и strtoul
работают одинаково, за исключением типа возвращаемого значения. strtod
аналогична им, но не позволяет указывать основание.
Эти старые функции С не являются единственным способом преобразования строк в числа. Проект Boost предоставляет класс преобразования
lexical_cast
, который выполняет то же самое для числовых строк, записанных с основанием 10. Пример 3.2 показывает как он используется.
Пример 3.2. Использование lexical_cast
#include
#include
#include
using namespace std;
int main() {
string str1 = "750" ;
string str2 = "2.71";
string str3 = "0x7FFF";
try {
cout << boost::lexical_cast(str1) << endl;
cout << boost::lexical_cast(str2) << endl;
cout << boost::lexical_cast(str3) << endl;
} catch (boost::bad_lexical_cast& e) {
cerr << "Bad cast: " << e.what() << endl;
}
}
Вывод примера 3.2 таков.
750
2.71
Bad cast: bad lexical cast: source type value could not be
interpreted as target
(Неверное преобразование: неверное лексическое преобразование: значение исходного типа не может быть преобразовано в целевой.)
Вы ведите, что для последнего значения, представляющего собой шестнадцатеричное число, он выбрасывает исключение. При преобразовании чисел с основанием, отличным от 10, требуется использовать функции
strtol
.
Также имеются версии функций
strtol
для работы с «широкими» символами. Эквивалент strtol
для работы с широкими символами — это wcstol
, которая объявлена в
. Эквивалентами функций strtod
и strtoul
являются wcstod
и wcstoul
. Каждая из этих функций точно такая же, за исключением того, что те параметры, которые в функциях для узких символов имеют тип char*
, в функциях для широких символов имеют тип wchar_t*
.
Рецепт 3.2.
Имеются числовые типы (
int
, float
), и вам требуется поместить их содержимое в string
, возможно, предварительно отформатировав.
Для выполнения этого имеется множество способов, каждый из которых имеет свои достоинства и недостатки. Первая представляемая мной методика использует для хранения строковых данных класс
stringstream
, который является частью стандартной библиотеки и прост в использовании. Этот подход показан в примере 3.3. Смотри обсуждение альтернативных методик.
Пример 3.3. Форматирование числа как строки
#include
#include
#include
#include
using namespace std;
int main() {
stringstream ss;
ss << "В моей корзине " << 9 << " яблок.";
cout<
// с содержимым
ss.str(""); // Очистка строки
ss << showbase << hex << 16; // Показать основание в шестнадцатеричном формате
cout << "ss = " << ss.str() << endl;
ss.str("");
ss << 3.14;
cout << "ss = " << ss.str() << endl;
}
Вывод примера 3.3 выглядит так.
В моей корзине 9 яблок.
ss = 0x10
ss = 3.14
stringstream
— это удобный способ поместить данные в string
, поскольку он позволяет использовать все возможности форматирования, предоставляемые классами стандартного ввода и вывода. В простейшем случае в примере 3.3 я для записи комбинации текста и числовых данных в строковый поток просто использую оператор сдвига влево (<<
).
ss << "В моей корзине " << 9 << " яблок.";
Оператор
<<
перегружен для встроенных типов и соответственно форматирует вывод. Когда требуется получить данные, хранящиеся в string
, используйте функцию-член str
.
cout << ss.str() << endl;
В
имеется большое количество манипуляторов потоками, и их использование при выводе числовых данных в строку позволяет выполнить все виды форматирования. В примере 3.3 для форматирования числа как шестнадцатеричного я использовал showbase
и hex
, но есть еще и другие возможности форматирования. Например, можно установить точность отображения, отличную от числа десятичных знаков по умолчанию.
ss << setprecision(6) << 3.14285;
Однако использование манипуляторов является не самой интуитивно понятной вещью, и именно поэтому создан рецепт, посвященный им. За дополнительной информацией о форматировании числовых данных с помощью манипуляторов потоками обратитесь к рецепту 10.2.
Конечно, как часто бывает в С++, имеется и другой способ. Библиотека Boost Format (написанная Сэмюэлем Кремппом (Samuel Krempp) содержит класс
format
, который делает форматирование и преобразование очень простыми. Пример 3.4 показывает, как выполнить подобное преобразование.
Пример 3.4. Форматирование целых в шестнадцатеричное представление
#include
#include
using namespace std;
using boost::format;
using boost.:io::str;
using boost::io::format_error;
int main() {
try {
format f("Имеется %1% способа. %2% %3% %4%");
f % 3;
f % "чтобы" % "это" % "сделать.";
cout << f << endl;
f.clear(); // Счистка буферов для форматирования чего-либо еще
f.parse("Это стоит $%d.");
f % 50;
cout << f << endl;
int x = 11256099;
string strx = str(format("%x") % x);
cout << strx << endl;
} catch (format_error &e) {
cout << e.what() << endl;
}
}
Вот что вы увидите при запуске этой программы.
Имеется 3 способа, чтобы это сделать.
Это стоит $50.
abc123
Использование класса
format
требует двух шагов, включая создание объекта format
и передачу ему содержимого. Для простейшего случая в примере 3.4 я создал объект format с помощью простейшей версии его синтаксиса.
format f(" Имеется %1% способа, %2% %3% %4%");
В строке формата заполнители — это числа, обрамленные с обеих сторон символами %. Затем я начинаю передавать в объект содержимое указанного формата.
f % 3;
f % "чтобы" % "это" % "сделать;
Оператор
%
в библиотеке форматирования был переопределен так, чтобы добавлять указанные в нем переменные в левую часть объекта format
. Его можно использовать как один раз на строку, так и несколько раз в одной строке. Он аналогичен оператору <<
для строк. Что же касается оператора <<
, он также был переопределен так, что объекты format
можно непосредственно записать в выходной поток. Кроме того, если требуется поместить результаты в строку, используйте функцию-член str
.
string s = f.str();
Если же вам нравится
printf
, то можно использовать форматную строку printf
.
f.parse("Это стоит $%d.*");
f % 50;
Если будет записано слишком много или слишком мало переменных для указанного формата, то при попытке записать строку в поток или извлечь отформатированную строку будет выброшено исключение
format_error
(или подкласс thereof
).
Класс
format
достаточно мощен и содержит слишком много возможностей форматирования, чтобы их можно было описать здесь, и его стоит изучить. Чтобы скачать Boost или почитать документацию, посетите web-сайт Boost по адресу www.boost.org.
Также для преобразования чисел из числовых типов в строки можно использовать
sprintf
или аналогичные ей функции. Обычно этого следует избегать, так как это небезопасно и для этого имеются лучшие альтернативы.
Глава 10.
Имеется строка
string
и требуется определить, содержит ли она допустимое число.
Для проверки допустимости числа можно использовать шаблон функции
lexical_cast
библиотеки Boost. При таком подходе допустимое число может включать предшествующий знак минус, предшествующий знак плюс, но не пробел. В примере 3.5 приводятся несколько образцов типов форматов, с которыми работает lexical_cast
.
Пример 3.5. Проверка числовой строки
#include
#include
using namespace std;
using boost::lexical_cast;
using boost::bad_lexical_cast;
template
bool isValid(const string& num) {
bool res = true;
try {
T tmp = lexical_cast(num);
} catch (bad_lexical_cast &e) {
res = false;
}
return(res);
}
void test(const string& s) {
if (isValid(s))
cout << s << " - допустимое целое число." << endl;
else
cout << s << " - HE допустимое целое число." << endl;
if (isValid(s))
cout << s << " - допустимое число двойной точности." << endl;
else
cout << s << " - HE допустимое число двойной точности." << endl;
if (isValid(s))
cout << s << " - допустимое число одинарной точности." << endl;
else
cout << s << " - HE допустимое число одинарной точности " << endl;
}
int main() {
test("12345");
test("1.23456");
test("-1.23456");
test(" - 1.23456");
test("+1.23456");
test(" 1.23456 ");
test("asdf");
}
Вот вывод этого примера.
12345 - допустимое целое число.
12345 - допустимое число двойной точности.
12345 - допустимое число одинарной точности.
1.23456 - НЕ допустимое целое число.
1.23456 - допустимое число двойной точности.
1.23456 - допустимое число одинарной точности.
-1.23456 - НЕ допустимое целое число.
-1.23456 - допустимое число двойной точности.
-1.23456 - допустимое число одинарной точности.
- 1.23456 - НЕ допустимое целое число.
- 1 23466 - НЕ допустимое число двойной точности.
- 1.23456 - НЕ допустимое число одинарной точности.
+1.23456 - НЕ допустимое целое число.
+1.23456 - допустимое число двойной точности.
+1.23456 - допустимое число одинарной точности.
1.23456 - НЕ допустимое целое число.
1.23456 - НЕ допустимое число двойной точности.
1.23456 - НЕ допустимое число одинарной точности.
asdf - НЕ допустимое целое число.
asdf - НЕ допустимое число двойной точности.
asdf - НЕ допустимое число одинарной точности.
Шаблон функции
lexical_cast
преобразует значение из одного типа в другой. Он объявлен следующим образом.
template
Target lexical_cast(Source arg)
Source
— это тип оригинальной переменной, a Target
— это тип переменной, в которую значение преобразуется. Таким образом, например, чтобы преобразовать из string
в int
, вызов lexical_cast
имеет вид:
int i = lexical_cast(str); // str - это строка
lexical_cast
проводит анализ и пытается выполнить преобразование. Если преобразование невозможно, он выбрасывает исключение bad_lexical_cast
. В примере 3.5 я только хочу проверить допустимость, и мне не требуется сохранять целевую переменную, так что если исключение не выбрасывается, я возвращаю true
, а в противном случае — false
.
В
lexical_cast
требуется передать только первый аргумент, поскольку это шаблон, что означает, что компилятор может догадаться, какой тип имеет аргумент функции, и использовать его в качестве второго аргумента. Пояснение этой ситуации более сложно, чем простая демонстрация, так что позвольте мне использовать фрагмент кода примера. Вместо того чтобы вызывать lexical_cast
, как в предыдущем фрагменте кода, можно сделать так.
int i = lexical_cast(str);
Это означает то же самое, но указывать аргумент
string
не требуется, так как компилятор видит, что str
— это string
, и понимает, что от него требуется дальше.
Если вы собираетесь написать аналогичную функцию-обертку для проверки допустимости, возвращающую
true
и false
, ее также можно написать как шаблон функции. В этом случае ее потребуется написать только один раз с использованием параметризованного типа, а различные версии будут генерироваться при каждом ее использовании с различными типами.
lexical_cast
также удобен для преобразования из одного числового типа в другой. Более подробно это обсуждается в рецепте 3.6.
Рецепт 3.6.
Требуется сравнить значения с плавающей точкой, но при этом выполнить сравнение на равенство, больше чем или меньше чем с ограниченным количеством десятичных знаков. Например, требуется, чтобы 3.33333 и 3.33333333 считались при сравнении с точностью 0.0001 равными.
Напишите свои функции сравнения, которые принимают в качестве параметра ограничение точности сравнения. Пример 3.6 показывает основную методику, используемую в такой функции сравнения.
Пример 3.6. Сравнение чисел с плавающей точкой
#include
#include // для fabs()
using namespace std;
bool doubleEquals(double left, double right, double epsilon) {
return (fabs(left - right) < epsilon);
}
bool doubleLess(double left, double right, double epsilon,
bool orequal = false) {
if (fabs(left - right) < epsilon) {
// В рамках epsilon, так что считаются равными
return (orequal);
}
return (left < right);
}
bool doubleGreater(double left, double right, double epsilon,
bool orequal = false) {
if (fabs(left - right) < epsilon) {
// В рамках epsilon, так что считаются равными
return (orequal);
}
return (left > right);
}
int main() {
double first = 0.33333333;
double second = 1.0 / 3.0;
cout << first << endl;
cout << second << endl;
// Тест на прямое равенство. Не проходит тогда, когда должно проходить.
// (boolalpha печатает булевы значения как "true" или "false")
cout << boolalpha << (first == second) << endl;
// Новое равенство. Проходит так, как требуется в научном приложении.
cout << doubleEquals(first, second, .0001) << endl;
// Новое меньше чем
cout << doubleLess(first, second, .0001) << endl;
// Новое больше чем
cout << doubleGreater(first, second, .0001) << endl;
// Новое меньше чем или равно
cout << doubleLess(first, second, .0001, true) << endl;
// Новое больше чем или равно
cout << doubleGreater(first, second, .0001, true) << endl;
}
Далее показан вывод этого примера.
0.333333
0.333333
false
true
false
false
true
true
Код примера 3.6 начинается с двух значений — 0.33333333 и того, что компьютер получает в результате деления 1.0 / 3.0. Он с помощью форматирования по умолчанию
cout
печатает эти два значения. Они кажутся одинаковыми и равными 0.333333. Однако при сравнении этих двух значений они оказываются различными. Значение 1.0 / 3.0 имеет больше значащих цифр, чем 0.33333333, и, следовательно, как полагает машина, эти два числа не равны. Однако в некоторых приложениях может потребоваться, чтобы они считались равными.
Чтобы добиться этого, надо написать собственные функции сравнения чисел с двойной точностью:
doubleLess
, doubleEquals
и doubleGreater
, каждая из которых принимает в качестве параметров два значения типа double
. Кроме того, doubleLess
и doubleGreater
имеют дополнительный параметр, который при его равенстве true
приводит к тому, что эти функции ведут себя как «меньше или равно» и «больше или равно» соответственно.
Чтобы заставить эти функции учитывать точность, рассмотрим функцию
doubleEquals
. Вместо того чтобы проверять на равенство, эта функция проверяет, находится ли разность двух чисел в указанном пользователем диапазоне epsilon
. (В качестве epsilon
пример использует значение 0.0001.) Если это так, то функция возвращает значение true, что означает, что значения одинаковы. Таким образом, равными окажутся значения 0.3333, 0.33333, 0.333333, 0.33333333333 и 0.33333323438.
Чтобы выполнить операцию «меньше чем» и «больше чем», вначале проверьте, не равны ли значения, как это делается в функции
doubleEquals
. Если так, то при наличии теста на равенство верните true
, а в противном случае — false
. В противном случае выполните прямое сравнение.
Имеется строка, содержащая число в экспоненциальной форме, и требуется сохранить значение числа в переменной типа
double
.
Наиболее простым способом анализа числа в экспоненциальной форме является использование встроенного в библиотеку C++ класса
stringstream
, объявленного в
, как показано в примере 3.7.
Пример 3.7. Лексический анализ числа в экспоненциальной форме
#include
#include
#include
using namespace std;
double sciToDub(const strings str) {
stringstream ss(str);
double d = 0;
ss >> d;
if (ss.fail()) {
string s = "Невозможно отформатировать ";
s += str;
s += " как число!";
throw (s);
}
return (d);
}
int main() {
try {
cout << sciToDub("1.234e5") << endl;
cout << sciToDub("6.02e-2") << endl;
cout << sciToDub("asdf") << endl;
} catch (string& e) {
cerr << "Ошибка: " << e << endl;
}
}
Далее показан вывод этого кода.
123400
0.0602
Ошибка: невозможно отформатировать asd как число!
Класс
stringstream
— это string
, который ведет себя как поток (что неудивительно). Он объявлен в
. Если требуется выполнить анализ string
, содержащей число в экспоненциальной форме (см. также рецепт 3.2), то с этой работой прекрасно справится stringstream
. Стандартные классы потоков уже «знают», как анализировать числа, так что не тратьте без острой необходимости время на повторную реализацию этой логики.
В примере 3.7 я написал простую функцию
sciToDub
, принимающую параметр типа string
и возвращающую содержащийся в ней double
, если он допустим. В sciToDub
я использую stringstream
следующим образом.
stringstream ss(str); // Конструирование из строки типа string
double d = 0;
ss >> d;
if (ss.fail()) {
string s = "Невозможно отформатировать ";
s += str;
s += " как число!";
throw (s);
}
return (d);
Наиболее важной частью здесь является то, что все, что требуется сделать, — это использовать для чтения из строкового потока в
double
оператор сдвига вправо (>>
), как это делается при чтении из cin
.
Ну, это не совсем все, что требуется сделать. Если в
stringstream
записано значение, которое не может быть записано в переменную в правой части оператора >>
, то для потока будет выставлен бит fail
. Этот бит можно проверить с помощью функции-члена fail
(на самом деле это функция-член basic_ios
, который является родительским классом для stringstream
). Кроме того, переменная справа от оператора >>
в случае ошибки значения не меняет.
Однако с целью обобщения можно избежать написания отдельных версий
sciToDub
для типов int
, float
, double
и чего-либо еще, что может потребоваться преобразовать, если написать шаблон функции. Рассмотрим такую новую версию.
template
T strToNum(const string& str) {
stringstream ss(str);
T tmp;
ss >> tmp;
if (ss.fail()) {
string s = "Невозможно отформатировать ";
s += str;
s += " как число!";
throw (s);
}
return (tmp);
}
Теперь, чтобы преобразовать
string
в числовой тип, можно сделать так.
double d = strToNum("7.0");
float f = strToNum("7.0");
int i = strToNum("7.0");
Также параметром шаблона можно сделать тип символов, но это очень просто сделать, так что я оставляю это в качестве вашего упражнения.
Рецепт 3.2.
Имеется число одного типа и требуется преобразовать его в другой, как
int
в short
или наоборот, но при этом необходимо перехватывать все ошибки переполнения (overflow) или потери значимости (underflow), возникающие при работе программы.
Используйте шаблон класса
numeric_cast
Boost. Он выполняет проверки, которые при переполнениях переменной, принимающей значение, или других ошибках выбрасывают исключение типа bad_numeric_cast
. Пример 3.8 показывает, как это выполняется.
Пример 3.8. Безопасное преобразование чисел
#include
#include
using namespace std;
using boost::numeric_cast;
using boost::bad_numeric_cast;
int main() {
// Целые типы
try {
int i = 32767;
short s = numeric_cast(i);
cout << "s = " << s << endl;
i++; // Теперь i выходит за диапазон (если sizeof(short) равен 2)
s = numeric__cast(i);
} catch (bad_numeric_cast& e) {
cerr << e.what() << endl;
}
try {
int i = 300;
unsigned int ui = numeric_cast(i);
cout << ui << endl; // Прекрасно
i *= -1;
ui = numeric_cast(i); // i отрицателен!
} catch (bad_numeric_cast& e) {
cerr << e.what() << endl;
}
try {
double d = 3.14.
int i = numeric_cast(d);
i = numeric_cast(d); // Это отрезает 0.14!
cout << i << endl; // i = 3
} catch (bad_numeric_cast& e) {
cerr << e.what( ) << endl;
}
}
Вы, вероятно, знаете, что базовые типы C++ имеют различные размеры. Стандарт C++ содержит жесткие указания по относительному размеру типов:
int
всегда не короче, чем short int
, но он не указывает абсолютных размеров. Это означает, что если взять long int
и попытаться записать его значение в short
или попытаться поместить int
в unsigned int
, то информация о значении переменной-источника, такая как знак или даже часть числового значения, может быть потеряна.
Только знания, что это может привести к проблемам, не достаточно. Вы можете быть ограничены жесткими требованиями по объему и не захотите использовать четыре байта для
long
, когда можно обойтись двумя байтами для short
(если ваша платформа на самом деле использует такие размеры, что очень распространено, но не гарантируется). Из-за ограничений по объему может возникнуть желание попробовать хранить значения в наименьших возможных типах. Если вы любите приключения, но вам нужна страховка, для перехвата потерь данных при работе программы используйте numeric_cast
из Boost.
Синтаксис
numeric_cast
очень прост. Это шаблон функции, объявленный следующим образом.
template
inline Target numeric_cast(Source arg)
Если вы уже прочли рецепты 3.1 и 3.3, он аналогичен
lexical_cast
. У него имеется два параметра шаблона — Target
и Source
, — которые представляют типы оригинального и результирующего значений. Так как это шаблон функции, компилятор может догадаться о типе аргумента Source
, так что требуется указать только Target
, как здесь.
int i = 32767;
short s = numeric_cast(i);
short
— это аргумент, передаваемый в шаблон как параметр Target
. Компилятор догадывается, что Source
имеет тип int
потому, что i
имеет тип int
.
В этом случае я впихиваю
int
в short
. В моей системе (Windows XP) int имеет длину четыре байта, a short
— два. short
имеет знак, это означает, что для представления числа в нем используется 15 бит и, следовательно, максимальным допустимым положительным значением для него является 32 767. Приведенный выше фрагмент кода работает молча, но когда я увеличиваю i
на единицу, она выходит за диапазон short
.
s = numeric_cast(i); // Ох!
Вы уже догадались, что выбрасывается исключение
bad_numeric_cast
. Смотри остальную часть примера 3.8: numeric
_cast также перехватывает потери знака, возникающие при присвоении отрицательного значения со знаком типу без знака.
Но
numeric_cast
не решает всех проблем. Если попытаться поместить значение с плавающей точкой в тип без плавающей точки, то будет потеряно все, что находится справа от десятичной точки, так? numeric_cast
в этой ситуации не спасает, так что не думайте, что он сможет уберечь вас от всех рискованных предприятий. Например, рассмотрим такой фрагмент кода из примера 3.8:
double a = 3.14;
int i = numeric_cast(d); // Ох!
Здесь не будет выброшено никаких исключений. Но это произойдет, если попробовать такое:
double d = -3.14;
unsigned int ui = numeric_cast(d);
Потому что, несмотря на то что происходит потеря всего, что находится справа от десятичной точки, происходит потеря знака, а это очень плохо.
Рецепты 3.1 и 3.3.
Требуется узнать наибольшее и наименьшее значения, представляемые на данной платформе числовым типом, таким как
int
или double
.
Чтобы среди прочего получить максимальное и минимальное допустимые значения числового типа, используйте шаблон класса
numeric_limits
из заголовочного файла
(см. пример 3.9).
Пример 3.9. Получение числовых ограничений
#include
#include
using namespace std;
template
void showMinMax() {
cout << "min: " << numeric_limits::min() << endl;
cout << "max: " << numeric_limits::max() << endl;
cout << endl;
}
int main() {
cout << "short:" << endl;
showMinMax();
cout << "int:" << endl;
showMinMax();
cout << "long:" << endl;
showMinMax();
cout << "float:" << endl;
showMinMax();
cout << "double:" << endl;
showMinMax();
cout << "long double:" << endl;
showMinMax();
cout << "unsigned short:" << endl;
showMinMax();
cout << "unsigned int:" << endl;
showMinMax();
cout << "unsigned long:" << endl;
showMinMax();
}
Вот что я получил в Windows XP, используя Visual C++ 7.1.
short:
min: -32768
max: 32767
int:
min: -2147483648
max: 2147483647
long:
min -2147483648
max 2147483647
float:
min: 1.17549e-038
max: 3.40282e-038
double:
min: 2.22507e-308
max: 1.79769e+308
long double:
min: 2.22507e-308
max: 1.79769e+308
unsigned short:
min: 0
max: 65535
unsigned int:
min: 0
max: 4294967295
unsigned long:
min: 0
max: 4294967295
Пример 3.9 показывает простой пример получения минимального и максимального значений встроенных числовых типов. Шаблон класса
numeric_limits
имеет специализации для всех встроенных типов, включая как числовые, так и нечисловые типы. Стандарт требует, чтобы все типы, которые я использовал в примере 3.9, а также перечисленные далее, имели свою специализацию numeric_limits
.
bool
char
signed char
unsigned char
wchar_t
min
и max
— это функции-члены numeric_limits
типа static
, которые возвращают наименьшее и наибольшее значения для типа переданного им параметра.