Double, Float et BigDecimal

Gestion des nombres à virgule en Java

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

En Java les nombres à virgule peuvent être représentés soit par double, soit par float. Cependant, les deux se comportent différement et les calculs sur un ne donneront pas forcément les mêmes résultats que sur l'autre.

La différence principale entre ces deux types primitifs repose sur l'exactitude des résultats. Float est codé sur 32 bits. Tandis que double est codé sur 64 bits. Il est conseillé d'utiliser floats pour économiser de la mémoire si la précision des résultats importe peu. Dans le cas contraire il faut privilégier double, malgré la taille deux fois plus grande. En ce qui concerne la rapidité d'exécution, il n'y a pas de différences sur les machines récentes.

public class FloatDoubleDifference {
    public static void main(String[] args) {
        System.out.println("Min value of float = " + Float.MIN_VALUE);
        System.out.println("Max value of float = " + Float.MAX_VALUE);
        System.out.println("Max length of float = " + Float.SIZE);
        System.out.println("Min value of double = " + Double.MIN_VALUE);
        System.out.println("Max value of double = " + Double.MAX_VALUE);
        System.out.println("Max length of double = " + Double.SIZE);

        float exFloat = 3.2f;
        double exDouble = 3.2;	

        if (exFloat == 3.2) {
            System.out.println("exFloat equals to 3.2");
        } else {
            System.out.println("exFloat doesn't equal to 3.2");
        }

        if (exDouble == 3.2) {
            System.out.println("exDouble equals to 3.2");
        } else {
            System.out.println("exDouble doesn't equal to 3.2");
            System.out.println("exDouble's value is " + exDouble);
        }

        System.out.printf("Float value : %.20f\n", 3.2f);
        System.out.printf("Double value : %.20f\n", 3.2);
    }
}

