Affichage des articles dont le libellé est calcul. Afficher tous les articles
Affichage des articles dont le libellé est calcul. Afficher tous les articles

mardi 8 mai 2018

BigDecimal : pour les calculs avec une grande précision

La précision du calcul avec les nombres en virgule flottante dépend fortement de la taille (nombre de bits) de ces nombres.
Ainsi, il est très facile de montrer les limites des différents types élémentaires largement utilisés. Par exemple, le code suivant qui utilise le type float :

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  float exemple = (float)34/145;
  System.out.println(exemple);
  
 }
}

Affiche le résultat suivant :


Pour augmenter la précision, il est possible d'utiliser le type double. Le code suivant :

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  double exemple = (double)34/145;
  System.out.println(exemple);
  
 }
}

Donne le résultat suivant :


Les deux résultats sont loin d'être complets et précis. Pour atteindre une précision beaucoup plus supérieure, nous pouvons utiliser les BigDecimal. BigDecimal est une classe du package java.math. Elle permet de créer des nombres avec des grandes précisions et de faire des opérations arithmétiques sur ces nombres. Comme étant une classe, les opérateurs traditionnels (+, -, *, /, %) ne peuvent pas être appliqués directement. Il faut faire recours à des fonctions pour effectuer ces opérations.
Le code suivant fait le même calcul que les deux codes cités ci-dessus. La précision choisie est 20 chiffres après la virgule :

import java.math.BigDecimal;
import java.math.RoundingMode;

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  BigDecimal bd34 = new BigDecimal(34);
  BigDecimal bd145 = new BigDecimal(145);
  BigDecimal result = bd34.divide(bd145, 20, RoundingMode.DOWN);
  System.out.println(result.toPlainString());
  
 }
}

Il affiche le résultat suivant :


Mettons les trois codes ensemble et augmontons la précision à 100 chiffres après la virgule :

import java.math.BigDecimal;
import java.math.RoundingMode;

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  float exemplef = (float)34/145;
  System.out.println("Float      : " + exemplef);
  
  double exempled = (double)34/145;
  System.out.println("Double     : " + exempled);
  
  BigDecimal bd34 = new BigDecimal(34);
  BigDecimal bd145 = new BigDecimal(145);
  BigDecimal result = bd34.divide(bd145, 100, RoundingMode.DOWN);
  System.out.println("BigDecimal : " +result.toPlainString());
  
 }
}

Le résultat sera :


Il est important de noter que ces opérations sont beaucoup plus lentes. Un test rapide :


import java.math.BigDecimal;
import java.math.RoundingMode;

public class ExempleBigDecimal {
 
 public static void main (String args[]) {
  
  float exemplef;
  long d = System.currentTimeMillis();
  for(int i = 0; i < 1000; i++)
   exemplef = (float)34/145;
  long f = System.currentTimeMillis(); 
  System.out.println("Float temps 1000 opérations    : " + (f - d));
  
  double exempled;
  d = System.currentTimeMillis();
  for(int i = 0; i < 1000; i++)
   exempled = (double)34/145;
  f = System.currentTimeMillis(); 
  System.out.println("Double temps 1000 opérations    : " + (f - d));
  
  BigDecimal bd34 = new BigDecimal(34);
  BigDecimal bd145 = new BigDecimal(145);
  BigDecimal result;
  d = System.currentTimeMillis();
  for(int i = 0; i < 1000; i++)
   result = bd34.divide(bd145, 100, RoundingMode.DOWN);
  f = System.currentTimeMillis(); 
  System.out.println("BigDecimal temps 1000 opérations : " + (f - d));
  
 }
}

Le résultat est :

Nous voyons clairement que 1000 opérations de division en BigDecimal sont beaucoup plus lentes que les opérateurs traditionnels.

lundi 5 mars 2018

Effet de bord (variables) Démo

L'effet de bord (effet secondaire) pour les variables surgit lorsque la variable prend une valeur illogique. Cet effet peut figurer aussi avec les fonctions. (Pour un peu de détail : ici)

Pour les variables, la cause principale est la limite imposée par la taille de la variable.

Cet article vise à donner un exemple sur cet effet.

