Outils d'utilisateurs

Outils du Site


informations_sur_les_types

Ceci est une ancienne révision du document !


Chapitre précédent Sommaire principal Chapitre suivant

static_cast pour convertir un char en int

Obtenir des informations sur les types et les valeurs

Adapter le type des variables utilisées permet d'optimiser la mémoire en fonction des besoins, mais cela à un impact sur les valeurs que peut prendre un type donné. Il est donc indispensable d'avoir un moyen pour obtenir des informations détaillées sur les types numériques.

Pour cela, le C++ propose un ensemble de fonctionnalités dans le fichier d'en-tête limits. Ces fonctionnalités sont un peu particulières, ce sont des classes génériques, leur syntaxes utilisent des chevrons. Une telle classe, permettant d'obtenir des informations sur un type, est appelée une classe de traits. Vous allez voir dans ce chapitre comment utiliser ce type de classes, leur création sera abordé dans la partie sur la programmation objet.

Les valeurs limites des types

Valeur maximale

Par exemple, connaître la valeur maximale que peut prendre le type int, il faut utiliser la syntaxe suivante :

main.cpp
#include <iostream>
#include <limits>
 
int main() {
    std::cout << "Max(int) = " << std::numeric_limits<int>::max() << std::endl;
}

La classe de traits numeric_limits est une classe générique, qui prend un type en paramètre (int, entre chevrons). Et on appelle la fonction max de cette classe. Le résultat affiché sera :

Max(int) = 2147483647 ou 7fffffff

Cette valeur maximale signifie concrètement que lorsque vous utiliser une variable de type int pour compter, vous ne pourrez compter que jusqu'à 2147483647. Au delà, le compte ne sera plus correcte (en pratique, il reviendra à une valeur négative) :

main.cpp
#include <iostream>
 
int main() {
    int i { 2147483645 };
    std::cout << i << std::endl;
    ++i;
    std::cout << i << std::endl;
    ++i;
    std::cout << i << std::endl;
    ++i;
    std::cout << i << std::endl;
    ++i;
    std::cout << i << std::endl;
}

affiche :

2147483645
2147483646
2147483647
-2147483648
-2147483647

Comme vous le savez déjà, le C++ est permissif, il n'interdira pas ce code. C'est à vous de vérifier que vous ne dépasser pas la valeur maximale possible pour un type.

On peut alors vérifier les valeurs maximales pour les différents types numériques :

main.cpp
#include <iostream>
#include <limits>
 
int main() {
    int const char_max { std::numeric_limits<char>::max() };
    std::cout << "Max(char) = " << char_max << std::endl;
    int const unsigned_char_max { std::numeric_limits<unsigned char>::max() };
    std::cout << "Max(unsigned char) = " << unsigned_char_max << std::endl;
    std::cout << "Max(short int) = " << std::numeric_limits<short int>::max() << std::endl;
    std::cout << "Max(unsigned short int) = " << std::numeric_limits<unsigned short int>::max() << std::endl;
    std::cout << "Max(int) = " << std::numeric_limits<int>::max() << std::endl;
    std::cout << "Max(unsigned int) = " << std::numeric_limits<unsigned int>::max() << std::endl;
    std::cout << "Max(long int) = " << std::numeric_limits<long int>::max() << std::endl;
    std::cout << "Max(unsigned long int) = " << std::numeric_limits<unsigned long int>::max() << std::endl;
 
    std::cout << std::endl;
    std::cout << "Max(float) = " << std::numeric_limits<float>::max() << std::endl;
    std::cout << "Max(double) = " << std::numeric_limits<double>::max() << std::endl;
    std::cout << "Max(long double) = " << std::numeric_limits<long double>::max() << std::endl;
}

Pour les types char et unsigned char, pour éviter que cout ne les affiche comme des caractères, les valeurs maximales sont enregistrées dans une variable de type int avant d'être affichées.

Le code précédent affiche :

Max(char) = 127
Max(unsigned char) = 255
Max(short int) = 32767
Max(unsigned short int) = 65535
Max(int) = 2147483647
Max(unsigned int) = 4294967295
Max(long int) = 9223372036854775807
Max(unsigned long int) = 18446744073709551615
Max(long long int) = 9223372036854775807
Max(unsigned long long int) = 18446744073709551615

