Gaussian/banker's rounding in JavaScript - javascript

I have been using Math.Round(myNumber, MidpointRounding.ToEven) in C# to do my server-side rounding, however, the user needs to know 'live' what the result of the server-side operation will be which means (avoiding an Ajax request) creating a JavaScript method to replicate the MidpointRounding.ToEven method used by C#.
MidpointRounding.ToEven is Gaussian/banker's rounding, a very common rounding method for accounting systems described here.
Does anyone have any experience with this? I have found examples online, but they do not round to a given number of decimal places...

function evenRound(num, decimalPlaces) {
var d = decimalPlaces || 0;
var m = Math.pow(10, d);
var n = +(d ? num * m : num).toFixed(8); // Avoid rounding errors
var i = Math.floor(n), f = n - i;
var e = 1e-8; // Allow for rounding errors in f
var r = (f > 0.5 - e && f < 0.5 + e) ?
((i % 2 == 0) ? i : i + 1) : Math.round(n);
return d ? r / m : r;
}
console.log( evenRound(1.5) ); // 2
console.log( evenRound(2.5) ); // 2
console.log( evenRound(1.535, 2) ); // 1.54
console.log( evenRound(1.525, 2) ); // 1.52
Live demo: http://jsfiddle.net/NbvBp/
For what looks like a more rigorous treatment of this (I've never used it), you could try this BigNumber implementation.

This is the unusual stackoverflow where the bottom answers are better than the accepted. Just cleaned up #xims solution and made a bit more legible:
function bankersRound(n, d=2) {
var x = n * Math.pow(10, d);
var r = Math.round(x);
var br = Math.abs(x) % 1 === 0.5 ? (r % 2 === 0 ? r : r-1) : r;
return br / Math.pow(10, d);
}

That's a great solution from #soegaard.
Here is a small change that makes it work for decimal points:
bankers_round(n:number, d:number=0) {
var x = n * Math.pow(10, d);
var r = Math.round(x);
var br = (((((x>0)?x:(-x))%1)===0.5)?(((0===(r%2)))?r:(r-1)):r);
return br / Math.pow(10, d);
}
And while at it - here are some tests:
console.log(" 1.5 -> 2 : ", bankers_round(1.5) );
console.log(" 2.5 -> 2 : ", bankers_round(2.5) );
console.log(" 1.535 -> 1.54 : ", bankers_round(1.535, 2) );
console.log(" 1.525 -> 1.52 : ", bankers_round(1.525, 2) );
console.log(" 0.5 -> 0 : ", bankers_round(0.5) );
console.log(" 1.5 -> 2 : ", bankers_round(1.5) );
console.log(" 0.4 -> 0 : ", bankers_round(0.4) );
console.log(" 0.6 -> 1 : ", bankers_round(0.6) );
console.log(" 1.4 -> 1 : ", bankers_round(1.4) );
console.log(" 1.6 -> 2 : ", bankers_round(1.6) );
console.log(" 23.5 -> 24 : ", bankers_round(23.5) );
console.log(" 24.5 -> 24 : ", bankers_round(24.5) );
console.log(" -23.5 -> -24 : ", bankers_round(-23.5) );
console.log(" -24.5 -> -24 : ", bankers_round(-24.5) );

The accepted answer does round to a given number of places. In the process it calls toFixed which converts the number to a string. Since this is expensive, I offer the solution below. It rounds a number ending in 0.5 to the nearest even number. It does not handle rounding to an arbitrary number of places.
function even_p(n){
return (0===(n%2));
};
function bankers_round(x){
var r = Math.round(x);
return (((((x>0)?x:(-x))%1)===0.5)?((even_p(r))?r:(r-1)):r);
};

I was not happy with the other answers. They have either too verbose or complicated code or fail to round properly for negative numbers. For negative numbers we have to cleverly fix a weird behavior of JavaScript:
JavaScript's Math.round has the unusual property that it rounds halfway cases towards positive infinity, regardless of whether they're positive or negative. So for example 2.5 will round to 3.0, but -2.5 will round to -2.0.
Source
This is wrong, so we have to round down on negatives .5 before applying the bankers rounding, accordantly.
Also, just as Math.round, I want to round to the next integer and enforce a precision of 0. I just want Math.round with the correct and fixed "round halves to even" method in positive and negative. It needs to round the same like in other programming languages such as PHP (PHP_ROUND_HALF_EVEN) or C# (MidpointRounding.ToEven).
/**
* Returns a supplied numeric expression rounded to the nearest integer while rounding halves to even.
*/
function roundMidpointToEven(x) {
const n = x >= 0 ? 1 : -1 // n describes the adjustment on an odd rounding from midpoint
const r = n * Math.round(n * x) // multiplying n will fix negative rounding
return Math.abs(x) % 1 === 0.5 && r % 2 !== 0 ? r - n : r // we adjust by n if we deal with a half on an odd rounded number
}
// testing by rounding cents:
for(let i = -10; i <= 10; i++) {
const val = i + .5
console.log(val + " => " + roundMidpointToEven(val))
}
Math.round as well as our custom roundMidpointToEven function won't care for precision, because it's always better to calculate with cents to avoid float-point issues on any calculations anyways.
However, if you don't deal with cents you can simply multiply and divide the appropriate factor for the number of decimal placeholders in the same way you would do it for Math.round:
const usd = 9.225;
const fact = Math.pow(10, 2) // A precision of 2, so 100 is the factor
console.log(roundMidpointToEven(usd * fact) / fact) // outputs 9.22 instead of 9.23
To fully validate the custom roundMidpointToEven function, here is the same output using PHP with its official PHP_ROUND_HALF_EVEN as well as C# using MidpointRounding.ToEven:
for($i = -10; $i <= 10; $i++) {
$val = $i + .5;
echo $val . ' => ' . round($val, 0, PHP_ROUND_HALF_EVEN) . "<br />";
}
for(int i = -10; i <= 10; i++)
{
double val = i + .5;
Console.WriteLine(val + " => " + Math.Round(val, MidpointRounding.ToEven));
}
Both snippets return the same like the test call of our custom roundMidpointToEven:
-9.5 => -10
-8.5 => -8
-7.5 => -8
-6.5 => -6
-5.5 => -6
-4.5 => -4
-3.5 => -4
-2.5 => -2
-1.5 => -2
-0.5 => 0
0.5 => 0
1.5 => 2
2.5 => 2
3.5 => 4
4.5 => 4
5.5 => 6
6.5 => 6
7.5 => 8
8.5 => 8
9.5 => 10
10.5 => 10
Success!

const isEven = (value: number) => value % 2 === 0;
const isHalf = (value: number) => {
const epsilon = 1e-8;
const remainder = Math.abs(value) % 1;
return remainder > .5 - epsilon && remainder < .5 + epsilon;
};
const roundHalfToEvenShifted = (value: number, factor: number) => {
const shifted = value * factor;
const rounded = Math.round(shifted);
const modifier = value < 0 ? -1 : 1;
return !isEven(rounded) && isHalf(shifted) ? rounded - modifier : rounded;
};
const roundHalfToEven = (digits: number, unshift: boolean) => {
const factor = 10 ** digits;
return unshift
? (value: number) => roundHalfToEvenShifted(value, factor) / factor
: (value: number) => roundHalfToEvenShifted(value, factor);
};
const roundDollarsToCents = roundHalfToEven(2, false);
const roundCurrency = roundHalfToEven(2, true);
If you do not like the overhead of calling toFixed()
Want to be able to supply an arbitrary scale
Don't want to introduce floating-point errors
Want to have readable, reusable code
roundHalfToEven is a function that generates a fixed scale rounding function. I do my currency operations on cents, rather than dollars, to avoid introducing FPEs. The unshift param exists to avoid the overhead of unshifting and shifting again for those operations.

Stricly speaking, all of these implementations should handle the case of a negative number of digits to round to.
It is an edge case, but still it would be wise to disallow it (or be very clear about what that means, for example -2 is rounding to the nearest amount of hundreds).

This solution is slightly more elegant than any of the current answers. It handles rounding negative numbers and negative number of decimal places correct.
function bankersRound (value, nDec = 2) {
let x = value * Math.pow(10, nDec);
let r = Math.round(x);
return (Math.abs(x) % 1 === .5 ? r - (r % 2) : r) / Math.pow(10, nDec);
}

For folks who want to be able to read the code a little better, here's an alternative implementation that seems to work.
function bankersRound(n, decimalPlaces) {
// Create our multiplier for floating point precision issues.
const multiplier = Math.pow(10, decimalPlaces);
// Multiple by decimal places to avoid rounding issues w/ floats
const num = n * multiplier;
// Use standard rounding
const rounded = Math.round(num);
// Only odd numbers should be rounded
const shouldUseBankersRound = rounded % 2 !== 0;
// Subtract one to ensure the rounded number is even
const bankersRound = shouldUseBankersRound ? rounded - 1 : rounded;
// Return to original precision
return bankersRound / multiplier;
}
console.log(
bankersRound(1.5255, 2),
bankersRound(1.53543, 2),
bankersRound(1.54543, 2),
bankersRound(1.54543, 3),
bankersRound(1.53529, 4),
bankersRound(1.53529, 2),
bankersRound(4.5, 0),
bankersRound(5.5, 0),
bankersRound(0.045, 2),
bankersRound(0.055, 2)
);

Related

Get the exact decimal representation of a JavaScript number

Every finite number in JavaScript has an exact real value. For example:
const x = Number.MAX_VALUE
Here, x has the precise value of 21024 - 2971 =
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
and we can demonstrate this by using x in arithmetic:
console.log(x % 10000) // 8368
But how can I get all of those decimal digits?
I'd like it if the solution also worked for non-integers, for example const y = Number.EPSILON is precisely 2-52 =
0.0000000000000002220446049250313080847263336181640625
The most effective way I've found to do this is to
write the float into an ArrayBuffer, then read it back as a 64-bit unsigned BigInt
extract the sign, exponent and mantissa using bitwise operations
compute the desired result multiplied by a large power of 10
use string operations on the stringified BigInt to insert a decimal point in the right place.
For example:
const SIGN_BITS = 1n
const EXPONENT_BITS = 11n
const MANTISSA_BITS = 52n
const BIAS = 1023n
export const stringify = value => {
if (typeof value !== 'number') {
throw Error('Not a number')
}
if (!Number.isFinite(value)) {
return String(value)
}
const dataView = new DataView(new ArrayBuffer(8))
dataView.setFloat64(0, value)
const bigUint64 = dataView.getBigUint64(0)
const mantissaBits = (bigUint64 >> 0n) & ((1n << MANTISSA_BITS) - 1n)
const exponentBits = (bigUint64 >> MANTISSA_BITS) & ((1n << EXPONENT_BITS) - 1n)
const signBits = (bigUint64 >> (MANTISSA_BITS + EXPONENT_BITS)) & ((1n << SIGN_BITS) - 1n)
const sign = signBits === 0b0n ? '' : '-'
const isSubnormal = exponentBits === 0b0n
// So as to keep this in integers, multiply the fraction by 2 ** 52 while subtracting
// that same power from the exponent
const m = ((isSubnormal ? 0n : 1n) << MANTISSA_BITS) + mantissaBits
const e = (isSubnormal ? 1n : exponentBits) - BIAS - MANTISSA_BITS
if (e >= 0n) {
// Pure integers, no problem
return sign + String(m << e)
}
// Multiply by a large enough power of 10 that all possible decimal digits are preserved
// when we then divide by the power of 2
const power10 = 10n ** -e
const f = (m * power10) >> -e
const pre = f / power10
const post = f % power10
if (post === 0n) {
return sign + String(pre)
}
return sign + String(pre) + '.' + String(post).padStart(Number(-e), '0').replace(/0+$/, '')
}
console.log(stringify(Number.MAX_VALUE))
This outputs:
179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368
Similarly:
console.log(stringify(Number.EPSILON))
outputs:
0.0000000000000002220446049250313080847263336181640625
You mis understand floating point.
You get a max of about 14 digits as there is no more precision than. 2^52
The max number is a number that has three parts.
The sign, positive or negative and is 1 bit. Thus you get 0 and -0
The Fraction 52 bits that gives the precision of about 14 digits.
The exponent 11 bits and is a signed value that is the amount to multiply the Fraction value with. 2 ^ exponent
See wiki Double-precision_floating-point_format for more info.

Rounding of negative numbers in Javascript

We have come across a problem with Math.round() in JavaScript. The problem is that this function doesn't round correctly for negative numbers. For example :
1.5 ~= 2
0.5 ~= 1
-0.5 ~= 0 // Wrong
-1.5 ~= -1 // Wrong
And this is not correct according to arithmetic rounding. The correct numbers for -0.5 should be -1 and -1.5 should be -2.
Is there any standard way, to correctly round negative numbers in Javascript ?
Apply Math.round after converting to a positive number and finally roll back the sign. Where you can use Math.sign method to get the sign from the number and Math.abs to get the absolute of the number.
console.log(
Math.sign(num) * Math.round(Math.sign(num) * num),
// or
Math.sign(num) * Math.round(Math.abs(num))
)
var nums = [-0.5, 1.5, 3, 3.6, -4.8, -1.3];
nums.forEach(function(num) {
console.log(
Math.sign(num) * Math.round(Math.sign(num) * num),
Math.sign(num) * Math.round(Math.abs(num))
)
});
You could save the sign and apply later, in ES5;
function round(v) {
return (v >= 0 || -1) * Math.round(Math.abs(v));
}
console.log(round(1.5)); // 2
console.log(round(0.5)); // 1
console.log(round(-1.5)); // -2
console.log(round(-0.5)); // -1
You could try using Math.ceil(num) to round num and then - 1 if num was negative, e.g.
if (num < 0) {
num = Math.ceil(num) - 1;
} else {
num = Math.ceil(num);
}
ES6 has added a method called Math.trunc using which we can get the integer part of a decimal number
The Math.trunc() function returns the integer part of a number by
removing any fractional digits.
Math.trunc(42.84) -> Returns 42
Math.trunc(5.3) -> Returns 5
Math.trunc(-4.3) -> Returns -4
Math.trunc(-3.123) -> Returns -3
var r = (Math.random() * 200) - 100;
How about purely evaluating the result without using the math library? eg, by using a ternary operator, you can elegantly check for negative numbers and then floor after "rounding":
var n = r + (r < 0 ? -0.5 : 0.5) | 0;
The | 0 is just a silly trick in js that "overloads" the binary operator (you could use any binary operator) in order to truncate the number.
Note that this is not flooring (like Math.floor), since Math.floor(-3.2), for example, will actually yield -4.
One could even do something similar to #Balan's answer (I like that one and the one below, but I feel like this or the ternary operator will just be a touch faster--I am probably wrong, though, because the Math libraries have been proven to be very fast):
var n = (r + Math.sign(r) / 2) | 0;
probably the fastest, most elegant way:
var n = Math.floor(r + 0.5);
example:
var body = document.getElementById("myTable").children[1];
var i, iMax = 100, r, tr, td;
for (i = 0; i < iMax; i++) {
r = Math.random() * 200 - 100;
tr = document.createElement("tr");
td = document.createElement("td");
td.innerHTML = r;
tr.appendChild(td);
td = document.createElement("td");
td.innerHTML = (r + Math.sign(r) / 2) | 0;
tr.appendChild(td);
body.appendChild(tr);
}
#myTable {
min-width: 250px;
}
<table id="myTable">
<thead>
<tr>
<th>float</th>
<th>round</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Rounding with precision.
Rounds to Int by default or, if second argument is provided, to n digits after decimal point.
+ '1' fixes incorrect rounding for .0X5 numbers.
function round(num, digits=0) {
if (!(num % 1)) return num
return +Number(num + '1').toFixed(digits)
}
First observe that Math.round(v) is defined as Math.floor(v + 0.5):
Math.round(-1.7) === -2
Math.round(-1.3) === -1
Math.floor(-1.7 + 0.5) === -2
Math.floor(-1.3 + 0.5) === -1
Then realize that parseInt(v) is similar to Math.floor(v), however, it handles negative numbers differently (exactly how you expected it to behave):
Math.floor(1.7) === 1
Math.floor(-1.7) === -2
parseInt(1.7) === 1
parseInt(-1.7) === -1
So the parseInt(v) behavior is what you want, but instead of flooring, you want rounding. In order to apply this to any number, we need to add +0.5 for positive numbers, and -0.5 for negative numbers. So the solution is this function:
var parseIntRound = function(v)
{
return parseInt(v + Math.sign(v)/2);
};
// or with modern syntactic sugar:
// const parseIntRound = v => parseInt(v + Math.sign(v)/2);
parseIntRound( 1.5) === 2
parseIntRound( 0.5) === 1
parseIntRound(-0.5) === -1
parseIntRound(-1.3) === -1
parseIntRound(-1.5) === -2
parseIntRound(-1.7) === -2
Where Math.sign(v) returns 1 for any positive number (>0), -1 for any negative number (<0), and 0 for positive zero (and -0 for negative zero).

