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
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.