Max(float) = 3.40282e+38
Max(double) = 1.79769e+308
Max(long double) = 1.18973e+4932

Comme on s'y attend, plus un type est large en mémoire, plus sa valeur maximale sera élevée. On peut remarquer aussi que les types long int et long long int, qui ont la même taille mémoire (8 octets), ont la même valeur maximale. De même pour les versions unsigned de ces types. Pour terminer, on peut aussi remarquer que les types unsigned ont des valeurs maximales plus grandes que le type signé correspondant.

Valeur minimale

Pour obtenir les valeurs minimales d'un type, il faut utiliser les fonctions min et lowest à la place de max. Cependant, ces fonctions ne retournent pas exactement la même chose, selon que l'on utilise un type entier ou un type réel.

Commençons par les types entiers. Dans ce cas, les deux fonctions retournent la même valeur, qui correspond à la plus petite valeur que peu prendre un type. Pour les types non signées, cette valeur sera toujours 0 et pour les types signés, cette valeur sera -max-1 :

main.cpp
#include <iostream>
#include <limits>
 
int main() {
    int const char_min { std::numeric_limits<char>::min() };
    std::cout << "Min(char) = " << char_min << std::endl;
    std::cout << "Min(short int) = " << std::numeric_limits<short int>::min() << std::endl;
    std::cout << "Min(int) = " << std::numeric_limits<int>::min() << std::endl;
    std::cout << "Min(long int) = " << std::numeric_limits<long int>::min() << std::endl;
}

affiche :

Min(char) = -128
Min(short int) = -32768
Min(int) = -2147483648
Min(long int) = -9223372036854775808

Concrètement, cela veut dire, par exemple pour une variable de type int, que l'on peut compter de -2147483648 à 2147483647. Et pour un type unsigned int que l'on peut compter de 0 à 4294967295.

Dans le cas des nombres réels, les fonctions min et lowest ne retournent pas la même valeur.

  • min retourne la plus petite valeur positive non nulle (la plus petite valeur proche de 0) ;
  • lowest retourne la plus petite valeur, donc -max.

En mathématique, quelque soit $x$ appartenant à l'ensemble des nombres réels positifs non nul $\mathbb{R}^*_+$, il existe toujours un nombre plus petit que $x$ (on peut démontrer cela facilement, on montrant que $\frac x 2$ est strictement compris entre 0 et $x$). Ce qui revient à dire qu'il n'existe pas de plus petit nombre positif non nul.

En informatique, la situation est différente. Un nombre en mémoire est représenté par un nombre fini de bits, par exemple 32 pour float ou 64 bits pour double. Cela veut dire qu'il est possible représenter qu'un nombre limité de valeur possible sur un ordinateur et donc qu'il n'existe pas toujours un nombre réel $\frac x 2$. Dit autrement, cela veut dire qu'il existe une valeur minimale positive. Cette valeur est celle obtenue avec la fonction min.

main.cpp
#include <iostream>
#include <limits>
 
int main() {
    std::cout << "Min(float) = " << std::numeric_limits<float>::min() << std::endl;
    std::cout << "Min(double) = " << std::numeric_limits<double>::min() << std::endl;
    std::cout << "Min(long double) = " << std::numeric_limits<long double>::min() << std::endl;
}

affiche :

Min(float) = 1.17549e-38
Min(double) = 2.22507e-308
Min(long double) = 3.3621e-4932

Quand à la fonction lowest, elle retourne la plus petite valeur négative :

main.cpp
#include <iostream>
#include <limits>
 
int main() {
    std::cout << "Lowest(float) = " << std::numeric_limits<float>::lowest() << std::endl;
    std::cout << "Lowest(double) = " << std::numeric_limits<double>::lowest() << std::endl;
    std::cout << "Lowest(long double) = " << std::numeric_limits<long double>::lowest() << std::endl;
}

affiche :

Lowest(float) = -3.40282e+38
Lowest(double) = -1.79769e+308
Lowest(long double) = -1.18973e+4932

Exercices

  • epsilon, nextafter, nexttoward
  • digits, digits10, max_digits10, radix
  • représentation des nombres entier négatif