Démo

Pour démontrer cet effet, nous allons essayer de calculer le factoriel (n!) de quelques nombres. Cette fonction donne, rapidement, des valeurs très grandes.

En Pascal :

Program EffetDeBord;

Var
 i, factoriel : Integer;

Begin
 
 factoriel := 1;
 
 For i := 1 to 10 Do
 Begin
  factoriel := factoriel * i;
  WriteLn(i, '! = ', factoriel);
 End;
 
End.

L'exécution de ce code :



Nous pouvons voir que le calcul commence à donner des valeurs érronées et illogiques des (8!). En effet, le type Integer ne peut pas contenir des valeurs en dehors de l'intervalle : -32768 .. 32767.

Ce phénomène peut être réduit si on utilise des variables "plus grandes" comme le Longint (Long Integer) :


Program EffetDeBord;

Var
 i, factoriel : LongInt;

Begin
 
 factoriel := 1;
 
 For i := 1 to 10 Do
 Begin
  factoriel := factoriel * i;
  WriteLn(i, '! = ', factoriel);
 End;
 
End.

Ce code donne des valeurs plus correctes :


Un entier de type LongInt peut stocker des données de l'intervalle : -2147483648 .. 2147483647.

Pour plus d'information sur la taille des types de données, il faut consulter la documentation officielle du compilateur que vous utilisez. Par exemple :

Turbo Pascal : (ici) 
Free Pascal : (ici)

vendredi 2 mars 2018

Un peu de pratique avec les boucles : calcul du cosinus

Enoncé
Ecrire le programme qui calcule la valeur du sinus grâce au développement limité suivant :

(source)
Solution
Dans cet exemple, j'ai présenté un problème très simiaire. On suit le même mode de raisonnement et on utilise l'écriture sous forme de série comme suit :

Le code Pascal sera ainsi :


Program CalculCosinus;

Var
 i, n, signe, factoriel : Integer;
 x, cosinus, puissancex : Real;

Begin
 
 WriteLn('Donnez le x pour cacluler cos(x) :');
 ReadLn(x);
 WriteLn('Donnez n qui désigne ma précision du calcul :');
 ReadLn(n);
 
 signe := 1;
 factoriel := 1;
 puissancex := 1;
 cosinus := 0;
 
 For i:= 1 to n Do
 Begin
  cosinus := cosinus + (signe * puissancex) / factoriel;
  WriteLn(signe, ' ', puissancex, ' ', factoriel, ' ', cosinus);
  signe := signe * -1;
  puissancex := puissancex * x * x;
  factoriel := factoriel * (2 * i - 1) * (2 * i);
  
 End;
 
 WriteLn('Valeur calculée par cet algorithme : ', cosinus:1:2);
 WriteLn('Valeur calculée par l''API de pascal : ', cos(x):1:2);
 
End.



Dans ce code comme le précédent, l'effet de bord est présent.

Un peu de pratique avec les boucles : calcul du sinus

Enoncé
Ecrire le programme qui calcule le sinus en utilisant le développement limité suivant :
(source)
Solution
Un autre exercice classique pour pratiquer avec les boucles.

L'astuce est simple : il ne faut pas penser à chaque élément d'une manière indépendant; il faut le baser sur l'élément précédent. En d'autres termes, pensez à utiliser l'écriture série de cette formule :


Ainsi, l'objectif est de calculer les éléments (les termes) de la sommes l'un après l'autre en se basant sur l'élément précédent.

Le code en Pascal sera :


Program CalculSinus;

Var
 i, n, signe, factoriel : Integer;
 x, sinus, puissancex : Real;

Begin
 
 WriteLn('Donnez le x pour cacluler sin(x) :');
 ReadLn(x);
 WriteLn('Donnez n qui désigne la précision du calcul :');
 ReadLn(n);
 
 signe := 1;
 factoriel := 1;
 puissancex := x;
 sinus := 0;
 
 For i:= 1 to n Do
 Begin
  sinus := sinus + (signe * puissancex) / factoriel;
  signe := signe * -1;
  puissancex := puissancex * x * x;
  factoriel := factoriel * (2 * i) * (2 * i + 1);
 End;
 WriteLn('Valeur calculée par cet algorithme : ', sinus:1:2);
 WriteLn('Valeur calculée par l''API de pascal : ', sin(x):1:2);
 
