Attention à la division entière !7 / 3 donne 2, et non 2.333.... Si vous voulez le résultat décimal, au moins un des opérandes doit être à virgule flottante : 7.0 / 3 donne 2.333...
Incrément et décrément : prefix vs postfix
int x {5};int a = ++x; // PREFIX: x becomes 6, then a gets 6 (increment first)int b = x++; // POSTFIX: b gets 6, then x becomes 7 (use first, then increment)// À éviter, il vaut mieux ceci, qui est plus clairint x {5};x++;int a {x};int b {x};x++;#Les 4 instructions font la même chose, i.e. ajouter 1 à x:++x;x++;x += 1;x = x + 1;
Opérateurs d'affectation composés
Opérateur
Équivalent
Exemple
+=
x = x + n
health += 10;
-=
x = x - n
health -= damage;
*=
x = x * n
score *= multiplier;
/=
x = x / n
speed /= 2;
%=
x = x % n
angle %= 360;
Priorité des opérateurs (simplifiée)
De la priorité la plus haute à la plus basse :
Priorité
Opérateurs
1
() (parenthèses)
2
++-- (préfixe), !
3
*/%
4
+-
5
<<=>>=
6
==!=
7
&&
8
||
9
=+=-= etc.
Bonne pratique : En cas de doute, utilisez des parenthèses pour clarifier l’intention.
Exemple : conversion de distances
// Converting distances#include <iostream>#include <format>int main(){ unsigned int yards {}, feet {}, inches {}; // Convert a distance in yards, feet, and inches to inches std::cout << "Enter a distance as yards, feet, and inches "; std::cout << "with the three values separated by spaces: " << std::endl; std::cin >> yards >> feet >> inches; const unsigned feet_per_yard {3}; const unsigned inches_per_foot {12}; unsigned total_inches {}; total_inches = inches + inches_per_foot * (yards * feet_per_yard + feet); std::cout << "This distance corresponds to " << total_inches << " inches." << std::endl; // Convert a distance in inches to yards, feet, and inches std::cout << "Enter a distance in inches: "; std::cin >> total_inches; feet = total_inches / inches_per_foot; inches = total_inches % inches_per_foot; yards = feet / feet_per_yard; feet = feet % feet_per_yard; // We can also use the std:format module (similar to Python's f-string formatting) std::cout << std::format("This distance corresponds to {} yards {} feet {} inches.", yards, feet, inches) << std::endl;}
Points clés à retenir :
std::cin >> lit à partir de l’entrée clavier
La division entière / donne le quotient (partie entière)
Le modulo % donne le reste
Ces deux opérateurs combinés sont parfaits pour les conversions d’unités
☝🏼
Écrivez un code similaire, cette fois-ci pour convertir mètres, centimètres et millimètres
Solution
// Converting distances#include <iostream>#include <format>int main(){ unsigned int meters {}, centimeters {}, millimeters {}; // Convert a distance in meters, centimeters, and millimeters to millimeters std::cout << "Enter a distance as meters, centimeters, and millimeters "; std::cout << "with the three values separated by spaces: " << std::endl; std::cin >> meters >> centimeters >> millimeters; const unsigned cm_per_meter {100}; const unsigned mm_per_cm {10}; unsigned total_millimeters {}; total_millimeters = millimeters + mm_per_cm * (meters * cm_per_meter + centimeters); std::cout << "This distance corresponds to " << total_millimeters << " millimeters." << std::endl; // Convert a distance in millimeters to meters, centimeters, and millimeters std::cout << "Enter a distance in millimeters: "; std::cin >> total_millimeters; centimeters = total_millimeters / mm_per_cm; millimeters = total_millimeters % mm_per_cm; meters = centimeters / cm_per_meter; centimeters = centimeters % cm_per_meter; // We can also use the std:format module (similar to Python's f-string formatting) std::cout << std::format("This distance corresponds to {} meters {} centimeters {} millimeters.", meters, centimeters, millimeters) << std::endl;}
int total {7};int count {3};double average = static_cast<double>(total) / count; // 2.333... (correct!)// Without cast: total / count = 2 (integer division!)
Ok, we know about the what of static_cast but not the why; meaning, why not just do this instead?
double average = (double)total / count; // C-style cast
Both produce the same result. The reason static_cast is preferred in modern C++ comes down to a few things:
It’s explicit about intent. The angle bracket syntax is verbose on purpose — it makes the cast impossible to miss during a code review. A C-style cast (double) can visually blend into surrounding code.
It’s safer. static_cast only performs conversions the compiler considers “reasonable” — like numeric type promotions. The C-style cast is a blunt instrument that will also attempt reinterpret_cast and const_cast behavior, meaning it can do things you didn’t intend without warning.
It’s searchable. If a bug is traced back to a cast, you can grep for static_cast in a large codebase. Good luck searching for (double) without tons of false positives.
It fails loudly when appropriate. If the conversion isn’t valid, static_cast will produce a compile error. The C-style cast will often find some interpretation and proceed silently.
Pourquoi les casts de style C sont dangereux
double pi {3.14159};int truncated = (int)pi; // C-style cast — compiles silently, loses dataint better = static_cast<int>(pi); // C++ cast — intent is clear, same result
**static_cast<>** est préférable car :
Il est recherchable dans le code (grep pour static_cast)
Il n’autorise que les conversions “logiques”
Il montre clairement l’intention — vous vouliez convertir
Exemple : calculateur de bassin (tout combiner)
// Sizing a pond for happy fishimport std;int main(){ // 0.2 square meters pond surface for every 15 cm of fish const double fish_factor { 0.2 / 0.15 }; // Area per unit length of fish (m² per m) double fish_count {}; // Number of fish double fish_length {}; // Average length of fish (cm) std::print("Enter the number of fish you want to keep: "); std::cin >> fish_count; std::print("Enter the average fish length in centimeters: "); std::cin >> fish_length; fish_length /= 100.0; // Convert cm to meters std::println(""); // Calculate the required surface area const double pond_area {fish_count * fish_length * fish_factor}; // Calculate the pond diameter from the area const double pond_diameter {2.0 * std::sqrt(pond_area / std::numbers::pi)}; // Convert to meters and centimeters const double pond_diameter_meters { std::floor(pond_diameter) }; const double pond_diameter_cm { std::round((pond_diameter - pond_diameter_meters) * 100.0) }; std::println("Pond diameter required for {} fish is {} m {} cm.", fish_count, pond_diameter_meters, pond_diameter_cm);}
Nouveaux concepts présentés :
std::sqrt() — fonction racine carrée
std::numbers::pi — constante C++20 pour pi
std::floor() — arrondit vers le bas
std::round() — arrondit au plus proche
/= affectation composée
Exercices
Exercice 1 : Énigmes de priorité, prédisez la valeur de chaque expression, puis vérifiez :
int a = 2 + 3 * 4; // ?int b = (2 + 3) * 4; // ?int c = 10 / 3; // ?int d = 10 % 3; // ?double e = 10 / 3; // ?double f = 10.0 / 3; // ?
Exercice 2 : Construire un convertisseur de température, écrivez un programme qui :
Demande à l’utilisateur une température en Celsius
Convertit en Fahrenheit : F = C * 9/5 + 32
Convertit en Kelvin : K = C + 273.15
Affiche les trois valeurs formatées à 1 décimale
Indice : Utilisez std::println("Temperature: {:.1f}", value) pour 1 décimale.
Exercice 3 : Calculatrice mixte, construisez une calculatrice qui :
Lit deux nombres (autorisez les entrées décimales)
Effectue +, -, *, / sur ces nombres
Affiche les résultats pour toutes les opérations
Gère correctement la division (pas de troncature entière)
Solutions
Solution de l'exercice 1 : Énigmes de priorité
int a = 2 + 3 * 4; // 14 (multiplication d'abord: 3*4=12, puis 2+12=14)int b = (2 + 3) * 4; // 20 (parenthèses d'abord: 5*4=20)int c = 10 / 3; // 3 (division entière, tronquée)int d = 10 % 3; // 1 (reste de la division: 10 - 3*3 = 1)double e = 10 / 3; // 3.0 (division entière d'abord → 3, puis converti en double)double f = 10.0 / 3; // 3.333... (division flottante car 10.0 est un double)
Solution de l'exercice 2 : Convertisseur de température
import std;int main(){ double a {}, b {}; std::print("Entrez le premier nombre : "); std::cin >> a; std::print("Entrez le deuxième nombre : "); std::cin >> b; std::println("{} + {} = {}", a, b, a + b); std::println("{} - {} = {}", a, b, a - b); std::println("{} * {} = {}", a, b, a * b); if (b != 0.0) std::println("{} / {} = {}", a, b, a / b); else std::println("Division par zéro impossible !");}
# ID Software, Quake II et Q_rsqrt : Ouate de Phoque ?
L’inverse de la racine carré d’un nombre, c’est l’une des opérations les plus fondamentales de toute la 3D temps réel.
La normalisation de vecteur
Normaliser un vecteur, c’est le ramener à une longueur entre 0.0 et 1.0 (un vecteur unitaire) tout en conservant sa direction.
La formule est :
v_normalisé = v / |v|
où |v| est la longueur du vecteur :
|v| = sqrt(x² + y² + z²)
Donc normaliser, c’est diviser chaque composante par cette longueur :
v_normalisé = v * (1 / sqrt(x² + y² + z²))
C’est exactement 1 / sqrt(value). L’inverse de la racine carrée n’est pas un truc de mathématicien — c’est la normalisation, exprimée de façon optimale.
Pourquoi a-t-on besoin de vecteurs unitaires partout ?
L’éclairage (Phong, Lambert, PBR)
C’est le cas le plus critique. Pour calculer si une surface est éclairée, on fait le produit scalaire (dot product) entre la normale de surface et la direction de la lumière :
intensité = dot(N, L) = N.xL.x + N.yL.y + N.z*L.z
Ce calcul ne donne un résultat correct que si N et L sont des vecteurs unitaires. Si la normale a une longueur de 2.5 au lieu de 1.0, ta surface sera 2.5× trop lumineuse. Dans Quake III, chaque pixel, chaque frame, pour chaque surface — on normalisait des vecteurs en permanence. C’est pour ça que la performance de 1/sqrt() importait autant.
Les normal maps
Chaque texel d’une normal map encode une normale. Au moment du rendu, le GPU doit normaliser ces normales après interpolation entre les vertices (l’interpolation brise la longueur unitaire). Des millions de normalisations par frame.
Les réflexions et réfractions
R = reflect(I, N) // nécessite N unitaire
Utilisé dans les shaders PBR, les ray tracers, les cubemaps.
L’animation et les quaternions
Les quaternions de rotation doivent rester normalisés pour représenter des rotations pures. Après des interpolations (SLERP, blending de poses), on renormalise pour éviter la dérive.
La physique — détection de collisions
Les normales de contact, les axes de séparation (SAT), les plans de collision — tous nécessitent des vecteurs unitaires pour que les calculs de distance et de projection soient corrects.
Le fait que Quake III faisait tourner des niveaux entiers avec des centaines d’entités éclairées en temps réel en 1999 sur du matériel 200 MHz, c’est en grande partie grâce à cette optimisation. Chaque personnage, chaque mur éclairé dynamiquement — des milliers de normalisations par frame, accélérées par ce hack de 6 lignes.
Dans Blender et Unreal aujourd’hui, c’est le GPU qui fait ces millions de normalisations en parallèle, mais le principe est identique — et les shaders GLSL/HLSL ont même une fonction intégrée normalize() qui fait exactement v * inversesqrt(dot(v,v)).
Quake’s Q_rsqrt (avec son WTF et son UB), une version équivalente moderne et un appel à la fonction sqrt standard:
// Benchmark: Original Q_rsqrt (UB) vs Modern bit_cast version// Requires C++20 with standard library modules support.import std;// ─────────────────────────────────────────────// Original Quake III version (undefined behavior)// NOTE: uses ‘long’ which was 32-bit on the original// target (MSVC/x86). On 64-bit Linux, long is 64-bit,// which BREAKS the bit trick — demonstrating real UB!// We preserve the original code exactly as written.// ─────────────────────────────────────────────float Q_rsqrt_original(float number){long i;float x2, y;const float threehalfs = 1.5F;x2 = number * 0.5F;y = number;i = * ( long * ) &y; // evil floating point bit level hackingi = 0x5f3759df - ( i >> 1 ); // what the fuck?y = * ( float * ) &i;y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration of Newton's methodreturn y;}// ─────────────────────────────────────────────// Modern C++20 version (defined behavior)// ─────────────────────────────────────────────float Q_rsqrt_modern(float number){const float x2 = number * 0.5F;const float threehalfs = 1.5F;std::uint32_t i = std::bit_cast<std::uint32_t>(number);i = 0x5f3759df - ( i >> 1 );float y = std::bit_cast<float>(i);y = y * ( threehalfs - ( x2 * y * y ) );return y;}// ─────────────────────────────────────────────// std::sqrt reference (for accuracy comparison)// ─────────────────────────────────────────────float rsqrt_reference(float number){return 1.0F / std::sqrt(number);}// ─────────────────────────────────────────────// Benchmark helper// ─────────────────────────────────────────────template<typename Func>double run_benchmark(Func func, const std::vector<float>& inputs, int iterations){// Warm upvolatile float sink {};for (auto v : inputs) sink = func(v);auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < iterations; ++i) for (auto v : inputs) sink = func(v);auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> elapsed = end - start;return elapsed.count();}int main(){// Build a set of test values (avoid 0)const int sample_count { 10000 };const int iterations { 1000 };std::vector<float> inputs(sample_count);for (int i = 0; i < sample_count; ++i) inputs[i] = 0.1F + static_cast<float>(i) * 0.01F;std::cout << std::fixed << std::setprecision(6);// ── Accuracy check ──────────────────────────────────────std::cout << "\n=== Accuracy Check (first 5 values) ===\n";std::cout << std::setw(10) << "Input" << std::setw(16) << "Reference" << std::setw(16) << "Original" << std::setw(16) << "Modern" << std::setw(14) << "Orig Error%" << std::setw(14) << "Mod Error%" << '\n';std::cout << std::string(86, '-') << '\n';for (int i = 0; i < 5; ++i){ float v = inputs[i]; float ref = rsqrt_reference(v); float orig = Q_rsqrt_original(v); float mod = Q_rsqrt_modern(v); double err_orig = std::abs((orig - ref) / ref) * 100.0; double err_mod = std::abs((mod - ref) / ref) * 100.0; std::cout << std::setw(10) << v << std::setw(16) << ref << std::setw(16) << orig << std::setw(16) << mod << std::setw(13) << err_orig << "%" << std::setw(13) << err_mod << "%" << '\n';}// ── Timing benchmark ────────────────────────────────────std::cout << "\n=== Benchmark (" << sample_count << " values × " << iterations << " iterations) ===\n";double time_orig = run_benchmark(Q_rsqrt_original, inputs, iterations);double time_mod = run_benchmark(Q_rsqrt_modern, inputs, iterations);double time_ref = run_benchmark(rsqrt_reference, inputs, iterations);std::cout << std::setprecision(3);std::cout << std::setw(20) << "Original (UB): " << time_orig << " ms\n";std::cout << std::setw(20) << "Modern (C++20): " << time_mod << " ms\n";std::cout << std::setw(20) << "std::sqrt ref: " << time_ref << " ms\n";double speedup = time_ref / std::min(time_orig, time_mod);if (speedup >= 1.0) std::cout << "\nFast inverse sqrt is ~" << speedup << "x faster than 1/std::sqrt()\n";else std::cout << "\n1/std::sqrt() is ~" << (1.0 / speedup) << "x faster than Fast inverse sqrt\n";std::cout << "\nNote: with modern CPUs the gap is much smaller than in 1999!\n" << " Hardware rsqrtss instruction often beats both.\n\n";return 0;}
Travail personnel
Lecture
Lire Beginning C++23 Chapitre 2
Exercices
Exercice 1 : Énigmes de priorité
Prédisez la valeur de chaque expression, puis vérifiez :
int a = 2 + 3 * 4; // ?int b = (2 + 3) * 4; // ?int c = 10 / 3; // ?int d = 10 % 3; // ?double e = 10 / 3; // ?double f = 10.0 / 3; // ?
Exercice 2 : Construire un convertisseur de température
Écrivez un programme qui :
Demande à l’utilisateur une température en Celsius
#include <iostream>#include <format>int main(){ double a {}, b {}; std::cout << std::format("Entrez le premier nombre : "); std::cin >> a; std::cout << std::format("Entrez le deuxième nombre : "); std::cin >> b; std::cout << std::format("{} + {} = {}", a, b, a + b) << std::endl; std::cout << std::format("{} - {} = {}", a, b, a - b) << std::endl; std::cout << std::format("{} * {} = {}", a, b, a * b) << std::endl; if (b != 0.0) std::cout << std::format("{} / {} = {}", a, b, a / b) << std::endl; else std::cout << std::format("Division par zéro impossible !") << std::endl;}