Informations sur les types

Il est également possible d'obtenir des informations plus générales sur les types, autre que les valeurs limites qu'elles peuvent prendre. Par exemple pour savoir si un type est signé ou non, savoir si un type est entier ou réel. L'ensemble de ces fonctionnalités est dans le fichier d'en-tête type_traits.

Fonction Rôle Exemple
is_arithmetic Teste si un type représente un nombre std::is_arithmetic<int>::value
is_integral Teste si un type représente un nombre entier std::is_integral<int>::value
is_floating_point Teste si un type représente un nombre réel std::is_floating_point<int>::value
is_signed Teste si un type est signé std::is_signed<int>::value
is_unsigned Teste si un type n'est pas signé std::is_unsigned<int>::value
is_const Teste si un type est constant std::is_const<int>::value

Ces différents code retournent un booléen, vous pouvez donc les afficher avec cout (en utilisant la directive boolalpha si vous voulez qu'il affiche “true” ou “false” en toutes lettres) et utiliser les opérateurs booléens déjà vu (inverse !, AND && et OR ||).

main.cpp
#include <iostream>
#include <type_traits>
 
int main() {
    std::cout << std::boolalpha;
    std::cout << "is_arithmetic(int) = " << std::is_arithmetic<int>::value << std::endl;
    std::cout << "is_arithmetic(bool) = " << std::is_arithmetic<bool>::value << std::endl;
 
    std::cout << std::endl;
    std::cout << "is_integral(int) = " << std::is_integral<int>::value << std::endl;
    std::cout << "is_integral(float) = " << std::is_integral<float>::value << std::endl;
 
    std::cout << std::endl;
    std::cout << "is_floating_point(float) = " << std::is_floating_point<float>::value << std::endl;
    std::cout << "is_floating_point(int) = " << std::is_floating_point<int>::value << std::endl;
 
    std::cout << std::endl;
    std::cout << "is_signed(int) = " << std::is_signed<int>::value << std::endl;
    std::cout << "is_signed(unsigned int) = " << std::is_signed<unsigned int>::value << std::endl;
 
    std::cout << std::endl;
    std::cout << "is_unsigned(unsigned int) = " << std::is_unsigned<unsigned int>::value << std::endl;
    std::cout << "is_unsigned(int) = " << std::is_unsigned<int>::value << std::endl;
 
    std::cout << std::endl;
    std::cout << "is_const(const int) = " << std::is_const<const int>::value << std::endl;
    std::cout << "is_const(int) = " << std::is_const<int>::value << std::endl;
}

affiche :

is_arithmetic(int) = true
is_arithmetic(bool) = true

is_integral(int) = true
is_integral(float) = false

is_floating_point(float) = true
is_floating_point(int) = false

is_signed(int) = true
is_signed(unsigned int) = false

is_unsigned(unsigned int) = true
is_unsigned(int) = false

is_const(const int) = false
is_const(int) = false

Pour terminer, il peut être intéressant de pouvoir comparer deux types, pour vérifier s'ils sont identiques ou non. Pour cela, vous pouvez utiliser la classe is_same de la façon suivante :

main.cpp
#include <iostream>
#include <type_traits>
 
int main() {
    std::cout << std::boolalpha;
    std::cout << "is_same(int, int) = " << std::is_same<int, int>::value << std::endl;
    std::cout << "is_same(int, long int) = " << std::is_same<int, long int>::value << std::endl;
}

affiche :

is_same(int, int) = true
is_same(int, long int) = false

Pour le moment, ces fonctionnalités permettent uniquement de vous aider à bien comprendre comment fonctionnent les types en C++. En réalité, ces fonctionnalités sont beaucoup plus utiles et puissantes que ce qui est présenté ici. En effet, par la suite, vous verrez qu'il est possible de créer du code C++ qui produit du code C++ en utilisant ce type de fonctionnalités. Cela s'appelle de la méta-programmation et c'est l'une des forces du C++.

Chapitre précédent Sommaire principal Chapitre suivant
informations_sur_les_types.1402179747.txt.gz · Dernière modification: 2014/06/08 00:22 (modification externe)