3

I am creating number spinner widget in JavaScript to essentially mimic the number field in webkit.

When you change the number, it needs to check to see if the value is not only within the accepted range, but also that it's in step:

<input type="number" min="0" max="100" step="1" />

If a user enters 5.5 the field will truncate this to the closest step lower than the value, which in this case is 5.

For a step of 2, if the user entered 5.5, the result would be 4.

The equation I was planning on using for this calculation looks like this:

...code...
_checkStep: function (val) {
    val ret,
        diff,
        moddedDiff;
    diff = val - this._min;
    moddedDiff = diff % this._step;
    ret = val - moddedDiff;
    return ret;
},
//set elsewhere
_min,
_step,
...more code...

Although for testing purposes, you could simply use this:

function checkStep(val, min, step) {
    var ret,
        diff,
        moddedDiff;
    diff = val - min;
    moddedDiff = diff % step;
    ret = val - moddedDiff;
    return ret;
}

This works great for integers and larger values, however I've run into issues with decimals due to how JavaScript handles floating point numbers.

For example:

checkStep(0.5, 0, 0.1) //returns 0.4, 0.5 is expected

In analyzing each line, it turns out that 0.5 % 0.1 in JavaScript returns 0.09999999999999998.

What can be done to make this function more accurate*?


*accurate being that it works for increments of 0.01 and up.

zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • 4
    There's no such thing as 'accurate' floating point. They're invariably best-effort approximations. At most you can specify a level of precision you're willing to live with. – Marc B Apr 27 '12 at 16:13
  • @MarcB, good point, forgot to mention that I'm interested in having this work to the hundredths place. – zzzzBov Apr 27 '12 at 16:15
  • 2
    *"0.5 % 0.1 in JavaScript returns 0.09999999999999998."* Oh, that's just the tip of the iceberg. `0.1 + 0.2 = 0.30000000000000004`. – T.J. Crowder Apr 27 '12 at 16:15
  • @T.J.Crowder, yes, yes, i'm well aware of standard inaccuracies in floating point arithmetic, I'm just not sure of how to account for them in this case. – zzzzBov Apr 27 '12 at 16:17
  • I find your examples a little confusing. For a step of 2 and an input of 5.5, shouldn't it return 6, not 4? 5.5 is closer to 6 than it is to 4. Likewise, shouldn't a step of 0.5 and an input of 0.1 return 0, since 0.1 is closer to 0 than it is to 0.5? – Ethan Brown Apr 27 '12 at 16:46
  • @EthanBrown, "closest step *lower* than the value". I've been mostly trying to mimic the chrome implementation, I've considered rounding to the nearest step, but that has additional complexities that need to be addressed, such as: what happens when the step is `2` and the value is `1`, which direction does it get rounded to? The easier implementation is to simply floor it to the closest step. – zzzzBov Apr 27 '12 at 18:01

2 Answers2

1

You could try making sure step is greater than 1 (by repeatedly multiplying by 10), then do your modulus, then scale back down to original. For example:

function checkStep(val, min, step) {
  var ret,
    diff,
    moddedDiff;
  var pows = 0;
  while( step < 1 ) { // make sure step is > 1
    step *= 10;
    val *= 10;
    min *= 10;
    pows++;
  }
  diff = val - min;
  moddedDiff = diff % step;
  ret = val - moddedDiff;
  return ret / Math.pow( 10, pows );
}

This works for the examples you provided, but I can't guarantee it will work for everything. See the jsfiddle here:

http://jsfiddle.net/bCTL6/2/

Ethan Brown
  • 26,892
  • 4
  • 80
  • 92
  • I was going to update my answer with some updated code. I'm essentially doing the same thing, but using a recursive function call. It seems to work so far, although there's some modulus checking that needs to be done as well. – zzzzBov Apr 27 '12 at 21:34
-1

There's no absolutely guaranteed accurate floating point calculations. Use integer calculations instead. In your 0.1 example you can count amount of "0.1"'s in integers, visually adding point before last digit for user.

Oleg V. Volkov
  • 21,719
  • 4
  • 44
  • 68
  • 1
    It's funny that you say there's no guarantee on accuracy, as floating point arithmetic is well defined and produces the same results in every browser. The problem is that it's often counter-intuitive. I was really hoping to get assistance into making floating point arithmetic code more resilient, and have it work for reasonable human ranges. – zzzzBov Apr 27 '12 at 16:21
  • Well, I've meant "from human decimal point of view" of accuracy, yes. :) Of course it always works exactly the same way each time inside. – Oleg V. Volkov Apr 27 '12 at 16:23