问题
I am trying to write a custom filter to filter numbers on the format xxx xxx,xxx
in my application.
This is the function I wrote so far:
var formatNumber = function(input, fractionSeparator, thousandsSeparator, fractionSize) {
fractionSeparator = fractionSeparator || ',';
thousandsSeparator = thousandsSeparator || ' ';
fractionSize = fractionSize || 3;
var output = '', parts = [];
input = input.toString();
parts = input.split(".");
output = parts[0].replace(/(\d{3})/g, ""+ thousandsSeparator +"$1").trim();
if(parts.length>1){
output += fractionSeparator;
output += parts[1].substring(0, fractionSize);
}
return output;
};
Actually this function works perfectly for some numbers but fails in other cases, for example:
Works with the following numbers:
- For
451.585478
it prints451,585
. - For
154455451.58
it prints154 455 451,58
.
Fails for these examples:
For
54455451.58
it prints54455 451,58
.For
55451.58
it prints55451,58
.
Demo:
var formatNumber = function(input, fractionSeparator, thousandsSeparator, fractionSize) {
fractionSeparator = fractionSeparator || ',';
thousandsSeparator = thousandsSeparator || ' ';
fractionSize = fractionSize || 3;
var output = '',
parts = [];
input = input.toString();
parts = input.split(".");
output = parts[0].replace(/(\d{3})/g, "" + thousandsSeparator + "$1").trim();
if (parts.length > 1) {
output += fractionSeparator;
output += parts[1].substring(0, fractionSize);
}
return output;
};
console.log(formatNumber(154455451.58));
console.log(formatNumber(55451.58));
console.log(formatNumber(54455451.58));
I don't know what's wrong with it. Any help will be appreciated.
Edit:
I finally got it working, the problem was with my Regex, this is the final regex I used to format thousands in my number:
output = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator).trim();
This the final code:
var formatNumber = function(input, fractionSeparator, thousandsSeparator, fractionSize) {
fractionSeparator = fractionSeparator || ',';
thousandsSeparator = thousandsSeparator || ' ';
fractionSize = fractionSize || 3;
var output = '',
parts = [];
input = input.toString();
parts = input.split(".");
output = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator).trim();
if (parts.length > 1) {
output += fractionSeparator;
output += parts[1].substring(0, fractionSize);
}
return output;
};
console.log(formatNumber(154455451.58));
console.log(formatNumber(55451.58));
console.log(formatNumber(54455451.58));
回答1:
The problem is here:
output = parts[0].replace(/(\d{3})/g, ""+ thousandsSeparator +"$1").trim();
You are inserting the space before every 3 digits (I'll mark matches between brackets):
"(123)(456)78" --> "( 123)( 456)78"
Then you are trimming it --> "123 45678"
Your idea is nice, but you must do it starting from right to left so I will invert parts[0]
and the replacement:
parts[0] = parts[0].split('').reverse().join('');
"12345678" --> "87654321"
parts[0] = parts[0].replace(/(\d{3})/g, "$1" + thousandsSeparator).trim();
"(876)(543)21" --> "(876 )(543 )21"
output = parts[0].split('').reverse().join('');
"876 543 21" --> "12 345 678"
Which is the output you want. Here is the updated snippet:
var formatNumber = function(input, fractionSeparator, thousandsSeparator, fractionSize) {
fractionSeparator = fractionSeparator || ',';
thousandsSeparator = thousandsSeparator || ' ';
fractionSize = fractionSize || 3;
var output = '',
parts = [];
input = input.toString();
parts = input.split(".");
parts[0] = parts[0].split('').reverse().join('');
parts[0] = parts[0].replace(/(\d{3})/g, "$1" + thousandsSeparator).trim();
output = parts[0].split('').reverse().join('');
if (parts.length > 1) {
output += fractionSeparator;
output += parts[1].substring(0, fractionSize);
}
return output;
};
console.log(formatNumber(154455451.58));
console.log(formatNumber(55451.58));
console.log(formatNumber(54455451.58));
回答2:
You can split your number on .
and then split it and convert it to an array and can use array#map
and array#filter
to get a new array with its element grouped in chunk of 3 and then you just have to reverse
and join
it backl
const formatNumber = (num, fractionSeparator, thousandsSeparator, fractionSize) => {
fractionSeparator = fractionSeparator || ',';
thousandsSeparator = thousandsSeparator || ' ';
fractionSize = fractionSize || 3;
let [first, second] = num.toString().split('.');
let reversed = first.split('').reverse();
reversed = reversed.map((e,i) => i%fractionSize===0 ? reversed.slice(i,i+fractionSize).join('') :null)
.filter(x => x)
.reverse()
.join(thousandsSeparator);
return reversed + fractionSeparator + second;
}
console.log(formatNumber(154455451.58));
console.log(formatNumber(55451.58));
console.log(formatNumber(54455451.58));
回答3:
Here's another approach that is based off of a "currency formatter" method that I once wrote:
var sRegexFormatPattern = '^([^\\' + thousandsSeparator +
'\.]+)(\\d{3}[\\' + thousandsSeparator +
'\.]|\\d{3}$)';
var regexFormatPattern = new RegExp(sRegexFormatPattern);
while (regexFormatPattern.test(input)) {
input = input.replace(regexFormatPattern, '$1' + thousandsSeparator + '$2');
}
Let's break down that regex starting from back to front . . .
The second capture group (
(\\d{3}[\\' + thousandsSeparator + '\.]|\\d{3}$)
) matches exactly 3 digits and a either a decimal point (\d{3}\.
), the character passed in as the "thousandsSeparator" (\d{3}\THOUSANDS_SEPARATOR
), or the end of the string (\d{3}$
).The first capture group (
^([^\\' + thousandsSeparator + '\.]+)
) matches all characters (which should all be digits . . . you'd need a separate check to make sure of that) that aren't either a decimal point or the character passed in as the "thousandsSeparator", up until it hits the second capture group.
NOTE: I added in an escape slash before the "thousandsSeparator", just in case somebody typed in something that has another meaning in regex, but, even still, some characters could cause issues in the execution of the regex . . . would need to look into it more to make it foolproof.
As long as it can find those two pieces inside the value, it will break them up with the "thousandsSeparator" and check again. Using "12345678.90" as an example, the iterations go like this:
12345
678.
--- changes the number to ---> 12345 678.9012
345 678.
--- changes the number to ---> 12 345 678.90
Everything before and after that section is more-or-less what you already had . . . I added a minor tweak to how you formatted the "fraction" part of the string as well, but the rest is pretty much the same (see full JS code below).
Using your test values, I got:
- 451.585478 ---> 451,585
- 154455451.58 ---> 154 455 451,58
- 54455451.58 ---> 54 455 451,58
- 55451.58 ---> 55 451,58
DEMO
$("document").ready(function() {
$(".formattingInput").on("change", function() {
$("#formattedNumber").text(formatNumber(
$("#unformattedNumberInput").val(),
$("#fractionSeparator").val(),
$("#thousandsSeparator").val(),
$("#fractionSize").val()
));
});
});
var formatNumber = function(input, fractionSeparator, thousandsSeparator, fractionSize) {
fractionSeparator = fractionSeparator || ',';
thousandsSeparator = thousandsSeparator || ' ';
fractionSize = fractionSize || 3;
var sRegexFormatPattern = '^([^\\' + thousandsSeparator +
'\.]+)(\\d{3}[\\' + thousandsSeparator +
'\.]|\\d{3}$)';
var regexFormatPattern = new RegExp(sRegexFormatPattern);
while (regexFormatPattern.test(input)) {
input = input.replace(regexFormatPattern, '$1' + thousandsSeparator + '$2');
}
var parts = input.split('.');
if (parts.length > 1) {
input = parts[0] +
fractionSeparator +
parts[1].substring(0, fractionSize);
}
return (input.length > 0) ? input : 'Please input a value in the "Number" field.';
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<label for="unformattedNumberInput">Number: <input type="text" id="unformattedNumberInput" class="formattingInput" /></label><br />
<label for="thousandsSeparator">Thousands Separator: <input type="text" id="thousandsSeparator" class="formattingInput" /></label><br />
<label for="fractionSeparator">Fraction Separator: <input type="text" id="fractionSeparator" class="formattingInput" /></label><br />
<label for="fractionSize">Fraction Size: <input type="text" id="fractionSize" class="formattingInput" /></label><br /><br />
</div>
<div>
<strong>Result:</strong>
<span id="formattedNumber">Please input a value in the "Number" field.</span>
</div>
来源:https://stackoverflow.com/questions/47039134/custom-filter-with-white-space-as-thousands-separator-is-not-working