Ce qui suit s'intéresse aux raisons de certaines opérations de transtypage en C#, mais touche aussi légèrement à la question de la signature des fonctions, mais en surface seulement, sans l'approfondir. Les fonctions constituent un sujet très important en programmation, et méritent un traitement de meilleure qualité que ce qui suit.
Soit le programme suivant, qui a pour rôle de lire une base , un exposant , puis de calculer et d'afficher , le tout exprimé d'abord en pseudocode, puis en code C# « naïf » (au sens de « trop simple pour être vrai ») :
Pseudocode | Code C# (naïf) |
---|---|
|
|
Tout cela semble simple et évident, or si le pseudocode est tout à fait convenable, le code C# nous met face à la réalité de la technique et refuse de compiler, produisant le message d'erreur suivant :
Impossible de convertir implicitement le type 'double' en 'float'. Une conversion explicite existe (un cast est-il manquant ?)
L'erreur est rapportée pour la ligne 21 du programme, plus précisément à l'appel de Math.Pow().
Si nous examinons la signature de Math.Pow(), nous constatons ce qui suit :
public static double Pow(double x, double y)
^^^^^^^^ ^^^^^^^^
Sans décortiquer cette signature dans le détail (nous y reviendrons), constatons :
Remarquons que notre programme, lui, utilise des float (nombres à virgule flottante, simple précision) pour les paramètres utilisés pour appeler la fonction. Ainsi, l'écriture suivante :
Math.Pow(valBase, valExposant);
... demande au compilateur C# de prendre nos deux float et d'en faire des double. Il se trouve que, pour C#, cette opération est implicitement légale : pour C#, il s'agit d'une promotion, où les valeurs à l'appel gagnent en précision lorsque l'appel se réalise.
Exprimé en mots, on demande ici à C# de prendre valBase et de le transtyper de float en double, puis de prendre valExposant et de le transtyper lui aussi de float en double. Ce transtypage est implicite car aux yeux du compilateur, il n'est pas dangereux. Ainsi, ce n'est pas la raison du problème.
La signature ne se termine pas là. Réexaminons-la à nouveau :
public static double Pow(double x, double y)
^^^^^^
Constatons donc le mot double placé avant le nom de la fonction (Pow). Ce mot indique le type du résultat de l'exécution de la fonction, c'est-à-dire ce que nous obtiendrons suite à un appel de la fonction. Ici, la fonction nous indique qu'elle retournera un double, or si nous réexaminons notre programme C# :
//
// Fichier : Exponentiation.cs
// Auteur : votre chic prof
// Rôle : calculer et afficher le fruit d'une exponentiation
// Date : 2 septembre 2013
//
using System;
float valBase;
float valExposant;
float résultat;
// ^^^^^^^^^^^
Console.Write("Base? ");
valBase = float.Parse(Console.ReadLine());
Console.Write("Exposant? ");
valExposant = float.Parse(Console.ReadLine());
résultat = Math.Pow(valBase, valExposant);
// ^^^^^
Console.WriteLine("{0}^{1}=={2}", valBase, valExposant, résultat);
... nous pouvons constater que la variable qui recevra le fruit du calcul réalisé par Math.Pow() est de type float. Ceci signifie que notre programme cherche à déposer un double, donc un nombre à virgule flottante de double précision, dans une variable dont le type est float, donc un nombre à virgule flottante de simple précision. Aux yeux de C#, cette opération est risquée, car elle entraîne manifestement une perte d'information.
Nous venons de trouver notre coupable!
Deux grandes options s'offrent à nous :
Examinons brièvement chacune de ces deux options.
Si nous changeons le type de la variable résultat, alors le programme devient :
//
// Fichier : Exponentiation.cs
// Auteur : votre chic prof
// Rôle : calculer et afficher le fruit d'une exponentiation
// Date : 2 septembre 2013
//
using System;
float valBase;
float valExposant;
double résultat;
// ^^^^^^^^^^^^
Console.Write("Base? ");
valBase = float.Parse(Console.ReadLine());
Console.Write("Exposant? ");
valExposant = float.Parse(Console.ReadLine());
résultat = Math.Pow(valBase, valExposant);
Console.WriteLine("{0}^{1}=={2}", valBase, valExposant, résultat);
Ce programme compile sans peine, et utilise une variable résultat dont la taille est deux fois celle qui était choisie à l'origine. Ce choix est simple, fait le travail, mais il requiert trop d'espace pour nos besoins, à moins bien sûr d'être intéressé(e)s par la quinzième décimale du calcul.
Ce qui est sans doute plus près de nos intentions est de nos besoins initiaux est de faire en sorte que le double résultant du calcul fait par Math.Pow() soit « forcé » à entrer dans un float, avec les pertes de décimales que ceci implique.
Une telle conversion se nomme un transtypage (on dit souvent Cast, ou Type Cast, selon l'appellation anglaise).
Avec un transtypage, la programmeuse ou le programmeur impose ses vues au compilateur; dans notre exemple, elle ou il lui indique au compilateur que le résultat du calcul fait par la fonction Math.Pow() doit être converti en float, de telle sorte que le fruit de cette conversion puisse être affecté sans problème à la variable résultat, elle-même float.
Le code résultant devient :
//
// Fichier : Exponentiation.cs
// Auteur : votre chic prof
// Rôle : calculer et afficher le fruit d'une exponentiation
// Date : 2 septembre 2013
//
using System;
float valBase;
float valExposant;
float résultat;
Console.Write("Base? ");
valBase = float.Parse(Console.ReadLine());
Console.Write("Exposant? ");
valExposant = float.Parse(Console.ReadLine());
résultat = (float) Math.Pow(valBase, valExposant);
// ^^^^^^^
Console.WriteLine("{0}^{1}=={2}", valBase, valExposant, résultat);
Un transtypage permet au programme d'indiquer explicitement au compilateur le besoin de certaines conversions de types que le compilateur, de lui-même, n'aurait pas fait. Toute situation où la conversion implique, selon le langage C#, une perte d'information ou un risque implique un transtypage dans le programme.
La syntaxe est relativement simple :
Sans transtypage | Avec transtypage |
---|---|
|
|
De manière générale, en C#, écrire (T)expr signifie transtyper le résultat de l'expression expr dans le type T. Notez que cela ne change rien à l'expression expr en soi; seule l'utilisation qui en est faite est influencée par le transtypage en question.
En bref, transtyper signifie mentir. Un transtypage est un mensonge volontaire fait par le programme envers le système de types du langage, or le système de types est là pour nous protéger, pour éviter des erreurs bêtes, réduire les risques de pertes d'information, de précision, et réduire les probabilités d'opérations dangereuses pour le programme.
Conséquemment, transtypez si nécessaire, pas de manière frivole, et documentez les transtypages pour les expliquer. Les commentaires sont particulièrement à propos dans un tel cas : lorsque quiconque constatera qu'un transtypage a été fait ici ou là, les explications soutenant ce choix seront là, juste à côté.
Par exemple :
//
// Fichier : Exponentiation.cs
// Auteur : votre chic prof
// Rôle : calculer et afficher le fruit d'une exponentiation
// Date : 2 septembre 2013
//
using System;
float valBase;
float valExposant;
float résultat;
Console.Write("Base? ");
valBase = float.Parse(Console.ReadLine());
Console.Write("Exposant? ");
valExposant = float.Parse(Console.ReadLine());
résultat = (float) Math.Pow(valBase, valExposant); // une simple précision suffit pour nos besoins
Console.WriteLine("{0}^{1}=={2}", valBase, valExposant, résultat);
Voilà. Le transtypage est un outil précieux, qui permet à la programmeuse et au programmeur d'imposer sa volonté au compilateur de manière pointue et à des moments choisis. C'est un outil auquel il faut avoir recours de manière judicieuse, et dont il ne faut pas abuser.
Quelques liens pour enrichir le propos.