Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Number input falsely accepting floats with decimal places

Tags:

html

chromium

<form>
  <input type="number" step="1" required>
  <input type="submit">
</form>

In the above code snippet, if you insert

0.0000001

into the number input, you cannot submit the form: The "step" attribute prevents this.

But if you insert

0.00000001

it works in Chrome/Chromium! Why does it seem to only read the first seven decimal places when validating the input? Is this documented anywhere and what can I do to prevent this?

I tested Firefox, it does not accept any such values.

For clarification, the common pitfall 0.30000000000000004 === 0.1+0.2 cannot be the issue here, as this occurs only with 16 decimal places. The input value above fails with 8 decimal places.

like image 366
phil294 Avatar asked Sep 16 '19 22:09

phil294


People also ask

Can input type number accept decimal?

"Save error: Input type 'number' does not support Decimal data type.

How do I put 2 decimal places in HTML?

To limit the number of digits up to 2 places after the decimal, the toFixed() method is used. The toFixed() method rounds up the floating-point number up to 2 places after the decimal.


Video Answer


2 Answers

While it is certainly a bug with the numbers you have, it actually allows to prevent issues with other sets of steps/values. There are core issues with fraction rounding, making a perfect step validation not that easy, at least not with that level of precision. In fact if you look at Firefox source code, you can find this comment https://dxr.mozilla.org/mozilla-central/source/dom/html/HTMLInputElement.cpp#4654:

There could be rounding issues below when dealing with fractional numbers, but let's ignore that until ECMAScript supplies us with a decimal number type.

So the issues are not only with Chrome. But let's start with the bug you describe. If you look at Chromium source code, you can see that in the step_range handling, there is a method called AcceptableError, which adjust the difference between the value and the value adjusted in regard to the step. See https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/forms/step_range.cc?l=60. This acceptable error is defined as:

the step value / 2^24 (in the source code UINT64_C(1) << FLT_MANT_DIG) 

This gives for a step of 1:

1 / 16777216 = 0.000000059604645

So in your original example, any number that has a remainder under this value, will validate, even if it shouldn't. The formula to calculate the remainder is:

value - step * Math.abs(Math.round(value / step));

So for example if you enter 2.000000059604644, the remainder will be:

  2.000000059604644 - 1 * Math.abs(Math.round(2.000000059604644))
= 2.000000059604644 - 2
= 0.000000059604644 

0.000000059604644 is under 0.000000059604645 so it'll validate. 2.000000059604646 won't.

<form>
  <input type="number" step="1" required>
  <input type="submit">
</form>

You can try with bigger numbers, for example take a step of 167772167. This gives an acceptable error of:

16777217 / 16777216 = 1.000000059604645

and for a value of 16777218 a remainder of:

  16777218 - 16777217 * Math.abs(Math.round(1.000000059604645 ))
= 16777218 - 16777217
= 1

<form>
  <input type="number" step="16777217" required>
  <input type="submit">
</form>

which is under your acceptable error of 1, so it'll validate.

So this is the bug. What it allows in revenge, is to tolerate certain steps and values that because of a loss of precision in the decimal operations wouldn't validate when they should. And in browsers such as Firefox, this is exactly what happens. Some values/steps pair don't validate when they should.

Take for example a step of 853.2394 and a value of 495714162280.48785. This should certainly validate, since :

495714162280.48785 / 853.2394 = 580978987

If you try it in Firefox, it doesn't validate. But in Chrome, it validates, because of the tolerance that causes the bug with the first case.

<form>
  <input type="number" step="853.2394" required>
  <input type="submit">
</form>

So in the end, this bug is linked to known issues with fractional operations, which aren't precise. The classic example is 0.1 + 0.2 = 0.30000000000000004. What happens in the background is a bit more complex and more precise, but the problem of dealing with decimal calculations causes these issues, and makes them hard to prevent for every case.

like image 86
Julien Grégoire Avatar answered Oct 06 '22 01:10

Julien Grégoire


To prevent that, I can propose you to limite the action for the user :

I added onkeypress="return event.charCode >= 48 && event.charCode <= 57" to don't allow the user to type . and ,

The last problem is that user can paste value inside the input so there is a little script to prevent that :

  const pasteBox = document.getElementById("no-paste");
  pasteBox.onpaste = e => {
    e.preventDefault();
    return false;
  };
<form>
  <input 
     type="number" 
     step="1" 
     onkeypress="return event.charCode >= 48 && event.charCode <= 57" 
     required
     id="no-paste">
  <input type="submit">
</form>

Hope it help you ;)

EDIT : You can use this javascript to limit the decimals :

const myInput = document.getElementById("my-input");
  myInput.addEventListener("input", function() {
    const dec = myInput.getAttribute('decimals');
    const regex = new RegExp("(\\.\\d{" + dec + "})\\d+", "g");
    myInput.value = myInput.value.replace(regex, '$1');
  });
 <input id="my-input" type="text" decimals="2" placeholder="2 dec" />
like image 31
Benjamin Barbé Avatar answered Oct 06 '22 02:10

Benjamin Barbé