Search code examples
javascriptsubtractionaddition

How to decrement decimal values in javascript


I'm trying to implement a decrement functionality for decimal values in js

On clicking a button the below should happen

1.22 -> 1.21
3.00 -> 2.99
1.2345 -> 1.2344

How could I do that, below is my code

var decimals = 1,
    stringC = String(positionData.volume),
    value = parseFloat(positionData.volume);
if (stringC.includes(".")) {
    const splitted = stringC.split(".");
    decimals = splitted[1];
}
const length = decimals?.length;
if (length > 0) {
    decimals = "0.";
    for (let i = 0; i < length; i++) {
        if (i == length - 1) {
            decimals += "1";
        }
        decimals += "0";
    }
}
console.log(decimals);
decimals = parseFloat(decimals).toFixed(2);
setCloseValue((value) =>
    (parseFloat(value) + decimals).toString()
);

Above is my code but it is appending the values as a string


Solution

  • EDIT: I just realized only decrementing was requested. Oh well - now this answer works for incrementing too... and also steps larger than ±1.


    I would solve this as follows:

    const input = document.getElementById('input')
    
    function applyStep (step) {
      const decimals = input.value.split('.')[1]?.length ?? 0
      const stepSize = 10 ** -decimals
      input.value = (Number(input.value) + stepSize * step).toFixed(decimals)
    }
    
    document.getElementById('increment').addEventListener('click', () => applyStep(1))
    document.getElementById('decrement').addEventListener('click', () => applyStep(-1))
    <input id="input" value="1.23" />
    <button id="increment">Inc</button>
    <button id="decrement">Dec</button>

    What I do here is this: I first check how many decimals we have after the . (defaulting to 0 if there is no dot).

    Then I calculate a step size of 10-decimals, e.g. with 3 decimals we get 10-3 which is 0.001.

    Then I add the given step (+1 or -1) multiplied by step size to the numeric value and reformat it with the previous number of decimals before writing it back into the field.

    Note that this won't work with numbers that go beyond the precision limits of JavaScripts default number type. If that is desired, a more complicated approach would be needed.

    It could be done by handling integer and fractional parts separately as two BigInts, but it needs a lot of thought when it comes to carry and negative values. Here is a possible solution:

    const input = document.getElementById('input')
    
    function applyStep (step) {
      if (!input.value.match(/^-?\d+(\.\d*)?$/)) throw new Error('Invalid format')
      const [strIntegerPart, strFractionalPart = ''] = input.value.split('.')
      let bnIntegerPart = BigInt(strIntegerPart)
      let bnFractionalPart = BigInt(strFractionalPart)
      const decimals = strFractionalPart.length
      const maxFractionalPart = 10n ** BigInt(decimals)
      let negative = strIntegerPart.startsWith('-') && (bnIntegerPart || bnFractionalPart)
      
      if (negative) {
        bnIntegerPart *= -1n
        step *= -1n
      }
      
      bnFractionalPart += step
      const offset = bnFractionalPart < 0n ? maxFractionalPart - 1n : 0n
      const carry = (bnFractionalPart - offset) / maxFractionalPart
      bnIntegerPart += carry
      bnFractionalPart -= carry * maxFractionalPart
      
      if (bnIntegerPart < 0n) {
        if (bnFractionalPart) {
          bnIntegerPart += 1n
          bnFractionalPart = maxFractionalPart - bnFractionalPart
        }
        bnIntegerPart *= -1n
        negative = !negative
      }
      
      let newValue = bnIntegerPart.toString()
      if (decimals > 0) newValue += '.' + bnFractionalPart.toString().padStart(decimals, '0')
      if (negative && (bnIntegerPart || bnFractionalPart)) newValue = '-' + newValue
      
      input.value = newValue
    }
    
    document.getElementById('increment').addEventListener('click', () => applyStep(1n))
    document.getElementById('decrement').addEventListener('click', () => applyStep(-1n))
    <input id="input" value="1.23" />
    <button id="increment">Inc</button>
    <button id="decrement">Dec</button>

    Both these solutions will work with steps other than 1 and -1 too.