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èrePython/MATLABC
Vitesse d'exécutionModérée (interprété)Excellente (compilé)
Contrôle mémoireAutomatiqueManuel (précis)
Temps de développementRapidePlus long
Systèmes embarquésDifficileStandard
Bibliothèques scientifiquesNumPy, SciPyBLAS, 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

hello.cc
#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

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

TypeTaille typiquePlage
int4 octets à
long8 octets à
unsigned int4 octets0 à

Types flottants

TypeTaillePrécisionPlage
float4 octets~7 chiffres
double8 octets~15 chiffres
long double16 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.

types.cc
#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

operations.cc
#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 :

tableaux.cc
#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)

matrices.cc
#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

c
// 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

c
// Boucle tant que condition vraie
while (erreur > tolerance) {
  // Itérer
  erreur = calculer_erreur();
}

Conditions

c
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

fonctions.cc
#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

trapezes.cc
#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 :

allocation.cc
#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

matrice_dynamique.cc
#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

produit_mv.pypython
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")
produit_mv.cc
#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 double pour les calculs scientifiques
  • N'oubliez pas -lm pour lier la bibliothèque math
  • Gérez la mémoire : chaque malloc doit avoir son free
  • L'option -O3 du compilateur peut accélérer le code de 2-10×

Aide-mémoire

Python/NumPyC
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);