Reputation: 1145
I want to do custom round up to near 0.05 , based on following condition.It is hard to explain , but following example will be easy to understand .
12.910 - 12.90
12.920 - 12.90
12.930 - 12.90
12.940 - 12.90
12.941 - 12.95
12.950 - 12.95
12.960 - 12.95
12.970 - 12.95
12.980 - 12.95
12.990 - 12.95
12.991 - 13.00
13.000 - 13.00
I tried several function , but it is rounding up 12.98 to 13.00.
function customRound( num) {
return Math.round(num * 20) / 20;
}
Upvotes: 1
Views: 343
Reputation: 5171
Coming at this visually, your rounding algorithm seems to look like this:
The dot is where you want to round to for that interval. ( marks the open end of an interval, ] the closed end. (12.99 belongs to the red interval.) We'll implement this algorithm by manipulating the line to match Math.floor
's.
First, let's work with integers.
num * 100
Your rounding interval is left-open and right-closed, but Math.floor
is left-closed and right-open. We can flip the line to match by multiplying by −1:
num * 100 * -1
⇒ num * -100
Your rounding intervals' lengths are 5, so we need to put the ends of the intervals on multiples of 5...
num * -100 - 1
...before dividing by 5 to match Math.floor
.
(num * -100 - 1 ) / 5
⇒ num * -20 - 0.2
Now we can take the floor.
return Math.floor(num * -20 - 0.2);
Scale back up to the original by multiplying by 5:
return Math.floor(num * -20 - 0.2) * 5;
Shift the returned value over to the dot by adding 4:
return Math.floor(num * -20 - 0.2) * 5 + 4;
Undo the alignment we did earlier:
return Math.floor(num * -20 - 0.2) * 5 + 4 + 1;
⇒ return Math.floor(num * -20 - 0.2) * 5 + 5;
Undo the flip:
return (Math.floor(num * -20 - 0.2) * 5 + 5) * -1;
⇒ return Math.floor(num * -20 - 0.2) * -5 - 5;
And divide the whole thing by 100 to get your original scale back:
return (Math.floor(num * -20 - 0.2) * -5 - 5) / 100;
⇒ return Math.floor(num * -20 - 0.2) * -0.05 - 0.05;
Using Robin Zigmond's testing framework,
function customRound(num) {
return Math.floor(num * -20 - 0.2) * -0.05 - 0.05;
}
// test desired results
var tests = [12.91, 12.92, 12.93, 12.94, 12.941, 12.95, 12.96, 12.97, 12.98, 12.99, 12.991, 13];
for (var i=0; i<tests.length; i++) {
console.log(`${tests[i].toFixed(3)} - ${customRound(tests[i]).toFixed(2)}`);
}
Upvotes: 2
Reputation: 18249
As far as I can tell from your example, the desired behaviour appears to be "round up to the nearest 0.01, then round that result down to the nearest 0.05".
This can be implemented as follows. As you can see, it agrees exactly with your examples (I even took care to format it the same way) - but please let me know if I've got the wrong end of the stick.
function customRound(num) {
var intermediateResult = Math.ceil(num*100)/100;
return Math.floor(intermediateResult*20)/20;
}
// test desired results
var tests = [12.91, 12.92, 12.93, 12.94, 12.941, 12.95, 12.96, 12.97, 12.98, 12.99, 12.991, 13];
for (var i=0; i<tests.length; i++) {
console.log(`${tests[i].toFixed(3)} - ${customRound(tests[i]).toFixed(2)}`);
}
Upvotes: 1
Reputation: 135
If you really intended to round everything within < .01 of the nearest .05 then try the below, to get the precision of the number it uses answer from Is there a reliable way in JavaScript to obtain the number of decimal places of an arbitrary number?
function decimalPlaces(n) {
var s = "" + (+n);
var match = /(?:\.(\d+))?(?:[eE]([+\-]?\d+))?$/.exec(s);
if (!match) { return 0; }
return Math.max(
0, // lower limit.
(match[1] == '0' ? 0 : (match[1] || '').length)
- (match[2] || 0));
}
var test = 12.941;
var factor = Math.pow(10,decimalPlaces(test));
var remainder = ((test * factor) % (.05 * factor))/factor;
var result;
if (remainder>.04) {
result = Math.round(test*20)/20;
} else {
result = (test*factor - remainder*factor)/factor;
}
console.log('result is:',result);
Upvotes: 1