Custom filter with white space as thousands separator is not working

谁说我不能喝 提交于 2019-12-13 15:34:05

问题


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 prints 451,585.
  • For 154455451.58 it prints 154 455 451,58.

Fails for these examples:

  • For 54455451.58 it prints 54455 451,58.

  • For 55451.58 it prints 55451,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.90
  • 12 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!