```function preciseRound(num, decimals = 2) { ...
# suitescript
c
Copy code
function preciseRound(num, decimals = 2) {
    const factor = Math.pow(10, decimals);
    return Math.round(num * factor) / factor
}
Tell me why I'm wrong for doing this (I have no idea if it will have side effects). The challenge was to make 1405.985 round to 1405.99 toFixed(2) was rounding it to 1405.98. I have a feeling this is something to do with floating point in binary.
e
We try to use decimaljs. new Decimal(1405.985).toFixed(2)
👍 1
a
JavaScript is bad by design with numbers, the only way I know to round numbers that never fails is this one; anything else will eventually fail under the right circumstances:     /** • Round using exponential. * • @param {Number} pValue • @param {Number} pDecimals • */ const roundByExponent = (pValue, pDecimals) => {    return Number(Math.round(pValue + 'e' + pDecimals) + 'e-' + pDecimals); }
c
I'm looking at your code to see if I should use that instead - it's hard to put together broad enough test cases to be honest.
a
Your function will fail with: • preciseRound(1.005, 2); • preciseRound(0.000000123456, 8); • preciseRound(-0.004, 2); • preciseRound(123456789.555, 2);
c
Am I calling your function as per: roundByExponent(1405.985, 2) ?
a
It fails for you use case, fixed version which I will actually add to my library…
const roundByExponentSafe = (pValue, pDecimals) => { return Number(Math.round((pValue + Number.EPSILON) + 'e' + pDecimals) + 'e-' + pDecimals); };
c
It rounded correctly for my case
or at least the logs and the UI show the correct rounding
a
Well never mind then…
c
I'll try that again
Copy code
const roundByExponentWithLogs = (pValue, pDecimals) => {
    log.debug('before rounding', pValue);
    const rounded = Number(Math.round(pValue + 'e' + pDecimals) + 'e-' + pDecimals);
    log.debug('after rounding', rounded);
    return rounded
}
Can't see any obvious issue.
a
I've always just used this... 100 for 2dp, if you want a different number of decimal places add more 0s
Copy code
Math.round((num + Number.EPSILON) * 100) / 100;
a
@Anthony OConnor const num = 1.005; Math.round((num + Number.EPSILON) * 100) / 100;
a
yeah? it rounds correctly to 1.01?
image.png
a
That is due to recent implementations or updates of the browsers, working in console does not grant working on server side with potentially older JS engines or version that NetSuite may be using.
a
Number.Epsilon is a value... a tiny value but a value so when you add it to
num
it becomes a different number that rounds differently
1.00500000000000000000000000234234 or something 🙂
which WILL round up to 1.01 instead of being dumb JS and rounding to 1.00
its been around since like 2012, its not some new ESNext stuff
a
Try this server side (I’m from my phone, away from my desk): const num = 1.275; const rounded = Math.round((num + Number.EPSILON) * 100) / 100;
a
image.png
image.png
c
To be fair, NS does use a very modern JS engine for SS2.1
e
JavaScript is bad by design with numbers
Just because this is always a bee in my bonnet, this is not a JavaScript-specific problem, but a universal problem with the IEEE standard representation of floating point numbers. Other languages have more native mechanics for dealing with it, but they all have to deal with it.
👍 2
a
yeah I have no clue if this would work under the old Rhino implementation, but we've been on GraalVM for... a while
...its not like there isn't tons other of goofy JS specfic stuff to complain about 😛
e
For an actual answer though, I've seen companies use Decimal.js and BigNumber.js, both with no apparent issues
a
Well it looks like some stuff are getting better or fixed, all I know is that I has been unable to break the version I shared using exponentials and without using EPSILON while I remember very weird stuff back from 2016 that no matter what you tried, rounding will eventually fail. But the final consensus everywhere seems to be using what @erictgrubaugh mentioned.
e
(and others 🙂 ) "Rounding currency accurately", in my mind, is a solved problem. My first attempt at it is not going to yield anything that hasn't been tried before or that someone hasn't solved better. I'm always going to opt for a well-tested, well-used third-party for something like that. Same thing with other problems like Date math, CSV parsing, XML parsing, ...
a
I must be the only person that doesn't mind getting my hands dirty with JS dates 😄
I literally wrote this code this week 🙂
Copy code
const daysToIncrement = threshold * multiplier;
 const lastDate = new Date (data.values.edited);
 let nextDate = new Date (lastDate);
 nextDate.setDate(lastDate.getDate() + daysToIncrement);
 return nextDate;
a
Ohh boy, • Dates - 100% not doing that myself with vanilla. • CSV parsing - Same. • XML I would probably try to build something with N/xml But agree with eric.
e
Hope you never use it on a leap year
I actually don't know if that'll be a problem with that code 😄 just the types of caveats I don't want to deal with
a
for this usecase its actually fine, business rules has wiggle room, but I've implemented other stuff to account for leapdays before
a
It is worth it tho? The time that you will invest re-inventing the wheel? Some managers would not allow you to waste that time…
a
it doesn't take me long I'm familiar enough with them that its quicker for to do that than read the docs on how to use moment or w/e luxon now i guess?
Copy code
function calculateTermInMonths({startDate, endDate}){

        const startDay = startDate.getDate();
        const startMon = startDate.getMonth();
        const startYr = startDate.getFullYear();
        const endDay = endDate.getDate();
        const endMon = endDate.getMonth();
        const endYr = endDate.getFullYear();
        const yrDiff = endYr - startYr;
        const monDiff = endMon - startMon;
        const dayDiff = endDay - startDay;

        const daysInEndMonth = new Date(endYr, endMon+1, 0).getDate();
        const frac = (dayDiff + 1)/daysInEndMonth;

        return (yrDiff * 12) + monDiff + frac;


    }

    function calculateEndDate({startDate, term}){

        let endDate = new Date(startDate);
        endDate = new Date( endDate.setMonth(endDate.getMonth() + term));
        const frac = term.toString().split('.')[1];
        if(frac) {
            const endMon = endDate.getMonth();
            const endYr = endDate.getFullYear();
            const daysInEndMonth = new Date(endYr, endMon+1, 0).getDate();
            const daysToAdd = Number(( daysInEndMonth * parseFloat('0.' + frac )).toFixed());
            endDate = new Date( endDate.setDate( endDate.getDate() + daysToAdd ));
        }
        endDate = new Date( endDate.setDate( endDate.getDate() - 1));

        return endDate;
    }
if i was just starting out... yeah I'd learn the libs its easier and someone else's problem... and that's what I tell junior devs... but i can do this, so i do
I'm not advocating for doing this!
c
Yeah but someone else has to maintain your code.
a
true, that's a totally fair point, in my defense these are little helper functions that don't really ever get updated
c
FYI const roundByExponentSafe = (pValue, pDecimals) => { return Number(Math.round((pValue + Number.EPSILON) + 'e' + pDecimals) + 'e-' + pDecimals); }; Input: 0 Output: NaN
a
you probably something like this?
Copy code
const betterRounding = (pValue, pDecimals) => {
    return Math.round((pValue + Number.EPSILON) * Math.pow(10,pDecimals)) / Math.pow(10,pDecimals);
};