End.



Pour les deux exemples, j'ai donné 4 comme précision. Cela m'évite un effet de bord à cause de valeur 11! (11 * 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1) si je donne 5 comme précision. Evidemment, toute valeur supérieure à 5 provoquera le même problème.





vendredi 23 février 2018

La somme des chiffres d'un nombre entier

Exercice 
Ecrire un programme qui calcule la somme des chiffres d'un entier lu à partir du clavier

Solution 
En revenant au code pérsenté ici, la modification à apporter est minimale. En effe, dans code, nous avons affiché les chiffres du nombres, c'est à dire, nous nous sommes limités à les utiliser avec la méthode "Afficher" seulement.

Pour calculer la somme, il suffit d'utiliser ces chiffres dans une somme et non pas dans l'affichage. Ainsi, les modifications à apporter sont :
  1. Ajouter une variable qui va contenir la somme,
  2. Initialiser cette variabe à 0 ('élément neutre de l'opération d'addition),
  3. Ajouter chaque chiffre extraît à la somme au lieu de l'afficher,
  4. Améliorer l'affichage (enlever quelques "Afficher" et ajouter une autre).
Le code résultat sera, en Pascal :

Program SommeChiffres;

Var 
 n, c, s : Integer;

Begin
 s := 0;
 
 WriteLn('Donnez un nombre entier');
 ReadLn(n);
 
 While (n > 0) Do
 Begin
     c := n mod 10;
  s := s + c;
  n := n div 10;
 End;
 WriteLn('La somme des chiffres est : ', s);
End.

En Java :


import java.util.Scanner;

public class SommeChiffres {
 
 public static void main (String args[]) {
  
  Scanner s = new Scanner(System.in);
  int somme = 0;
  
  System.out.println("Donnez un nombre entier :");
  int n = s.nextInt();
  
  while(n > 0){
   somme += n % 10;
   n = n / 10;
  }
  System.out.println("La somme des chiffres est : " + somme);
  
  
 }
}

L'exécution est identique pour les deux codes :


Extraire les chiffres d'un nombre entier

Exercice
Ecrire un programme qui affiche les chiffres d'un nombre lu à partir du clavier.

Soution
Cet exercice est l'un des exercices classiques. Il vise à donner un exemple de manipulation sur deux éléments :
  1. Boucles "Tant que" (while)
  2. Extraîre les chiffres d'un nombre.
L'extraction d'un chiffre (une position) dans un nombre n'est pas aussi simple que l'extraction d'une lettre à partir d'une chaîne de caractères; il ne faut pas confondre les deux situations.

Dans le cas d'une chaîne de caractères, nous avons un tableau de caractères. En mémoire, chaque caractère occupe une case (d'un ou de deux octets, selon le langage et la plate-forme). Ainsi, il suffit de connaître la position d'un caractère pour l'extraire. Le parcours peut être réalisé avec une boucle "Pour" (connaissant la taille de la chaîne de caractères).

Dans le cas d'un nombre, nous avons une seule valeur sauvegarder dans un seul emplacement mémoire (de un, deux, quatre ou huit octets). La valeur est sauvegardée en binaire et les chiffres ne sont pas distingués. Ainsi, connaître la position d'un chiffre de facilite pas son extraction. En effet, il faut faire appel à des calculs pour extraire ce chiffre. Dans ce cas les deux opérations nécessaires sont la division et le modulo.Pour récupérer un chiffre, il faut faire deux choses :
  1. Couper le nombre pour rendre le chiffre recherché dans la position des unités. Cela est fait par la division sur une puissance de 10.
  2. Extraire le chiffre qui devient (après avoir coupé le nombre) le modulo 10.
Par exemple, si le nombre n = 123456. Pour extraire le troisième chiffre à partir de droite, il faut :
  1. Diviser le nombre sur 100 (10^(position - 1)), n devient 1234.
  2. Extraire le nombre la module 10, le chiffre extrait sera 4.
