web-dev-qa-db-fra.com

Pourquoi l'opérateur de modulus retourne-t-il un numéro fractionnaire en JavaScript?

Pourquoi est-ce que 49.90 % 0.10 dans le retour JavaScript 0.09999999999999581? Je m'attendais à ce que ce soit 0.

30
Edgar

Parce que JavaScript utilise le point flottant des mathématiques qui conduis toujours à des erreurs d'arrondi.

Si vous avez besoin d'un résultat exact avec deux décimales, multipliez vos numéros avec 100 Avant l'opération, puis diviser à nouveau après:

var result = ( 4990 % 10 ) / 100;

Rond si nécessaire.

51
Aaron Digulla

Le numéro de Javascript utilise "la double précision IEEE" pour stocker les valeurs. Ils sont incapables de stocker tous les nombres décimaux exactement. Le résultat n'est pas zéro à cause de l'erreur rondelle lors de la conversion du nombre décimal en binaire.

49.90 = 49.89999999999999857891452848...
 0.10 =  0.10000000000000000555111512...

Ainsi, le plancher (49,90/0,10) n'est que de 498 et le reste sera de 0,09999 ....


Il semble que vous utilisiez des chiffres pour stocker la quantité de dollars. Ne faites pas cela , comme des opérations de point flottant propagent et amplifient l'erreur rondelle. Stockez le nombre comme quantité de cents à la place. Entier peut être représenté exactement, et 4990 % 10 retournera 0.

18
kennytm

Je vais juste laisser cela ici pour référence future, mais voici une fonction pratique qui peut être plus précise reste (depuis JS n'a pas d'opérateur modulo ) impliquant des flotteurs .

  function floatSafeRemainder(val, step){
    var valDecCount = (val.toString().split('.')[1] || '').length;
    var stepDecCount = (step.toString().split('.')[1] || '').length;
    var decCount = valDecCount > stepDecCount? valDecCount : stepDecCount;
    var valInt = parseInt(val.toFixed(decCount).replace('.',''));
    var stepInt = parseInt(step.toFixed(decCount).replace('.',''));
    return (valInt % stepInt) / Math.pow(10, decCount);
  }