Using Javascript to convert radians to degrees with positive and negative values [duplicate]

According to Google Calculator (-13) % 64 is 51.
According to Javascript (see this JSBin) it is -13.
How do I fix this?
Number.prototype.mod = function (n) {
"use strict";
return ((this % n) + n) % n;
};
Taken from this article: The JavaScript Modulo Bug
Using Number.prototype is SLOW, because each time you use the prototype method your number is wrapped in an Object. Instead of this:
Number.prototype.mod = function(n) {
return ((this % n) + n) % n;
}
Use:
function mod(n, m) {
return ((n % m) + m) % m;
}
See: https://jsperf.app/negative-modulo/2
~97% faster than using prototype. If performance is of importance to you of course..
The % operator in JavaScript is the remainder operator, not the modulo operator (the main difference being in how negative numbers are treated):
-1 % 8 // -1, not 7
A "mod" function to return a positive result.
var mod = function (n, m) {
var remain = n % m;
return Math.floor(remain >= 0 ? remain : remain + m);
};
mod(5,22) // 5
mod(25,22) // 3
mod(-1,22) // 21
mod(-2,22) // 20
mod(0,22) // 0
mod(-1,22) // 21
mod(-21,22) // 1
And of course
mod(-13,64) // 51
The accepted answer makes me a little nervous because it re-uses the % operator. What if Javascript changes the behavior in the future?
Here is a workaround that does not re-use %:
function mod(a, n) {
return a - (n * Math.floor(a/n));
}
mod(1,64); // 1
mod(63,64); // 63
mod(64,64); // 0
mod(65,64); // 1
mod(0,64); // 0
mod(-1,64); // 63
mod(-13,64); // 51
mod(-63,64); // 1
mod(-64,64); // 0
mod(-65,64); // 63
If x is an integer and n is a power of 2, you can use x & (n - 1) instead of x % n.
> -13 & (64 - 1)
51
Fix negative modulo (reminder operator %)
Simplified using ES6 Arrow function, and without dangerously extending the Number prototype
const mod = (n, m) => (n % m + m) % m;
console.log(mod(-90, 360)); // 270 (Instead of -90)
Though it isn't behaving as you expected, it doesn't mean that JavaScript is not 'behaving'. It is a choice JavaScript made for its modulo calculation. Because, by definition either answer makes sense.
See this from Wikipedia. You can see on the right how different languages chose the result's sign.
This is not a bug, there's 3 functions to calculate modulo, you can use the one which fit your needs (I would recommend to use Euclidean function)
Truncating the decimal part function
console.log( 41 % 7 ); // 6
console.log( -41 % 7 ); // -6
console.log( -41 % -7 ); // -6
console.log( 41 % -7 ); // 6
Integer part function
Number.prototype.mod = function(n) {
return ((this%n)+n)%n;
};
console.log( parseInt( 41).mod( 7) ); // 6
console.log( parseInt(-41).mod( 7) ); // 1
console.log( parseInt(-41).mod(-7) ); // -6
console.log( parseInt( 41).mod(-7) ); // -1
Euclidean function
Number.prototype.mod = function(n) {
var m = ((this%n)+n)%n;
return m < 0 ? m + Math.abs(n) : m;
};
console.log( parseInt( 41).mod( 7) ); // 6
console.log( parseInt(-41).mod( 7) ); // 1
console.log( parseInt(-41).mod(-7) ); // 1
console.log( parseInt( 41).mod(-7) ); // 6
So it seems that if you're trying to mod around degrees (so that if you have -50 degrees - 200 degrees), you'd want to use something like:
function modrad(m) {
return ((((180+m) % 360) + 360) % 360)-180;
}
I deal with négative a and negative n too
//best perf, hard to read
function modul3(a,n){
r = a/n | 0 ;
if(a < 0){
r += n < 0 ? 1 : -1
}
return a - n * r
}
// shorter code
function modul(a,n){
return a%n + (a < 0 && Math.abs(n));
}
//beetween perf and small code
function modul(a,n){
return a - n * Math[n > 0 ? 'floor' : 'ceil'](a/n);
}
There is a NPM package that will do the work for you. You can install it with the following command.
npm install just-modulo --save
Usage copied from the README
import modulo from 'just-modulo';
modulo(7, 5); // 2
modulo(17, 23); // 17
modulo(16.2, 3.8); // 17
modulo(5.8, 3.4); //2.4
modulo(4, 0); // 4
modulo(-7, 5); // 3
modulo(-2, 15); // 13
modulo(-5.8, 3.4); // 1
modulo(12, -1); // NaN
modulo(-3, -8); // NaN
modulo(12, 'apple'); // NaN
modulo('bee', 9); // NaN
modulo(null, undefined); // NaN
GitHub repository can be found via the following link:
https://github.com/angus-c/just/tree/master/packages/number-modulo
For fun, here's a "wrap" function that works sorta like a modulo, except you can also specify the minimum value of the range (instead of it being 0):
const wrap = (value = 0, min = 0, max = 10) =>
((((value - min) % (max - min)) + (max - min)) % (max - min)) + min;
Basically just takes the true modulo formula, offsets it such that min ends up at 0, then adds min back in after.
Useful if you have a value that you want to keep between two values.