Le parcours est beaucoup plus simple parce que on fait des divisions successives sur 10; aucun besoin pour utiliser la fonction puissance de 10.

Il nous reste à signaler deux remarques très importantes :
  1. Contrairement à un tableau (ou chaîne de caractères), le parcours se fait de droite à gauche.
  2. L'opération détruit la valeur d'origine. Si vous aurez besoin d'utiliser cette valeur plus loin dans le programme alors il faut la sauvegarder dans une variable.
En Pascal :


Program ExtraireChiffres;

Var 
 n, c : Integer;

Begin
 WriteLn('Donnez un nombre entier');
 ReadLn(n);
 
 WriteLn('Ses chiffre (de droite à gauche) sont :');
 While (n > 0) Do
 Begin
     c := n mod 10;
     WriteLn(c);
     n := n div 10;
 End;
End.

En Java :


import java.util.Scanner;

public class ExtraireChiffres {
 
 public static void main (String args[]) {
  
  Scanner s = new Scanner(System.in);
  
  System.out.println("Donnez un nombre entier :");
  int n = s.nextInt();
  
  System.out.println("Ses chiffres sont :");
  while(n > 0){
   System.out.println(n % 10);
   n = n / 10;
  }
  
 }
}

L'exécution est identique pour les deux codes :


jeudi 9 novembre 2017

Calculs simples (partie 2)

Ennoncé de l'exercice :

Ecrire l'algorithme qui permet de calculer la somme, la différence et le produit de deux nombres entiers.

Solution (partie 2):

Nous avons vu dans la première partie, le programme qui permet de calculer la somme, la différence et le produit de deux nombres entiers. Néanmoins, nous avons codé directement les valeurs; tout changement des valeurs nécessitera de modifier le code et de le re-compiler. C'est trop pénible. Alors comment faire pour permettre au programme de récupérer à chaque fois de nouvelles valeurs sans devoir le modifier à chaque fois ?

Cela peut être fait en deux étapes :

La première étape : l'utilisation des variables :

Tout comme en mathématiques, il est possible de définir des variables, un symbole défini sur un ensemble donné et qui peut contenir UNE seule valeur A LA FOIS de cet ensemble. Cette valeur peut changer dans le temps, mais, à un instant donné t, cette valeur est unique.

En effet, si je définit la variable x ∈ N, cela veut dire que x prend sa valeur dans l'ensemble des nombres naturels N, mais il ne prendra qu'une seule valeur à la fois. Une telle déclaration en Pascal sera :


x : Integer;

Et en Java :

int x;

L'ordre peut changer entre les deux langages (symbole de la variable en premier ou bien son type) et même le nom du domaine (entre Integer en Pascal et int "seulement" en Java), néanmoins, l'idée et l'effet des deux déclaration sont exactement les mêmes.
En ajoutant les déclarations à nos codes, nous allons obtenir en Pascal :


Program FaireCalculs;
Var
 a, b, r1, r2, r3 : Integer;
Begin
 a := 5;
 b := 3;

 r1 := a + b;
 r2 := a - b;
 r3 := a * b;

 WriteLn(r1);
 WriteLn(r2);
 WriteLn(r3);
End.

Et en Java :

public class Exemple {
 public static void main(String[] args){
  int a, b, r1, r2, r3;
  
  a = 5;
  b = 3;

  r1 = a + b;
  r2 = a - b;
  r2 = a * b;

  System.out.println(r1);
  System.out.println(r2);
  System.out.println(r3);
 }
}

En utilisant ces variables, nous avons obtenu deux avantages :
  1. Il est maintenant possible de gagner du temps en ne modifiant que les valeurs de a et b (deux valeurs seulement) pour obtenir de nouveaux résultats.
  2. Nous avons séparé le calcul de l'affichage. En effet, les deux actions sont exécutées par des instructions séparées. Cela rend le code plus lisible et plus compréhensible.
Note : Les trois variables r1, r2 er r3 vont occuper 12 octets de votre RAM qui dépasse (probablement) 4 Go (4.294.967.296 octets); alors mettez vous à l'aise et déclarez autant de variables que vous voulez.

