开发者

Why does modulus operator return fractional number in javascript?

Wh开发者_如何学编程y does 49.90 % 0.10 in JavaScript return 0.09999999999999581? I expected it to be 0.


Because JavaScript uses floating point math which can lead to rounding errors.

If you need an exact result with two decimal places, multiply your numbers with 100 before the operation and then divide again afterwards:

var result = ( 4990 % 10 ) / 100;

Round if necessary.


Javascript's Number is using "IEEE double-precision" to store the values. They are incapable of storing all decimal numbers exactly. The result is not zero because of round-off error when converting the decimal number to binary.

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

Thus floor(49.90 / 0.10) is only 498, and the remainder will be 0.09999....


It seems that you are using numbers to store amount of dollars. Don't do this, as floating point operations propagate and amplify the round-off error. Store the number as amount of cents instead. Integer can be represented exactly, and 4990 % 10 will return 0.


I'll just leave this here for future reference, but here is a handy function that can more precisely handle Remainder (since JS doesn't have a modulo operator) involving floats.

  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>


Cause

Floating point can't store all decimal values exactly. So when using floating point formats there will always be rounding errors on the input values. The errors on the inputs of course results on errors on the output. In case of a discrete function or operator there can be a big difference on the output around the point where the function or operator is discrete. The modula operator is discrete and your case is clearly an example of this problem.

Input and output for floating point values

So, when using floating point variables, you should always be aware of this. And whatever output you want from a calculation with floating points should always be formatted/conditioned before displaying with this in mind.
When only continuous functions and operators are used, rounding to the desired precision often will do (don't truncate). Standard formatting features used to convert floats to string will usually do this for you.
To have a correct output based on expected precision of inputs and desired precision of output, you should also

  • Round inputs to the expected precision or make sure no values can be entered with higher precision.
  • Add a small value to the outputs before rounding/formatting them which is smaller than or equal to 1/4 of the desired precision and bigger than the maximum expected error caused by rounding errors on input and during calculation. If that is not possible the combination of the precision of the used data type isn't enough to deliver the desired output precision for your calculation.

These 2 things are often not done and in most cases the differences caused by not doing them are too small to be important for most users, but I already had a project where output wasn't accepted by the users without those corrections.

Discrete functions or operators (like modula)

When discrete operators or functions are involved, extra corrections might be required to make sure the output is as expected. Rounding and adding small corrections before rounding can't solve the problem.
A special check/correction on intermediate calculation results, immediately after applying the discrete function or operator might be required.

Specific case of this question

In this case, you expect input with a certain precision, so it is possible to correct output for impact of rounding errors which are a lot smaller than the desired precision.

If we say the precision of your data type is e.
Your input will not be stored as the values a and b you entered, but as a*(1+/-e) and b*(1+/-e)
The result of a division a*(1+/-e) by b*(1+/-e) would result in (a/b)(1+/-2e).
The modula function has to truncate the result and multiply again. So the result will be (a/b
b)(1+/-3e) = a(1+/-3e) resulting in an error of a*3e.
The mod adds a*e to the possible error of a*3e because of the subtraction of 2 values with a possible errors of a*3e and a*e.
So you should check that the total possible error a*4e is smaller than the desired precision and if that condition is met and the result differs no more from b than that maximum possible error, you can safely replace it by 0.

Better avoid having the problem

It is often more efficient to avoid these problems by using data types (integer or fixed point formats) for calculations like this which can store the expected input without rounding errors. An example of that is that you should never use floating point values for financial calculations.


Take a look at floating points and its disadvantages - a number like 0.1 can't be saved correctly as floating point, so there will always be such problems. Take your numbers *10 or *100 and do the calculations with integers instead.


http://en.wikipedia.org/wiki/Modulo_operation Don't be angry modulo is used with integers ^^ So floating values occure some errors.


Hopefully this is helpful to someone:

let asBuffer = new ArrayBuffer(8);
let asInt = new Uint32Array(asBuffer );
let asFloat = new Float32Array(asBuffer );
asFloat.set([49.9,.1]);
asInt.set([asFloat[0]/asFloat[1], asFloat[0]%asFloat[1]]);

Result

asInt Uint32Array(2) [499, 0, buffer: ArrayBuffer(8), 
byteLength: 8, byteOffset: 0, length: 2, 
Symbol(Symbol.toStringTag): 'Uint32Array']
//In general I'm moving more and more towards 
//Using typed arrays to store values in JS.


This is not a perfect answer but it works.

function format_float_bug(num)
{
   return parseFloat( num.toFixed(15) ); 
} 

you can use as follows,

format_float_bug(4990 % 10);

because below number (49.89999999999999857891452848) first 15 decimal places are like 9999999

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