Dès qu’on a un pied dans l’informatique, on croise généralement assez rapidement des bases numériques autres que le décimal.
Le premier contact, c’est généralement soit le binaire, soit de l’hexadécimal, utilisé notamment pour les couleurs web, façon #FF00A6. C’est normal : le binaire ou base 2 est la langue de base des composants informatiques, et les bases 8 et 16, en tant que puissances de 2, sont des bases de choix pour communiquer avec le binaire.
Quelle que soit votre raison de passer d’une base à l’autre, il peut être utile de connaître le principe de conversion et d’avoir une fonction prête à l’emploi pour cet usage. C’est ce que nous allons voir ci-dessous.
Dans cet article
Principe d’une base N
Sans aller jusqu’à réexpliquer le principe des bases arithmétiques (je vous renvoie pour ça à l’article Wikipédia), juste un point explicatif : la base détermine en gros quel est notre « chiffre maximum » pour chaque position dans l’écriture d’un chiffre.
Donc, en base 10, c’est une fois arrivé au dixième chiffre que le chiffre de droite repart à 0 et qu’on ajoute 1 au chiffre de gauche. En base 8, c’est lorsque nous arrivons au huitième chiffre…
Donc nous comptons comme ça : 0, 1, 2, 3, (…), 6, 7, 10, 11, 12, (…), 16, 17, 20, 21…
De même, en hexadécimal (ou base 16), nous allons jusqu’au 16e chiffre avant d’augmenter le chiffre de gauche. Nous complétons les chiffres 0 à 9 par les lettres de A à F. Là, nous comptons comme suit : 0, 1, 2, 3, (…), 6, 9, A, B, C, D, E, F, 10, 11, 12, (…), 18, 19, 1A, 1B, 1C…
Ça, c’est l’explication simple. En arithmétique, ça se traduit par l’explication suivante : pour obtenir l’équivalent décimal depuis la base N, chaque chiffre, de droite à gauche, doit être multiplié par N élevé à la puissance du rang (le chiffre tout à droite étant au rang 0).
Donc, si on écrit 5FB2 en base 16, c’est l’équivalent d’écrire :
5 × 163 + F × 162 + B × 161 + 6 × 160
Donc, avec F valant 15 et B valant 11,
101101 en binaire, c’est, en décimal :
1 × 25 + 0 × 24 + 1 × 23 + 1 × 22 + 0 × 21 + 1 × 20 = 45.
Méthodes de conversion natives, pour les bases 2, 8 et 16
Commençons par le plus simple : la classe système Convert incluse dans C# dispose d’une méthode pour convertir un entier décimal en chiffre de base N. C’est Convert.ToString(Int32, Int32), en utilisant deux entiers en paramètres :
public static string Convert.ToString(int value, int toBase);
Cependant, cette méthode ne fonctionne qu’avec les bases 2, 8, 10 et 16 (donc toBase doit être une de ces quatre valeurs). Autre point : les valeurs hexadécimales sont exprimées avec les lettres en minuscules, donc .ToUpper()
string hexValue = Convert.ToString(decValue, 16)).ToUpper();
Nous avons aussi la fonction inverse, qui convertit un nombre au format chaîne de caractères en base N vers un décimal. La méthode est Convert.ToInt32(String, Int32) :
Là aussi, ça ne fonctionne qu’avec les valeurs 2, 8, 10 et 16. Ça couvre en effet la grosse majorité des besoins, mais quid des autres ?
Fonction de conversion pour n’importe quelle base N jusqu’à 36
La méthode ConvertToBase ci-dessous permet de faire une conversion de la valeur value avec n’importe quel base de 2 à 36.
Pourquoi 36 ? Parce que c’est la base maximale qu’on peut écrire avec les chiffres de 0 à 9 et les 26 lettres de l’aphabet latin.
public static string ConvertToBase(int value, int toBase)
{
string symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (toBase < 2 || toBase > 36)
{
throw new ArgumentOutOfRangeException("toBase", "Base must be between 2 and 36");
}
string baseNumber = "";
while (value > 0)
{
baseNumber = symbols[(int)(value % toBase)] + baseNumber;
value /= toBase;
}
return baseNumber == "" ? "0" : baseNumber;
}
Pour cette méthode, on s’appuie sur la méthode des divisions successives. baseNumber est le chiffre value (décimal) écrit en base toBase. À chaque étape, on divise le quotient (value) par la base. On écrit sur baseNumber le reste de cette division (value % toBase), en utilisant notre chaîne symbols pour choisir le caractère.
Puis, notre quotient devient le résultat entier de cette division (comme value est un entier, ce n’est que la partie entière de la division qui est stockée dans value).
La valeur 0 est gérée dans « return », mais on pourrait tout aussi bien la gérer au début de la fonction (si value = 0, il faut toujours retourner « 0 »).
La méthode suivante fait l’inverse : elle convertit un chiffre au format chaîne de caractères vers sa valeur décimale, selon la base N fromBase.
Là, on utilise tout simplement la méthode arithmétique : on lit les chiffres de droite à gauche et on les multiplie par la base N élevée à la puissance du rang i. En somme, ABC(base N) = A × N2 + B × N1 + C × N0.
public static int ConvertFromBase(string value, int fromBase)
{
if (fromBase < 2 || fromBase > 36)
{
throw new ArgumentOutOfRangeException("fromBase", "Base must be between 2 and 36");
}
string symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
value = value.ToUpper();
if (!value.All(c=> symbols.Substring(0, fromBase).Contains(c)))
{
throw new ArgumentException("value", "Value contains non recognized symbols.");
}
int decimalNumber = 0;
for (int i = 0; i < value.Length; i++)
{
decimalNumber += symbols.IndexOf(value[value.Length - 1 - i]) * (int)Math.Pow(fromBase, i);
}
return decimalNumber;
}
Il y a un peu plus de particularités sur cette méthode, notamment liées au fait que nous recevons une chaîne en entrée. Nous mettons toute la chaîne en majuscule et nous vérifions si elle est correcte. Pour ça, on tronque la liste des symboles pour n’avoir que les symboles de 0 à N, et on utilise LINQ avec une fonction lambda pour nous assurer que tous les symboles font bien partie de la base. Ainsi, si quelqu’un essaie de convertir depuis la base 12 un qui contient n’importe quelle lettre autre que A et B, la méthode lance une Exception.
Bien sûr, on pourrait choisir de rendre cette méthode plus stricte en enlevant value = value.ToUpper(). Dans ce cas, une valeur hexadécimale correcte serait 5BF2 mais pas 5bf2.
Pour aller plus loin : au-delà de 36 ?
Nous nous arrêtons à 36 car c’est une convention en conversion de base (liée principalement aux 10 chiffres et 26 lettres disponibles dans l’alphabet latin). Sur les deux méthodes précédentes, on peut imaginer des bases à volonté, en ajoutant des symboles à la chaîne de caractères proposées, mais dans ce cas, il n’existe pas de convention universelle.
Ce que nous dit Wikipédia, c’est que au-delà de la base 36, on considère que chaque chiffre peut être représenté sous la forme d’un nombre décimal. Dans ce cas, exit les lettres, on considère que 10, 11 ou 42 (par exemple) sont les chiffres de notre base. En base 60, on peut noter le nombre 7510 comme suit : [1; 15]60.
Dans un programme, pour ce cas de figure, nous utiliserons typiquement un tableau d’entiers, qui associe chaque index à une puissance de N. Ainsi, si notre chiffre en base 60 est stocké dans un tableau d’entiers, on pourra calculer sa valeur décimale ainsi :
int[] numBase60 = new int[]
{
// Definition du nombre, avec des entiers entre 0 et 59
};
int decValue = 0;
for (int i = 0; i < numBase60.Length; i++)
{
decValue += numBase60[i] * (int)Math.Pow(60, i);
}
Et pour l’afficher, on peut utiliser string.Join(), sans oublier d’inverser le tableau, étant donné que notre indice 0 doit être présenté tout à droite :
string showNumber = "[" + string.Join("; ", numBase60.Reverse()) + "]";
Console.WriteLine(showNumber);
// Chiffre en base 60 montré sous la forme : [6; 59; 0; 21]
Mais dans ce cas de figure, nous avons probablement plutôt intérêt à créer une classe dédiée pour manipuler de manière plus stricte ce type de nombre et s’assurer que nous sommes toujours dans la bonne base. Et ceci est une autre histoire !