$(function() {
  
  
  function floatSafeModulus(val, step) {
    var valDecCount = (val.toString().split('.')[1] || '').length;
    var stepDecCount = (step.toString().split('.')[1] || '').length;
    var decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount;
    var valInt = parseInt(val.toFixed(decCount).replace('.', ''));
    var stepInt = parseInt(step.toFixed(decCount).replace('.', ''));
    return (valInt % stepInt) / Math.pow(10, decCount);
  }
  
  
  $("#form").submit(function(e) {
    e.preventDefault();
    var safe = 'Invalid';
    var normal = 'Invalid';
    var var1 = parseFloat($('#var1').val());
    var var2 = parseFloat($('#var2').val());
    if (!isNaN(var1) && !isNaN(var2)) {
      safe = floatSafeModulus(var1, var2);
      normal = var1 % var2
    }
    $('#safeResult').text(safe);
    $('#normalResult').text(normal);
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="form" novalidate>
  <div>
    <input type="number" id="var1">%
    <input type="number" id="var2">
  </div>
  <div>safe: <span id="safeResult"></span><div>
  <div>normal (%): <span id="normalResult"></span></div>
  <input type="submit" value="try it out">
</form>
13
TheSharpieOne

cause

Le point flottant ne peut pas stocker toutes les valeurs décimales exactement. Ainsi, lorsque vous utilisez des formats de points flottants, il y aura toujours des erreurs d'arrondi sur les valeurs d'entrée. Les erreurs sur les entrées des résultats des erreurs sur les erreurs sur la sortie. En cas d'une fonction ou d'un opérateur discrète, il peut y avoir une grande différence sur la sortie autour du point où la fonction ou l'opérateur est discrète. L'opérateur de modula est discret et votre cas est clairement un exemple de ce problème.

entrée et sortie pour les valeurs de point flottant

Ainsi, lors de l'utilisation de variables à virgule flottante, vous devez toujours en être conscient. Et quelle que soit la sortie que vous souhaitez d'un calcul avec des points flottants doit toujours être formatée/conditionnée avant d'afficher dans cet esprit.
[.____] Lorsque seules des fonctions et des opérateurs continues sont utilisées, l'arrondi sur la précision souhaitée sera souvent (ne pas tronquer). Les fonctions de formatage standard utilisées pour convertir des flotteurs en chaîne le feront généralement pour vous.
[.____] Pour avoir une sortie correcte basée sur la précision attendue des entrées et la précision souhaitée de la production, vous devriez également

  • Entrées rondes à la précision attendue ou assurez-vous qu'aucune valeur ne peut être entrée avec une précision supérieure.
  • Ajoutez une petite valeur aux sorties avant l'arrondi/les formatant de la forme inférieure ou égale à 1/4 de la précision souhaitée et de plus grande que l'erreur maximale attendue causée par des erreurs d'arrondi sur l'entrée et le calcul. Si cela n'est pas possible, la combinaison de la précision du type de données utilisé ne suffit pas pour délivrer la précision de sortie souhaitée pour votre calcul.

Ces 2 choses ne sont souvent pas faites et dans la plupart des cas, les différences causées par ne les faisant pas sont trop petites pour être importantes pour la plupart des utilisateurs, mais j'ai déjà eu un projet où la production n'était pas acceptée par les utilisateurs sans ces corrections.

fonctions discrètes ou opérateurs (comme modula)

Lorsque des opérateurs ou des fonctions discrets sont impliqués, des corrections supplémentaires peuvent être nécessaires pour s'assurer que la production est comme prévu. Arrondir et ajouter de petites corrections avant l'arrondi ne peut résoudre le problème.
[.____] une vérification/correction spéciale sur les résultats de calcul intermédiaire, immédiatement après l'application de la fonction discrète ou de l'exploitant pourraient être nécessaires.

cas spécifique de cette question

Dans ce cas, vous vous attendez à une saisie avec une certaine précision, il est donc possible de corriger la production d'impact des erreurs d'arrondi qui sont beaucoup plus petites que la précision souhaitée.

Si nous disons que la précision de votre type de données est e.
[.____] Votre entrée ne sera pas stockée comme des valeurs A et B que vous avez entrées, mais comme un * (1 +/- e) et B * (1 +/- e)
[.____] Le résultat d'une division A * (1 +/- E) par B * (1 +/- e) entraînerait (A/B) (1 +/-2e).
[.____] La fonction de modulation doit tronquer le résultat et multiplier à nouveau. Donc, le résultat sera (A/B
B) (1 +/- 3e) = A (1 +/- 3e) entraînant une erreur de * 3e.
[.____] Le MOD ajoute A * E à l'erreur possible de A * 3e en raison de la soustraction de 2 valeurs avec des erreurs éventuelles d'A * 3E et A * E.
[ .

mieux éviter d'avoir le problème

Il est souvent plus efficace d'éviter ces problèmes en utilisant des types de données (formats entier ou point fixe) pour des calculs tels que ceux-ci pouvant stocker l'entrée attendue sans erreur d'arrondi. Un exemple de ceci est que vous ne devez jamais utiliser des valeurs de points flottants pour les calculs financiers.

2
Stefan Mondelaers

Jetez un coup d'œil à points flottants et ses inconvénients - un numéro comme 0.1 Ne peut pas être enregistré correctement comme point flottant, il y aura donc toujours de tels problèmes. Prenez vos numéros * 10 ou * 100 et effectuez les calculs avec des entiers à la place.

1
oezi

http://fr.wikipedia.org/wiki/modulo_operation Ne soyez pas en colère Modulo est utilisé avec des entiers ^^ afin que les valeurs flottantes soient des erreurs.

0
MatTheCat