Bases de C pour le calcul scientifique
Leçon optionnelle — Introduction au langage C pour l'analyse numérique
Pourquoi apprendre le C ?
Python et MATLAB sont excellents pour le prototypage, mais quand la performance est critique, le C reste incontournable :
| Critère | Python/MATLAB | C |
|---|---|---|
| Vitesse d'exécution | Modérée (interprété) | Excellente (compilé) |
| Contrôle mémoire | Automatique | Manuel (précis) |
| Temps de développement | Rapide | Plus long |
| Systèmes embarqués | Difficile | Standard |
| Bibliothèques scientifiques | NumPy, SciPy | BLAS, LAPACK, GSL |
Le C dans l'industrie
Les simulateurs de vol (CAE), les moteurs de jeux vidéo, les systèmes temps réel et les supercalculateurs utilisent massivement le C (ou C++) pour les parties critiques.
Premier programme
#include <stdio.h>
int main() {
printf("Hello, analyse numérique!\n");
return 0;
}Compilation et exécution :
gcc hello.c -o hello
./hello
Structure d'un programme C
#include <stdio.h> // Bibliothèque standard (entrées/sorties)
#include <math.h> // Fonctions mathématiques (sin, cos, sqrt, exp...)
int main() {
// Déclarations de variables
// Instructions
return 0; // Code de retour (0 = succès)
}Types de données numériques
Types entiers
| Type | Taille typique | Plage |
|---|---|---|
int | 4 octets | à |
long | 8 octets | à |
unsigned int | 4 octets | 0 à |
Types flottants
| Type | Taille | Précision | Plage |
|---|---|---|---|
float | 4 octets | ~7 chiffres | |
double | 8 octets | ~15 chiffres | |
long double | 16 octets | ~18 chiffres |
Toujours utiliser double
En calcul scientifique, utilisez toujours double sauf raison impérieuse. La précision de float est insuffisante pour la plupart des applications numériques.
#include <stdio.h>
int main() {
int n = 100;
double x = 3.14159265358979;
double y = 1.0e-10; // Notation scientifique
printf("n = %d\n", n);
printf("x = %.15f\n", x);
printf("y = %.2e\n", y);
return 0;
}Opérations et fonctions mathématiques
#include <stdio.h>
#include <math.h> // Nécessaire pour sin, cos, sqrt, etc.
int main() {
double x = 2.0;
// Opérations de base
double somme = x + 3.0;
double produit = x * 3.0;
double quotient = x / 3.0;
double puissance = pow(x, 3.0); // x³
// Fonctions mathématiques
double racine = sqrt(x);
double sinus = sin(x);
double cosinus = cos(x);
double exponentielle = exp(x);
double logarithme = log(x); // ln(x)
double log10_x = log10(x); // log₁₀(x)
double valeur_abs = fabs(-x); // |x|
printf("sqrt(2) = %.15f\n", racine);
printf("sin(2) = %.15f\n", sinus);
printf("exp(2) = %.15f\n", exponentielle);
return 0;
}Compilation avec la bibliothèque math :
gcc operations.c -o operations -lm
Le -lm lie la bibliothèque mathématique (obligatoire sous Linux).
Tableaux et vecteurs
En C, les tableaux sont des zones mémoire contiguës :
#include <stdio.h>
int main() {
// Déclaration d'un tableau de 5 doubles
double v[5];
// Initialisation
v[0] = 1.0;
v[1] = 2.0;
v[2] = 3.0;
v[3] = 4.0;
v[4] = 5.0;
// Ou directement
double w[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
// Attention : les indices commencent à 0 !
printf("Premier élément : v[0] = %.1f\n", v[0]);
printf("Dernier élément : v[4] = %.1f\n", v[4]);
// Parcours avec une boucle
double somme = 0.0;
for (int i = 0; i < 5; i++) {
somme += v[i];
}
printf("Somme = %.1f\n", somme);
return 0;
}Matrices (tableaux 2D)
#include <stdio.h>
int main() {
// Matrice 3×3
double A[3][3] = {
{1.0, 2.0, 3.0},
{4.0, 5.0, 6.0},
{7.0, 8.0, 9.0}
};
// Accès : A[ligne][colonne]
printf("A[0][0] = %.1f\n", A[0][0]); // 1.0
printf("A[1][2] = %.1f\n", A[1][2]); // 6.0
// Parcours
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%.1f ", A[i][j]);
}
printf("\n");
}
return 0;
}Boucles et conditions
Boucle for
// Boucle de 0 à n-1
for (int i = 0; i < n; i++) {
// Corps de la boucle
}
// Boucle avec pas différent
for (int i = 0; i <= 100; i += 10) {
// i = 0, 10, 20, ..., 100
}Boucle while
// Boucle tant que condition vraie
while (erreur > tolerance) {
// Itérer
erreur = calculer_erreur();
}Conditions
if (x > 0) {
printf("x est positif\n");
} else if (x < 0) {
printf("x est négatif\n");
} else {
printf("x est nul\n");
}Fonctions
#include <stdio.h>
#include <math.h>
// Déclaration d'une fonction
double f(double x) {
return sqrt(1.0 + cos(x) * cos(x));
}
// Fonction pour le produit scalaire
double produit_scalaire(double *u, double *v, int n) {
double resultat = 0.0;
for (int i = 0; i < n; i++) {
resultat += u[i] * v[i];
}
return resultat;
}
int main() {
// Utiliser f
printf("f(0) = %.10f\n", f(0.0));
printf("f(π/2) = %.10f\n", f(M_PI / 2.0));
// Utiliser produit_scalaire
double u[3] = {1.0, 2.0, 3.0};
double v[3] = {4.0, 5.0, 6.0};
printf("u·v = %.1f\n", produit_scalaire(u, v, 3)); // 32.0
return 0;
}Passage par pointeur
En C, les tableaux sont passés par référence (pointeur). La notation double *u signifie « un pointeur vers un double », qui pointe vers le premier élément du tableau.
Exemple complet : méthode des trapèzes
#include <stdio.h>
#include <math.h>
// Fonction à intégrer
double f(double x) {
return sqrt(1.0 + cos(x) * cos(x));
}
// Méthode des trapèzes
double trapezes(double a, double b, int n) {
double h = (b - a) / n;
double somme = 0.5 * (f(a) + f(b));
for (int i = 1; i < n; i++) {
double x = a + i * h;
somme += f(x);
}
return h * somme;
}
int main() {
double a = 0.0;
double b = M_PI; // π défini dans math.h
printf("Méthode des trapèzes pour ∫₀^π √(1 + cos²(x)) dx\n");
printf("%-10s %-20s\n", "n", "Approximation");
printf("----------------------------------------\n");
int valeurs_n[] = {10, 100, 1000, 10000, 100000};
int nb_tests = 5;
for (int i = 0; i < nb_tests; i++) {
int n = valeurs_n[i];
double resultat = trapezes(a, b, n);
printf("%-10d %.15f\n", n, resultat);
}
return 0;
}Compilation et exécution :
gcc trapezes.c -o trapezes -lm -O3
./trapezes
L'option -O3 active les optimisations du compilateur.
Sortie :
Méthode des trapèzes pour ∫₀^π √(1 + cos²(x)) dx
n Approximation
----------------------------------------
10 3.820181221825196
100 3.820198907023066
1000 3.820198908676698
10000 3.820198908693234
100000 3.820198908693401
Allocation dynamique de mémoire
Pour des tableaux de taille variable, on utilise l'allocation dynamique :
#include <stdio.h>
#include <stdlib.h> // Pour malloc et free
int main() {
int n = 1000;
// Allouer un vecteur de n doubles
double *v = (double *)malloc(n * sizeof(double));
// Vérifier que l'allocation a réussi
if (v == NULL) {
printf("Erreur d'allocation mémoire\n");
return 1;
}
// Utiliser le vecteur
for (int i = 0; i < n; i++) {
v[i] = (double)i;
}
// IMPORTANT : libérer la mémoire
free(v);
return 0;
}Allocation d'une matrice
#include <stdio.h>
#include <stdlib.h>
// Allouer une matrice n×m
double **allouer_matrice(int n, int m) {
double **A = (double **)malloc(n * sizeof(double *));
for (int i = 0; i < n; i++) {
A[i] = (double *)malloc(m * sizeof(double));
}
return A;
}
// Libérer une matrice
void liberer_matrice(double **A, int n) {
for (int i = 0; i < n; i++) {
free(A[i]);
}
free(A);
}
int main() {
int n = 100, m = 100;
double **A = allouer_matrice(n, m);
// Initialiser
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
A[i][j] = i + j;
}
}
printf("A[50][50] = %.1f\n", A[50][50]);
liberer_matrice(A, n);
return 0;
}Fuites de mémoire
Toute mémoire allouée avec malloc doit être libérée avec free. Sinon, on crée une fuite de mémoire : la mémoire reste occupée jusqu'à la fin du programme.
Comparaison Python vs C
Produit matrice-vecteur
import numpy as np
import time
n = 2000
A = np.random.rand(n, n)
x = np.random.rand(n)
start = time.time()
for _ in range(100):
y = A @ x
print(f"Python (NumPy) : {time.time() - start:.3f} s")#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void matrice_vecteur(double **A, double *x, double *y, int n) {
for (int i = 0; i < n; i++) {
y[i] = 0.0;
for (int j = 0; j < n; j++) {
y[i] += A[i][j] * x[j];
}
}
}
int main() {
int n = 2000;
// ... allocation et initialisation ...
clock_t start = clock();
for (int k = 0; k < 100; k++) {
matrice_vecteur(A, x, y, n);
}
double temps = (double)(clock() - start) / CLOCKS_PER_SEC;
printf("C : %.3f s\n", temps);
return 0;
}Résultats typiques :
- Python (NumPy) : ~0.8 s
- C naïf : ~1.2 s
- C optimisé (-O3) : ~0.3 s
- C avec BLAS : ~0.1 s
NumPy utilise du C !
NumPy est rapide car ses opérations sont implémentées en C optimisé (BLAS/LAPACK). Le C pur peut être encore plus rapide avec les bonnes optimisations.
À retenir
Points clés
- Le C est le langage de choix pour les performances critiques
- Utilisez toujours
doublepour les calculs scientifiques - N'oubliez pas
-lmpour lier la bibliothèque math - Gérez la mémoire : chaque
mallocdoit avoir sonfree - L'option
-O3du compilateur peut accélérer le code de 2-10×
Aide-mémoire
| Python/NumPy | C |
|---|---|
import numpy as np | #include <math.h> |
x = np.zeros(n) | double *x = calloc(n, sizeof(double)); |
for i in range(n): | for (int i = 0; i < n; i++) |
np.sqrt(x) | sqrt(x) |
print(f"{x:.10f}") | printf("%.10f", x); |