Cet exemple montre quelle est la différence de précision pour le chiffre 3,2. S'il s'agit d'un double, le résultat est correct (sur l'écran on voit "Double value : 3,20000000000000000000"). En ce qui concerne float, le résultat du test est faux (l'écran affiche "Float value : 3,20000004768371600000"). Cela démontre bien que pour rester précis, il vaut mieux se tourner vers double.

Cependant, aucun des deux types ne devrait pas être utilisé tout seul pour les opérations financières. Float et double peuvent falsifier les résultats. Regardons ceci sur un exemple d'achat :

public class BigDecimalCase {
    public static void main(String[] args) {
        System.out.println("Double result for 1.13 - 0.22 = " + (1.13 - 0.22));
        System.out.println("Float result for 1.13f - 0.22f = " + (1.13f - 0.22f));
    }
}

Le cas de figure va afficher les messages suivants :

Double result for 1.13 - 0.22 = 0.9099999999999999
Float result for 1.13f - 0.22f = 0.90999997

Le résultat attendu est pourtant 0.91. Pour l'achever, il faut effectuer les calculs sur BigDecimal.

import java.math.BigDecimal;

public class BigDecimalCase {
    public static void main(String[] args) {
        float toPay = 1.13f;
        float toReturn = 0.22f;
        double toPayD = 1.13;
        double toReturnD = 0.22;

        BigDecimal toPayBd = new BigDecimal(Float.toString(toPay));
        BigDecimal toReturnBd = new BigDecimal(Float.toString(toReturn));
        System.out.println("BigDecimal result for converted floats 1.13 - 0.22 = " + toPayBd.subtract(toReturnBd));
 
        BigDecimal toPayDBd = new BigDecimal(Double.toString(toPayD));
        BigDecimal toReturnDBd = new BigDecimal(Double.toString(toReturnD));
        System.out.println("BigDecimal result for converted double 1.13 - 0.22 = " + toPayDBd.subtract(toReturnDBd));   
    }
}

Le résultat retourné est finalement correct :

BigDecimal result for converted floats 1.13 - 0.22 = 0.91
BigDecimal result for converted double 1.13 - 0.22 = 0.91

L'avantage d'utiliser BigDecimal est une possibilité de spécifier la précision du résultat (le nombre de chiffres qui apparaîtront après le virgule). C'est la méthode setScale() qui s'en charge. Elle prend comme paramètre l'instance de la classe MathContext qui détermine la précision du résultat (nombre de chiffres à afficher).

On peut également coupler les opérations effectuées sur BigDecimal avec les types primitifs double et float. Avec l'utilisation des classes utilitaires d'internationalisation, on peut facilement gérer l'affichage des chiffres en fonction du pays d'origine.

Voici l'exemple complet, contenant les arrondis et les calculs corrects des prix :

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.Locale;

public class BigDecimalCase {
    public static void main(String[] args) {
        float toPay = 1.13f;
        float toReturn = 0.22f;
        double toPayD = 1.13;
        double toReturnD = 0.22;

        System.out.println("Double result for 1.13 - 0.22 = " + (toPayD - toReturnD));
        System.out.println("Float result for 1.13f - 0.22f = " + (toPay - toReturn));

        BigDecimal toPayBd = new BigDecimal(Float.toString(toPay));
        BigDecimal toReturnBd = new BigDecimal(Float.toString(toReturn));
        System.out.println("BigDecimal result for converted floats 1.13 - 0.22 = " + toPayBd.subtract(toReturnBd));
 
        BigDecimal toPayDBd = new BigDecimal(Double.toString(toPayD));
        BigDecimal toReturnDBd = new BigDecimal(Double.toString(toReturnD));
        System.out.println("BigDecimal result for converted double 1.13 - 0.22 = " + toPayDBd.subtract(toReturnDBd));
    
        MathContext mc = new MathContext(5);
        BigDecimal bdScale = new BigDecimal("1.53929");    
        bdScale.setScale(3, BigDecimal.ROUND_HALF_UP);
        System.out.println("BigDecimal with 2 numbers scale " + bdScale.round(mc));

        System.out.println("Show big amount in the American and French format");
        BigDecimal bigTest = new BigDecimal("759403.535");
        NumberFormat usFormat = NumberFormat.getCurrencyInstance(Locale.US); 
        double btDoubled = bigTest.doubleValue();
        String btString = usFormat.format(btDoubled);
        System.out.println("American representation of 759403.535 " + btString);
        Locale frLocale = new Locale("fr", "FR", "EURO");
        NumberFormat frFormat = NumberFormat.getCurrencyInstance(frLocale); 
        String btStringFr = frFormat.format(btDoubled);
        System.out.println("French representation of 759403.535 " + btStringFr);
    }
}

L'écran affichera :

Double result for 1.13 - 0.22 = 0.9099999999999999
Float result for 1.13f - 0.22f = 0.90999997
BigDecimal result for converted floats 1.13 - 0.22 = 0.91
BigDecimal result for converted double 1.13 - 0.22 = 0.91
BigDecimal with 2 numbers scale 1.5393
Show big amount in the American and French format
American representation of 759403.535 $759,403.54
French representation of 759403.535 759 403,54 €
http://stackoverflow.com/questions/6232446/float-or-double Comment on peut comparer un double et un float ? BigDecimal !
Bartosz KONIECZNY Classes

Une question ? Une remarque ?

*

*

Un conseil Symfony2

Comment personnaliser l'affichage des champs du formulaire ?

Pour personnaliser l'affichage des champs du formulaire sous Symfony2 on doit surcharger le paramètre nommé templating.helper.form.class. Par défaut il s'agit de la classe Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper. Le code se présente ainsi :

<?xml version="1.0" encoding="UTF-8" ?>
  <container xmlns="http://symfony.com/schema/dic/services"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <parameters>
      <parameter key="templating.helper.form.class">Frontend\FrontBundle\Helper\FormHelper
    </parameters>
  </container>
La classe surchargée va, avant d'afficher le champ, faire appel à la méthode setTheme(). Tout cela pour déterminer quel thème doit utiliser Symfony2 pour le rendu du formulaire (champ text, boutons radio etc.). Le code de cette FormHelper se présente ainsi :
namespace Frontend\FrontBundle\Helper;

use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Exception\FormException;
use Symfony\Component\Form\Util\FormUtil;
use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper as ParentHelper;

class FormHelper extends ParentHelper
{
    protected $engine;

    protected $varStack;

    protected $context;

    protected $resources;

    protected $themes;

    protected $templates;

    protected function renderSection(FormView $view, $section, array $variables = array())
    {
        // if new theme is set (for exemple for override form templates)
        if(isset($variables['attr']['theme'])) $this->setTheme($view,  $variables['attr']['theme']);
        return parent::renderSection($view, $section, $variables);
    }

}
Afin de personnaliser un champ du formulaire sous Symfony2 il faut passer un paramètre theme qui appelera la fonction setTheme. Voici un exemple :
// it will show the template located ad /app/Resources/views directory
echo $view['form']->widget($form['replyType'], array('attr' => array('theme' => array(0 => ':'))));
Pour voir comment on peut personnaliser ces champs, veuillez vous référer aux fichiers placés dans /vendor/Symfony/Bundle/FrameworkBundle/Resources/views/Form.