Estoy tratando de escribir un filtro personalizado para filtrar números en el formato xxx xxx,xxx en mi aplicación.

Esta es la función que escribí hasta ahora:

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;
};

En realidad, esta función funciona perfectamente para algunos números, pero falla en otros casos, por ejemplo:

Funciona con los siguientes números:

  • Para 451.585478 imprime 451,585.
  • Para 154455451.58 imprime 154 455 451,58.

Falla en estos ejemplos:

  • Para 54455451.58 imprime 54455 451,58.

  • Para 55451.58 imprime 55451,58.

Demostración:

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));

No sé qué tiene de malo. Cualquier ayuda será apreciada.


Editar:

Finalmente lo puse a funcionar, el problema estaba con mi Regex, esta es la expresión regular final que solía formatear miles en mi número:

output = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator).trim();

Este es el código final:

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));
2
cнŝdk 31 oct. 2017 a las 18:33

3 respuestas

La mejor respuesta

El problema está aquí:

output = parts[0].replace(/(\d{3})/g, ""+ thousandsSeparator +"$1").trim();

Estás insertando el espacio antes de cada 3 dígitos (marcaré coincidencias entre paréntesis):

"(123)(456)78" --> "( 123)( 456)78"

Luego lo estás cortando - & gt; {{x0}}

Su idea es buena, pero debe hacerlo comenzando de derecha a izquierda para que invierta parts[0] y el reemplazo:

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"

Cuál es la salida que quieres. Aquí está el fragmento actualizado:

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));
1
FcoRodr 31 oct. 2017 a las 17:16

Puede dividir su número en . y luego dividirlo y convertirlo en una matriz y puede usar array#map y array#filter para obtener una nueva matriz con su elemento agrupado en un bloque de 3 y luego solo tienes que reverse y join retroceder

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));
1
Hassan Imam 31 oct. 2017 a las 17:36

Aquí hay otro enfoque que se basa en un método de "formateador de moneda" que una vez escribí:

var sRegexFormatPattern = '^([^\\' + thousandsSeparator +
                          '\.]+)(\\d{3}[\\' + thousandsSeparator +
                          '\.]|\\d{3}$)';
var regexFormatPattern = new RegExp(sRegexFormatPattern);

while (regexFormatPattern.test(input)) {
    input = input.replace(regexFormatPattern, '$1' + thousandsSeparator + '$2');
}

Analicemos esa expresión regular comenzando de atrás hacia adelante. . .

  • El segundo grupo de captura ((\\d{3}[\\' + thousandsSeparator + '\.]|\\d{3}$)) coincide exactamente con 3 dígitos y un punto decimal (\d{3}\.), el carácter pasado como "milesSeparator" (\d{3}\THOUSANDS_SEPARATOR), o el final de la cadena (\d{3}$).

  • El primer grupo de captura (^([^\\' + thousandsSeparator + '\.]+)) coincide con todos los caracteres (que deberían ser dígitos ... necesitaría una verificación por separado para asegurarse de eso) que tampoco son decimales punto o el personaje pasado como "milesSeparator", hasta que llegue al segundo grupo de captura .

NOTA: agregué una barra de escape antes del "milesSeparator", en caso de que alguien escribiera algo que tuviera otro significado en expresiones regulares, pero, aún así, algunos caracteres podrían causar problemas en la ejecución del regex. . . necesitaría investigarlo más para que sea infalible.

Mientras pueda encontrar esas dos piezas dentro del valor, las dividirá con el "milesSeparator" y volverá a verificar. Usando "12345678.90" como ejemplo, las iteraciones son así:

  • 12345 {{x1}} --- Cambia el número a --- & gt; 12345 678.90
  • 12 {{x1}} --- Cambia el número a --- & gt; 12 345 678.90

Todo antes y después de esa sección es más o menos lo que ya tenía. . . Agregué un pequeño ajuste a cómo formateó la parte de "fracción" de la cadena también, pero el resto es más o menos lo mismo ( vea el código JS completo a continuación ).

Usando sus valores de prueba, obtuve:

- 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>
1
talemyn 1 nov. 2017 a las 15:21