convert decimal number to fraction in javascript or closest fraction [duplicate]

This question already has answers here:
How to simplify a decimal into the smallest possible fraction?
(6 answers)
Closed last year.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
So i want to be able to convert any decimal number into fraction. In both forms such as one without remainder like this: 3/5 or with remainder: 3 1/4.
what i was doing is this..
lets say i have number .3435.
Calculate amount of digits after decimals.
multiply by 10 with power of the amount before number.
then somehow find greatest common factor.
Now i don't know how to find GCF. And nor i know how to implement logic to find fraction that represents a number closely or in remainder form if exact fraction doesn't exists.
code i have so far: (testing)
x = 34/35;
a = x - x.toFixed();
tens = (10).pow(a.toString().length - 2);
numerator = tens * x;
denominator = tens;
Your first 2 steps are reasonable.
But what you should do is for the numerator and denominator calculate the Greatest Common Divisor (GCD) and then divide the numerator and denominator with that divisor to get the fraction you want.
GCD is rather easy to calculate. Here is Euclid's algorithm:
var gcd = function(a, b) {
if (!b) return a;
return gcd(b, a % b);
};
Edit
I've added a fully working JSFiddle.
Unless you are willing to work on developing something yourself then I would suggest using a library that someone has already put effort into, like fraction.js
Javascript
var frac = new Fraction(0.3435);
console.log(frac.toString());
Output
687/2000
On jsFiddle
You can use brute force test on different denominators and retain the result that has least error.
The algorithm below is an example of how you might go about this, but, suffers from being inefficient and limited to searching for denominators up to 10000.
function find_rational( value, maxdenom ) {
console.clear();
console.log( "Looking up: " + value );
let best = { numerator: 1, denominator: 1, error: Math.abs(value - 1) }
if ( !maxdenom ) maxdenom = 10000;
for ( let denominator = 1; best.error > 0 && denominator <= maxdenom; denominator++ ) {
let numerator = Math.round( value * denominator );
let error = Math.abs( value - numerator / denominator );
if ( error >= best.error ) continue;
best.numerator = numerator;
best.denominator = denominator;
best.error = error;
console.log( "Intermediate result: "
+ best.numerator + "/" + best.denominator
+ " (" + ( best.numerator/best.denominator)
+ " error " + best.error + " )" );
}
console.log( "Final result: " + JSON.stringify( best ) );
return best;
}
function calc() {
const value = parseFloat( $("#myInput").val() );
if ( isNaN(value) ) {
$( "#myResult" ).val( "NaN" );
return;
}
const rational = find_rational( value, 10000 );
$("#myResult").val( rational.numerator
+ " / " + rational.denominator
+ " ( Error: " + rational.error + " )" );
}
calc();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<P>
Enter a decimal number:<BR/>
<INPUT type="text" name="myInput" id="myInput" value=".3435" onkeyup="calc()"/><BR/>
</P>
<P>
Resulting Rational:<BR/>
<INPUT name="myResult" id="myResult" value=""/><BR/>
</P>
The above determines the .3435 as a fraction is 687 / 2000.
Also, had you gave it PI (e.g. 3.1415926) it produces good looking fractions like 22/7 and 355/113.
One quick and easy way of doing it is
getFraction = (decimal) => {
for(var denominator = 1; (decimal * denominator) % 1 !== 0; denominator++);
return {numerator: decimal * denominator, denominator: denominator};
}
I get very poor results using the GCD approach. I got much better results using an iterative approach.
For example, here is a very crude approach that zeros in on a fraction from a decimal:
function toFraction(x, tolerance) {
if (x == 0) return [0, 1];
if (x < 0) x = -x;
if (!tolerance) tolerance = 0.0001;
var num = 1, den = 1;
function iterate() {
var R = num/den;
if (Math.abs((R-x)/x) < tolerance) return;
if (R < x) num++;
else den++;
iterate();
}
iterate();
return [num, den];
}
The idea is you increment the numerator if you are below the value, and increment the denominator if you are above the value.
Use the Euclidean algorithm to find the greatest common divisor.
function reduce(numerator,denominator){
var gcd = function gcd(a,b){
return b ? gcd(b, a%b) : a;
};
gcd = gcd(numerator,denominator);
return [numerator/gcd, denominator/gcd];
}
This will provide you with the following results on your console
reduce(2,4);
// [1,2]
reduce(13427,3413358);
// [463,117702]
So by continuing from already what you have,
var x = 34/35;
var a = x - x.toFixed();
var tens = Math.pow(10,a.toString().length - 2);
var numerator = tens * x;
var denominator = tens;
reduce(numerator,denominator);
Source: https://stackoverflow.com/a/4652513/1998725
I had researched all over the website and I did combine all code into one, Here you go!
function fra_to_dec(num){
var test=(String(num).split('.')[1] || []).length;
var num=(num*(10**Number(test)))
var den=(10**Number(test))
function reduce(numerator,denominator){
var gcd = function gcd(a,b) {
return b ? gcd(b, a%b) : a;
};
gcd = gcd(numerator,denominator);
return [numerator/gcd, denominator/gcd];
}
return (reduce(num,den)[0]+"/"+reduce(num,den)[1])
}
This code is very easy to use! You can even put number in this function!
The tricky bit is not letting floating points get carried away.
Converting a number to a string restrains the trailing digits,
especially when you have a decimal with an integer, like 1.0625.
You can round off clumsy fractions, by passing a precision parameter.
Often you want to force a rounded value up, so a third parameter can specify that.
(e.g.; If you are using a precision of 1/64, the smallest return for a non-zero number will be 1/64, and not 0.)
Math.gcd= function(a, b){
if(b) return Math.gcd(b, a%b);
return Math.abs(a);
}
Math.fraction= function(n, prec, up){
var s= String(n),
p= s.indexOf('.');
if(p== -1) return s;
var i= Math.floor(n) || '',
dec= s.substring(p),
m= prec || Math.pow(10, dec.length-1),
num= up=== 1? Math.ceil(dec*m): Math.round(dec*m),
den= m,
g= Math.gcd(num, den);
if(den/g==1) return String(i+(num/g));
if(i) i= i+' and ';
return i+ String(num/g)+'/'+String(den/g);
}
Math.roundFraction(.3435,64); value: (String) 11/32
Inspired by #chowey answer, which contained recursive implementation of finding close fraction for a decimal value within given tolerance, here is better (see benchmark), iterative version of it.
function toFractionIterative(x, epsilon = 0.0001) {
if (x == 0) return [0, 1];
const a = Math.abs(x);
let n = 0;
let d = 1;
let r;
while (true) {
r = n / d;
if (Math.abs((r - a) / a) < epsilon) {
break;
}
if (r < a) {
n++;
}
else {
d++;
}
}
return [x < 0 ? -n : n, d];
}
Benchmark (tl;dr: recursive 1,589 ops/s, iterative 5,955 ops/s; use iterative approach)
let v = 3.141592;
document.write(d2f(v)); // 392699/125000
function d2f(v) // decimal to fraction
{
if (Math.floor(v) == v) return v + '/' + 1;
v = Math.abs(v);
let ret = .01, // rounding error tolerance
td = v-Math.floor(v), // trailing digits
r = 1/td, // reciprocal
d = r, // start building denominator
lim = 20; // max loop limit
for (let i = 0; i < lim; i++)
{
td = r-Math.floor(r);
if (Math.abs(r-Math.round(r)) < ret) break;
r = 1/td;
d *= r;
}
return Math.round(d*v) + '/' + Math.round(d);
}
I came up with this for 16ths
function getfract(theNum){
var input=theNum.toString();
var whole = input.split(".")[0];
var rem = input.split(".")[1] * .1;
return(whole + " " + Math.round(rem * 16) + "/16");
}
function decimalToFraction(num) {
let numsAfterDecPoint = num.toString().split('.')[1] ? num.toString().split('.')[1].length : 0;
let numerator = num * Math.pow(10, numsAfterDecPoint);
let denominator = Math.pow(10, numsAfterDecPoint);
console.log(numerator + " / " + denominator)
let d = GCD(numerator,denominator)
return numerator / d + " / " + denominator / d
}
console.log(decimalToFraction(0.5)); // 5 / 10 => 1 / 2
console.log(decimalToFraction(178.45)); // 17845 / 100 => 3569 / 20
function GCD(a,b) {
let r = 0;
while(b != 0) {
r = a % b
a = b;
b = r;
}
return a;
}

