Глава 3 Числа

3.0. Введение

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

Некоторые из рецептов содержат методики преобразования из числовых типов в тип

string
и обратно чисел, представленных в различных форматах (шестнадцатеричном, с плавающей точкой или экспоненциальном). Самостоятельное написание кода для таких преобразований утомительно и требует времени, так что я показываю возможности стандартной библиотеки или одной из библиотек Boost, облегчающие выполнение этих задач. Также имеется несколько рецептов по работе исключительно с числовыми типами: безопасное преобразование между ними, сравнение чисел с плавающей точкой с граничными значениями и поиск минимального и максимального значений.

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

3.1. Преобразование строки в числовой тип

Проблема

Имеются числа в строковом формате, и вам требуется преобразовать их в числовой тип, такой как

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.

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.

3.3. Проверка, содержит ли строка допустимое число

Проблема

Имеется строка

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.4. Сравнение чисел с плавающей точкой с ограниченной точностью

Проблема

Требуется сравнить значения с плавающей точкой, но при этом выполнить сравнение на равенство, больше чем или меньше чем с ограниченным количеством десятичных знаков. Например, требуется, чтобы 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
. В противном случае выполните прямое сравнение.

3.5. Лексический анализ строки, содержащей число в экспоненциальной форме

Проблема

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

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.

3.6. Преобразования между числовыми типами

Проблема

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

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.

3.7. Получение минимального и максимального значений числового типа

Проблема

Требуется узнать наибольшее и наименьшее значения, представляемые на данной платформе числовым типом, таким как

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
, которые возвращают наименьшее и наибольшее значения для типа переданного им параметра.

Загрузка...