La deuxième étape : lecture à partir du clavier :

Si l'écran est la "sortie" principale de l'ordinateur, c'est à dire, le moyen principal qu'il utilise pour nous parler, le clavier est son "entrée" principale c'est à dire, le moyen principal que nous utilisons pour parler à l'ordinateur.

Nous avons déjà demandé à notre programme de nous parler. Nous avons demandé qu'il nous "affiche" quelque chose, certainement, sur sa sortie principale. L'ordre était différent entre les deux langages mais avec le même sens :
En Pascal :

WriteLn();  

En Java :


System.out.println();

Si on ignore la partie "ln" pour le moment, nous auront "write" et "print", au sens de "afficher" les données. Suivant la même idée, nous pouvons demander au programme de recevoir des données de notre part. Pour cela, nous utilisons son entrée principale, c'est à dire le clavier, pour lui parler.

En Pascal : très simple, il arrive à reconnaitre le type de la donnée passée en entrée.


ReadLn(a); {Lire la donnée "a"}

En Java : un peu plus complexe, parce qu'il ne reconnaît pas le type de la donnée, c'est à nous de lui préciser qu'est ce qu'il doit recevoir :


Scanner scanner = new Scanner(System.in); // Ecouter le clavier
a = scanner.nextInt(); // Lire l'entier suivant et le mettre dans la donnée "a"

En intégrant ces notions dans le code précédent, nous aurons le code Pascal :


Program FaireCalcul;
Var
 a, b, r1, r2, r3 : Integer;
Begin
 ReadLn(a);
 ReadLn(b);

 r1 := a + b;
 r2 := a - b;
 r3 := a * b;

 WriteLn(r1);
 WriteLn(r2);
 WriteLn(r3);
End.

Et en Java :


import java.util.Scanner;

public class Exemple {
 public static void main(String[] args){
  int a, b, r1, r2, r3;
  
  Scanner scanner = new Scanner(System.in);  
  a = scanner.nextInt();
  b = scanner.nextInt();

  r1 = a + b;
  r2 = a - b;
  r3 = a * b;

  System.out.println(r1);
  System.out.println(r2);
  System.out.println(r3);
 }
}

A ce stade, notre programme ne définit pas des valeur fixes; il les captent à partir du clavier (c'est l'utilisateur qui les saisit), par conséquent, nous pouvons calculer la somme, la différence et le produit autant de fois que nous voulons et avec de nouvelles valeurs à chaque fois sans avoir à modifier et re-compiler notre programme.

Calculs simples (partie 1)

Énoncé de l'exercice :

Ecrire l'algorithme qui permet de calculer la somme, la différence et le produit de deux nombres entiers.

Solution :


Cet exercice est parmi les premiers exercices à résoudre, il y a pas plus simple mais s'il s'agit du premier exercice de votre vie, alors des explications supplémentaires peuvent être données.

Tout comme en mathématiques, le produit, la différence et le produit prennent deux opérandes; l'opérateur prend sa place entre les deux. Ainsi, Une première idée qui viens en esprit sera le code Pascal suivant :


Program FaireCalculs;
Begin
 5 + 3;
 5 - 3;
 5 * 3;
End.

En Java :


public class Exemple {
 public static void main(String[] args){
  5 + 3;
  5 - 3;
  5 * 3;
 }
}

Mais, ce code est incorrect; faire un calcul sans garder le résultat et sans l'utiliser n'a aucun sens. Mais pourquoi le programme ne l'a pas affiché tout simplement ? Il ne l'a pas affiché parce que nous ne l'avons pas demandé, nous avons mis les opérations seulement. Pour "afficher" le résultat du calcul, il faut le demander explicitement. Ainsi, le programme qui fait le calcul et l'affiche sera en Pascal :


Program FaireCalculs; 
Begin
 WriteLn(5 + 3);
 WriteLn(5 - 3);
 WriteLn(5 * 3);
End.

Et en Java :


public class Exemple {
 public static void main(String[] args){
  System.out.println(5 + 3);
  System.out.println(5 - 3);
  System.out.println(5 * 3);
 }
}

(partie 2)