How to round float numbers in javascript?

I need to round for example 6.688689 to 6.7, but it always shows me 7.
My method:
Math.round(6.688689);
//or
Math.round(6.688689, 1);
//or
Math.round(6.688689, 2);
But result always is the same 7... What am I doing wrong?
Number((6.688689).toFixed(1)); // 6.7
var number = 6.688689;
var roundedNumber = Math.round(number * 10) / 10;
Use toFixed() function.
(6.688689).toFixed(); // equal to "7"
(6.688689).toFixed(1); // equal to "6.7"
(6.688689).toFixed(2); // equal to "6.69"
Upd (2019-10). Thanks to Reece Daniels code below now available as a set of functions packed in npm-package expected-round (take a look).
You can use helper function from MDN example. Than you'll have more flexibility:
Math.round10(5.25, 0); // 5
Math.round10(5.25, -1); // 5.3
Math.round10(5.25, -2); // 5.25
Math.round10(5, 0); // 5
Math.round10(5, -1); // 5
Math.round10(5, -2); // 5
Upd (2019-01-15). Seems like MDN docs no longer have this helper funcs. Here's a backup with examples:
// Closure
(function() {
/**
* Decimal adjustment of a number.
*
* #param {String} type The type of adjustment.
* #param {Number} value The number.
* #param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* #returns {Number} The adjusted value.
*/
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// If the value is negative...
if (value < 0) {
return -decimalAdjust(type, -value, exp);
}
// Shift
value = value.toString().split('e');
value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
}
// Decimal round
if (!Math.round10) {
Math.round10 = function(value, exp) {
return decimalAdjust('round', value, exp);
};
}
// Decimal floor
if (!Math.floor10) {
Math.floor10 = function(value, exp) {
return decimalAdjust('floor', value, exp);
};
}
// Decimal ceil
if (!Math.ceil10) {
Math.ceil10 = function(value, exp) {
return decimalAdjust('ceil', value, exp);
};
}
})();
Usage examples:
// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
Math.round10(-1.005, -2); // -1.01
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50
> +(6.688687).toPrecision(2)
6.7
A Number object in JavaScript has a method that does exactly what you need. That method is Number.toPrecision([precision]).
Just like with .toFixed(1) it converts the result into a string, and it needs to be converted back into a number. Done using the + prefix here.
simple benchmark on my laptop:
number = 25.645234 typeof number
50000000 x number.toFixed(1) = 25.6 typeof string / 17527ms
50000000 x +(number.toFixed(1)) = 25.6 typeof number / 23764ms
50000000 x number.toPrecision(3) = 25.6 typeof string / 10100ms
50000000 x +(number.toPrecision(3)) = 25.6 typeof number / 18492ms
50000000 x Math.round(number*10)/10 = 25.6 typeof number / 58ms
string = 25.645234 typeof string
50000000 x Math.round(string*10)/10 = 25.6 typeof number / 7109ms
If you not only want to use toFixed() but also ceil() and floor() on a float then you can use the following function:
function roundUsing(func, number, prec) {
var tempnumber = number * Math.pow(10, prec);
tempnumber = func(tempnumber);
return tempnumber / Math.pow(10, prec);
}
Produces:
> roundUsing(Math.floor, 0.99999999, 3)
0.999
> roundUsing(Math.ceil, 0.1111111, 3)
0.112
UPD:
The other possible way is this:
Number.prototype.roundUsing = function(func, prec){
var temp = this * Math.pow(10, prec)
temp = func(temp);
return temp / Math.pow(10, prec)
}
Produces:
> 6.688689.roundUsing(Math.ceil, 1)
6.7
> 6.688689.roundUsing(Math.round, 1)
6.7
> 6.688689.roundUsing(Math.floor, 1)
6.6
My extended round function:
function round(value, precision) {
if (Number.isInteger(precision)) {
var shift = Math.pow(10, precision);
// Limited preventing decimal issue
return (Math.round( value * shift + 0.00000000000001 ) / shift);
} else {
return Math.round(value);
}
}
Example Output:
round(123.688689) // 123
round(123.688689, 0) // 123
round(123.688689, 1) // 123.7
round(123.688689, 2) // 123.69
round(123.688689, -2) // 100
round(1.015, 2) // 1.02
See below
var original = 28.59;
var result=Math.round(original*10)/10 will return you returns 28.6
Hope this is what you want..
There is the alternative .toLocaleString() to format numbers, with a lot of options regarding locales, grouping, currency formatting, notations. Some examples:
Round to 1 decimal, return a float:
const n = +6.688689.toLocaleString('fullwide', {maximumFractionDigits:1})
console.log(
n, typeof n
)
Round to 2 decimals, format as currency with specified symbol, use comma grouping for thousands:
console.log(
68766.688689.toLocaleString('fullwide', {maximumFractionDigits:2, style:'currency', currency:'USD', useGrouping:true})
)
Format as locale currency:
console.log(
68766.688689.toLocaleString('fr-FR', {maximumFractionDigits:2, style:'currency', currency:'EUR'})
)
Round to minimum 3 decimal, force zeroes to display:
console.log(
6.000000.toLocaleString('fullwide', {minimumFractionDigits:3})
)
Percent style for ratios. Input * 100 with % sign
console.log(
6.688689.toLocaleString('fullwide', {maximumFractionDigits:2, style:'percent'})
)
I have very good solution with if toFixed() is not working.
function roundOff(value, decimals) {
return Number(Math.round(value+'e'+decimals)+'e-'+decimals);
}
Example
roundOff(10.456,2) //output 10.46
float(value,ndec);
function float(num,x){
this.num=num;
this.x=x;
var p=Math.pow(10,this.x);
return (Math.round((this.num).toFixed(this.x)*p))/p;
}
+((6.688689 * (1 + Number.EPSILON)).toFixed(1)); // 6.7
+((456.1235 * (1 + Number.EPSILON)).toFixed(3)); // 456.124
I think this function can help.
function round(value, ndec){
var n = 10;
for(var i = 1; i < ndec; i++){
n *=10;
}
if(!ndec || ndec <= 0)
return Math.round(value);
else
return Math.round(value * n) / n;
}
round(2.245, 2) //2.25
round(2.245, 0) //2
if you're under node.js context, you can try mathjs
const math = require('mathjs')
math.round(3.1415926, 2)
// result: 3.14
Math.round((6.688689 + Number.EPSILON) * 10) / 10
Solution stolen from https://stackoverflow.com/a/11832950/2443681
This should work with nearly any float value. It doesn't force decimal count though. It's not clear whether this was a requirement. Should be faster than using toFixed(), which has other issues as well based on the comments to other answers.
A nice utility function to round in needed decimal precision:
const roundToPrecision = (value, decimals) => {
const pow = Math.pow(10, decimals);
return Math.round((value + Number.EPSILON) * pow) / pow;
};
I think below function can help
function roundOff(value,round) {
return (parseInt(value * (10 ** (round + 1))) - parseInt(value * (10 ** round)) * 10) > 4 ? (((parseFloat(parseInt((value + parseFloat(1 / (10 ** round))) * (10 ** round))))) / (10 ** round)) : (parseFloat(parseInt(value * (10 ** round))) / ( 10 ** round));
}
usage : roundOff(600.23458,2); will return 600.23
Minor tweak to this answer:
function roundToStep(value, stepParam) {
var step = stepParam || 1.0;
var inv = 1.0 / step;
return Math.round(value * inv) / inv;
}
roundToStep(2.55, 0.1) = 2.6
roundToStep(2.55, 0.01) = 2.55
roundToStep(2, 0.01) = 2
How to correctly round decimals in a number (basics):
We start from far right number:
If this number is >= to 5 rounding is required, we will then report a 1 to the first number on the left.
If this number is < to 5 means no rounding
Once you know if you need to report a value or not you can delete the last number and repeat the operation.
If there is a value to be reported you will first add it to the new far right number before repeating the previous tests.
Beware there is a special case when you need to report a value and the number that must be added to that value is 9 : in such case you will have to change the number value for 0 before reporting a 1 on the following left number.
For some of the failing answers it looks like decimals are splitted left to right for the required amount of decimals without even caring about the rounding.
Now that this is stated here is a function that will round a provided float value recursively using the above logic.
function roundFloatR(n, precision = 0, opts = { return: 'number' }) { // Use recursivity
if ( precision == 0 ) { // n will be rounded to the closest integer
if (opts.return == 'number') return Math.round(n);
else if (opts.return == 'string') return `${Math.round(n)}`;
} else {
let ns = `${n}`.split(''); // turns float into a string before splitting it into a char array
if ( precision < 0 ) { // precision is a negative number
precision += ns.length - 1; // precision equals last index of ns - its actual value
} else if ( precision > 0 ) { // precision is a positive number
if ( ns.indexOf('.') > -1 )
precision += ns.indexOf('.'); // precision equals its value + the index of the float separator in the string / array of char
}
// RECURSIVE FUNCTION: loop from the end of ns to the precision index while rounding the values
// index: index in the ns char array, rep: reported value, (INTERNAL_VAR, cn: current number)
const recursive = (index, rep) => {
let cn = parseInt(ns[index]); // get the current number from ns at index
if (index <= precision) { // current index inferior or equal to the defined precision index (end of rounding)
if (rep) { // if a reported value exists
cn += rep; // add reported value to current number
if (cn == 10) { // extends rounding for special case of decimals ending with 9 + reported value
ns[index] = '0';
recursive( (index - 1), 1 ); // calls recursive() again with a reported value
} else if (cn < 10)
ns[index] = `${cn}`;
}
} else if (index > precision) { // current index superior to defined precision index
ns.pop(); // each passage in this block will remove the last entry of ns
if (rep) cn += rep; // adds reported value (if it exists) to current number
if ( cn >= 5 ) // ROUNDING
recursive( (index - 1), 1 ); // calls recursive() again with a reported value
else // NO ROUNDING
recursive( index - 1 ); // calls recursive() again w/o a reported value
}
}; // end of recursive()
recursive(ns.length - 1); // starts recursive rounding over the ns char array (arg is the last index of ns)
if (opts.return == "number") return parseFloat(ns.join('')); // returns float number
else if (opts.return == "string") return ns.join(''); // returns float number as string
}
} //
How it works:
We first turn the provided float value into a string before splitting it into an array of char using the String.split('') instruction.
Then we will call the recursive() function with the last index of the array of chars as argument, to iterate through that array from last index to the precision index while rounding the value.
Arguments explanation:
There is a total of 3 arguments which allow different functionnalities.
n:
the value to be rounded (number or string).
precision: [default = 0]
an int which represent the amount of decimals we want to round the provided number to.
There are 3 possibilities:
precision == 0: value returned will be the same as using the Math.round() method
precision > 0: precision will be defined from the float separator index + precision value
precision < 0: precision will be defined from the index of the last number - precision value
opts: [default = {return: 'number'}]
an options object with a unique property called return which take a string value options are 'number' or 'string'. allows the selection of the type of value returned by the function
2nd and 3rd arguments are optionnals
Usage and examples:
using a float value
let n = 20.336099982261654;
let r = roundFloatR(n); // r = 20
r = roundFloatR(n, 2); // r = 20.34
r = roundFloatR(n, 6); // r = 20.3361
r = roundFloatR(n, 6, {return: 'string'}); // r = "20.336100"
// negative precision
r = roundFloatR(n, -2); // r = 20.3360999822617
using a string value
let n = '20.48490002346038';
let r = roundFloatR(n); // r = 20
r = roundFloatR(n, 2); // r = 20.49
r = roundFloatR(n, 6); // r = 20.4849
r = roundFloatR(n, 6, {return: 'string'}); // r = "20.484900"
// negative precision
r = roundFloatR(n, -10); // r = 20.4849
What about performance ?
Most of the time it will convert the provided value in under .3 ms. (measured with performance.now())
What is not supported and possible issues:
not supported: exponential type values some changes may be required to support them.
possible issues:
a negative precision value that exceeds the provided number length or its float separator index may cause unexpected results as these cases are not handled yet.
no error handling in case the n parameter doesn't match what is currently asked.
If you're using Browserify today, you're going to have to try: roundTo a very useful NPM lib

Categories

Resources