Use Intl.NumberFormat without rounding - javascript

I'm using Intl.NumberFormat to format numbers:
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 1,
maximumFractionDigits: 4,
minimumSignificantDigits: 1,
maximumSignificantDigits: 4
})
formatter.format(0.99999) // results: '1'. desired result: '0.9999'
formatter.format(0.006393555) // results: '0.006394'. desired result: '0.006393'
formatter.format(0.9972620384752073) // results: '0.9973'. desired result: '0.9972'
formatter.format(12345.67) // results: '12,350'. desired result: '12,345.67'
formatter.format(200001) // results: '200,000'. desired result: '200,001'
As you can see the numbers are being rounded automatically, which is undesirable behavior in my case.
Is there a way to tell the formatter not to round?
I Didn't found any option or combination of options to achieve that.

I don't think this is possible with current spec and there are few proposals for the new spec, but you can still use formatToParts method and add custom function to format number parts as you wish.
For your first use case it could look something like:
const trauncateFractionAndFormat = (parts, digits) => {
return parts.map(({ type, value }) => {
if (type !== 'fraction' || !value || value.length < digits) {
return value;
}
let retVal = "";
for (let idx = 0, counter = 0; idx < value.length && counter < digits; idx++) {
if (value[idx] !== '0') {
counter++;
}
retVal += value[idx];
}
return retVal;
}).reduce((string, part) => string + part);
};
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 20
})
console.log(trauncateFractionAndFormat(formatter.formatToParts(0.99999), 4));
console.log(trauncateFractionAndFormat(formatter.formatToParts(0.006393555), 4));
console.log(trauncateFractionAndFormat(formatter.formatToParts(0.9972620384752073), 4));
console.log(trauncateFractionAndFormat(formatter.formatToParts(12345.67), 4));
console.log(trauncateFractionAndFormat(formatter.formatToParts(20001), 4));

NumberFormat will always round up, but you can play around this one extra function.
function roundDownSignificantDigits(number, decimals) {
let significantDigits = (parseInt(number.toExponential().split('e-')[1])) || 0;
let decimalsUpdated = (decimals || 0) + significantDigits - 1;
decimals = Math.min(decimalsUpdated, number.toString().length);
return (Math.floor(number * Math.pow(10, decimals)) / Math.pow(10, decimals));
}
and then
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 1,
maximumFractionDigits: 4,
minimumSignificantDigits: 1,
maximumSignificantDigits: 4
})
result:
formatter.format(roundDownSignificantDigits(0.99999,4)); // "0.9999"
formatter.format(roundDownSignificantDigits(0.006393555,4)); // "0.006393"
formatter.format(roundDownSignificantDigits(0.9972620384752073,4)); // "0.9972"

Sometimes the above solutions round off the value,
here is the simplest Solution I'm using and its working fine for me,
let a = "2.2652";// string
let b = 2.2652; // decimal-number
let c = 22200223.26522200225; // decimal-number
let d = 2 // non-decimal
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 20,
minimumSignificantDigits: 1,
maximumSignificantDigits: 20
});
const newFunc = (val)=>{
val = formatter.format((val))
if(val.includes(".")){
let number = val.toString().split(".")[0]+"."+val.split(".")[1].slice(0, 2)
return number
}else{
return val
}
}
console.log(newFunc(a))
console.log(newFunc(b))
console.log(newFunc(c))
console.log(newFunc(d))

I could get it using formatToParts. You can also do some javascript Array functions tricks to separate out the decimal part if needed.
export const formatNumberWithoutDecimals = (price) =>
new Intl.NumberFormat(i18n.language, {
minimumFractionDigits: 2,
}).formatToParts(price).reduce((result, part) => (part.type !== "decimal" && part.type !== "fraction") ? result + part.value : result, "");

Related

toLocaleString() changes behaviour after using toFixed method?

I was working on 'Formatting a number as price' problem on codewars and thought to have finally solved it with this code:
var numberToPrice = function(number) {
if (typeof number !== 'number') {
return 'NaN';
}
let newPrice = number.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
return newPrice;
}
One test failed because of rounding problem (Expected: '13,422.12', instead got: '13,422.13'), others passed.
When I changed code to this:
var numberToPrice = function(number) {
if (typeof number !== 'number') {
return 'NaN';
}
let price = (Math.floor(100 * number) / 100).toFixed(2);
let newPrice = price.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
return newPrice;
}
in order to try to avoid rounding problem, to LocaleString didn't work the same. I got many errors like this: Expected: '245,123,215.00', instead got: '245123215.00'
What is going on here?
Once you use toFixed, price is a string, not a number, so toLocaleString will not change it. Given that you have used Math.floor(100 * number) / 100, you have already converted price to only have two significant digits, so toFixed(2) is not necessary, and leaving price as a number allows toLocaleString to format it.
var numberToPrice = function(number) {
if (typeof number !== 'number') {
return 'NaN';
}
let price = Math.floor(100 * number) / 100;
let newPrice = price.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
return newPrice;
}
console.log(numberToPrice(245123215))
console.log(numberToPrice(13422.126))

When the amount is not in USD currency don't have the $ symbol in the front

How can I add a condition in the following function --> When the currency is not USD don't add the $ sign in the front of the amount.
var convertToCurrency = number => {
if (!number) return '';
return new Intl.NumberFormat('en', {
style: 'currency',
currency: 'USD'
}).format(number);
};
var amount = {
amount: 10,
currency: "JPY"
};
convertToCurrency(amount["amount"]);
==> $10.00 JPY
If I understand what you want correctly you could add a flag to your function isUSD or something like this and then do the following.
const convertToCurrency = (number, isUSD) => {
if (!number) return '';
if (isUSD) {
return new Intl.NumberFormat('en', {
style: 'currency',
currency: 'USD'
}).format(number);
} else {
return (Math.round(number * 100) / 100).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",");;
}
};
console.log(convertToCurrency(10000, true));
console.log(convertToCurrency(10000, false));
You can pass an optional boolean and either return full string or skip the first character (which is $ sign) if it's USD.
var convertToCurrency = (number, isUsd) => {
if (!number) return '';
var sliceFrom = isUsd ? 1 : 0;
return new Intl.NumberFormat('en', {
style: 'currency',
currency: 'USD',
}).format(number).slice(sliceFrom);
};
convertToCurrency(10000, true);
If you plan to use other formats for your currency conversion, you could also pass in format string and check against that.
So instead of isUsd, use currency
var convertToCurrency = (number, currency) => {
if (!number || !currency) return '';
var sliceFrom = currency === 'USD' ? 1 : 0;
return new Intl.NumberFormat('en', {
style: 'currency',
currency,
}).format(number).slice(sliceFrom);
};
convertToCurrency(10000,'USD');
var convertToCurrency = (number, currency) => {
if (!number || !currency) return '';
var isUSD = (currency.toUpperCase() === 'USD');
if (isUSD) {
return new Intl.NumberFormat('en', {
style: 'currency',
currency: 'USD',
}).format(number);
} else {
return new Intl.NumberFormat('en', {
style: 'decimal',
}).format(number) + ' ' + currency;
}
};
convertToCurrency(10000, 'USD'); // "$10,000.00"
convertToCurrency(10000, 'JPY'); // "10,000.00 JPY"
Some remarks:
Your current code (modifying the currency parameter) wouldn't produce "$ 100 JPY", but "¥100". Not quite the same…
Maybe your are too much tweaking the output, and rather, should just use the standard outputs. (most notably, put all the currencies at left or right, but avoid doing a mix)
You might be interested by the parameter currencyDisplay: 'symbol' / 'code'. The former outputs a symbol like "$" or "¥" if possible, the latter outputs an ISO code like "USD" or "JPY".
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
var style = "currency";
var currency = 'USD'; //replace with your dynamic value
var convertToCurrency = (number, style, currency) => {
if (!number) return '';
return new Intl.NumberFormat('en', {
style: style,
currency: currency
}).format(number);
};
console.log(convertToCurrency(10000, style, currency));

Generic formula for build an array of numbers of base-n

Say I want to build an array of numbers base 8, or base 26, I'm not sure how to approach a general formula for doing this:
console.log(arrayOfNumbersOfBase(8, 0, 10));
console.log(arrayOfNumbersOfBase(26, 0, 10));
function arrayOfNumbersOfBase(base, start, size)
{
var array = [];
for (var i = start, n = size; i < n; i++)
{
array.push(i * (base));
}
return array;
}
You can take the next approach as a starting point, basically I had to define some utility methods:
mapToChar(n) maps a number n to a character representation, for example, 10 is mapped to 'A'.
convertToBaseN(n, base) converts the number n to his representation on the given base. This method uses a recursive approach and utilizes the previous one.
Finally, generateNumbersOfBase(base, start, size) generates an array of size elements starting with the number start for the given base.
CODE:
// Next utility method map a decimal number to a character representation.
const mapToChar = (n) =>
{
n = (n >= 0 && n <= 9) ? '0'.charCodeAt() + n : n - 10 + 'A'.charCodeAt();
return String.fromCharCode(n);
}
// Next utility method convert a decimal number to his base-n representation.
const convertToBaseN = (n, base, res = "") =>
{
if (n <= 0)
return (res && res.split("").reverse().join("")) || "0";
// Convert input number to given base by repeatedly
// dividing it by base and taking remainder.
res += mapToChar(n % base);
return convertToBaseN(Math.floor(n / base), base, res);
}
// Next method generates an array of numbers for a given base.
const generateNumbersOfBase = (base, start, size) =>
{
return Array(size).fill(0).map((x, idx) => convertToBaseN(start + idx, base));
}
// Finally, generate some arrays.
let base10Array = generateNumbersOfBase(10, 15, 5);
let base2Array = generateNumbersOfBase(2, 5, 9);
let base16Array = generateNumbersOfBase(16, 10, 12);
let base8Array = generateNumbersOfBase(8, 1, 12);
console.log(
JSON.stringify(base10Array),
JSON.stringify(base2Array),
JSON.stringify(base16Array),
JSON.stringify(base8Array),
);
Now, if you need to convert some base-n representation back to decimal number, you can use next approach:
const convertToDec = (str, base) =>
{
let codeA = 'A'.charCodeAt();
let code0 = '0'.charCodeAt();
return str.split("").reverse().reduce((acc, c, idx) =>
{
let code = c.charCodeAt();
c = code + ((c >= '0' && c <= '9') ? -code0 : -codeA + 10);
return acc += c * Math.pow(base, idx);
}, 0);
}
// Lets convert back some arrays generated on the previous exampel
let base2Array = ["101","110","111","1000","1001","1010","1011","1100","1101"];
let base16Array = ["A","B","C","D","E","F","10","11","12","13","14","15"];
let res2 = base2Array.map(x => convertToDec(x, 2));
let res16 = base16Array.map(x => convertToDec(x, 16));
console.log(
JSON.stringify(res2),
JSON.stringify(res16)
);

How to get a leading '+' for positive numbers in Intl.NumberFormat?

I'm using Intl.NumberFormat to convert a number type to a formatted string in typescript/javascript in Angular2. I want a native solution and this is ideal, but I need a leading plus sign to be included for positive numbers.
If this is not possible with Intl.NumberFormat how else might I do it natively?
#Input() amount : number;
drawLabel() {
var formatter = new Intl.NumberFormat("en-GB",
{
style: "decimal",
minimumFractionDigits:1
});
...
this.label = formatter.format(this.amount)
}
In 2019 you do this:
var formatter = new Intl.NumberFormat("en-GB", { style: "decimal", signDisplay: 'always' });
console.log(formatter.format(-100.123456));
// output -100.123
console.log(formatter.format(100.123456));
// output +100.123
console.log(formatter.format(0));
// output +0.
Try something like this:
class FormatterWithSign extends Intl.NumberFormat {
constructor(...args) {
super(...args);
}
format(x) {
var res = super.format(x);
return x < 0 ? res : "+" + res;
}
}
var formatter = new FormatterWithSign("en-GB", { style: "decimal", minimumFractionDigits:1 });
console.log(formatter.format(-100.123456));
console.log(formatter.format(100.123456));
console.log(formatter.format(0));
Just check if this.amount is bigger than 0. Add a leading + if that is the case.
if(this.amount > 0) this.label = "+"+formatter.format(this.amount);
else this.label = formatter.format(this.amount);
better
this.label = formatter.format(this.amount);
if(this.amount > 0) this.label = "+"+this.label;
or in short
this.label = this.amount > 0 ? "+"+formatter.format(this.amount): formatter.format(this.amount)
You can also add Your own formatWithSign method :
Intl.NumberFormat.prototype.formatWithSign = function(x)
{
let y = this.format(x);
return x < 0 ? y : '+' + y;
}
const MyFormat = new Intl.NumberFormat('en-GB', { style: "decimal", minimumFractionDigits: 2, maximumFractionDigits:2} )
console.log(MyFormat.formatWithSign(-78.123456)); // -78,12
console.log(MyFormat.formatWithSign(90.123456)); // +90.12
console.log(MyFormat.formatWithSign(0)); // +0.00

JavaScript: Always return .toFixed(2) from a function (currency support)

Is there a way in Node/JavaScript to signal that a variable should always be returned with .toFixed(2). I.E. a currency type.
See the following example function:
let PER_SHARE_COMMISSION = 0.005; // USD
let MINIMUM_COMMISSION_PER_ORDER = 1.00; // USD
let MAXIMUM_PERCENT_OF_ORDER = 0.005; // 0.5%
exports.commission = function(price, shares) {
let commish = shares * PER_SHARE_COMMISSION;
if(commish < MINIMUM_COMMISSION_PER_ORDER) {
return MINIMUM_COMMISSION_PER_ORDER.toFixed(2);
}
var maxCommish = shares * price * MAXIMUM_PERCENT_OF_ORDER;
if(commish > maxCommish) {
return maxCommish.toFixed(2);
}
return commish.toFixed(2);
};
See how I have to manually put .toFixed(2) anywhere before returning the value. This is error prone. Is there a pattern around this?
Here is basically the solution I am looking for:
currency(1) // "1.00"
currency(23.3433) // "23.34"
currency(87.3987) // "87.40"
currency(12342.453) // "12342.45"
function currency(input) {
return new Intl.NumberFormat('en-US', {
style: 'decimal',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
useGrouping: false
}).format(input);
}

Categories

Resources