Convierta un número largo en una cadena abreviada en JavaScript, con un requisito especial de brevedad


81

En JavaScript, ¿cómo se escribiría una función que convierta un número dado [editar: entero positivo ] (por debajo de 100 mil millones) en una abreviatura de 3 letras, donde 0-9 y az / AZ cuentan como una letra, pero el punto (ya que es tan pequeño en muchas fuentes proporcionales) no lo haría, y sería ignorado en términos del límite de letras?

Esta pregunta está relacionada con este útil hilo , pero no es lo mismo; por ejemplo, dónde se convertiría esa función, por ejemplo, "123456 -> 1.23k" ("123.5k" son 5 letras). Estoy buscando algo que haga "123456 -> 0.1m" ("0 [.] 1m" sea de 3 letras ). Por ejemplo, este sería el resultado de la función esperada (original izquierdo, valor de retorno ideal derecho):

0                      "0"
12                    "12"
123                  "123"
1234                "1.2k"
12345                "12k"
123456              "0.1m"
1234567             "1.2m"
12345678             "12m"
123456789           "0.1b"
1234567899          "1.2b"
12345678999          "12b"

¡Gracias!

Actualización: ¡Gracias! Hay una respuesta y funciona según los requisitos cuando se realizan las siguientes enmiendas:

function abbreviateNumber(value) {
    var newValue = value;
    if (value >= 1000) {
        var suffixes = ["", "k", "m", "b","t"];
        var suffixNum = Math.floor( (""+value).length/3 );
        var shortValue = '';
        for (var precision = 2; precision >= 1; precision--) {
            shortValue = parseFloat( (suffixNum != 0 ? (value / Math.pow(1000,suffixNum) ) : value).toPrecision(precision));
            var dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g,'');
            if (dotLessShortValue.length <= 2) { break; }
        }
        if (shortValue % 1 != 0)  shortValue = shortValue.toFixed(1);
        newValue = shortValue+suffixes[suffixNum];
    }
    return newValue;
}

Aunque no es JS, hice lo mismo en C ++, que puede ver en gist.github.com/1870641 ; el proceso será el mismo. Sin embargo, es mejor que busque soluciones listas para usar.
CSL

Baz, mi inicio actual es solo una función ligeramente reformateada del hilo vinculado, con los problemas mencionados. Antes de intentar darle vida a esa función, esperaba que alguien más ya tuviera una función inteligente escrita en algún lugar, o supiera cómo hacerlo fácilmente. Csl, eso es genial, ¡muchas gracias! ¿Esa conversión también haría el estilo "0,1 m" mencionado?
Philipp Lenssen


9
lo que es shortNum? nunca lo usa, y ni siquiera se declara ..
vsync

1
También hay una mezcla entre .y ,, por lo que 1001.5se convierte en millones en lugar de miles.
vsync

Respuestas:


62

Creo que la solución de ninjagecko no se ajusta del todo al estándar que querías. La siguiente función hace:

function intToString (value) {
    var suffixes = ["", "k", "m", "b","t"];
    var suffixNum = Math.floor((""+value).length/3);
    var shortValue = parseFloat((suffixNum != 0 ? (value / Math.pow(1000,suffixNum)) : value).toPrecision(2));
    if (shortValue % 1 != 0) {
        shortValue = shortValue.toFixed(1);
    }
    return shortValue+suffixes[suffixNum];
}

Para valores superiores a 99 billones no se agregará ninguna letra, que se puede arreglar fácilmente agregando a la matriz de 'sufijos'.

La edición de Philipp sigue: ¡ Con los siguientes cambios, se adapta perfectamente a todos los requisitos!

function abbreviateNumber(value) {
    var newValue = value;
    if (value >= 1000) {
        var suffixes = ["", "k", "m", "b","t"];
        var suffixNum = Math.floor( (""+value).length/3 );
        var shortValue = '';
        for (var precision = 2; precision >= 1; precision--) {
            shortValue = parseFloat( (suffixNum != 0 ? (value / Math.pow(1000,suffixNum) ) : value).toPrecision(precision));
            var dotLessShortValue = (shortValue + '').replace(/[^a-zA-Z 0-9]+/g,'');
            if (dotLessShortValue.length <= 2) { break; }
        }
        if (shortValue % 1 != 0)  shortValue = shortValue.toFixed(1);
        newValue = shortValue+suffixes[suffixNum];
    }
    return newValue;
}

¡Gracias! Hice algunas enmiendas para que se ajustara al 100% a los requisitos y edité su respuesta (espero que sea la forma correcta de hacerlo en StackOverflow, cámbiela si es necesario). ¡Magnífico!
Philipp Lenssen

Solo por curiosidad: ¿qué caso / requisito me perdí?
chucktator

1
Esos fueron menores y se modificaron rápidamente en función de su gran respuesta. Los siguientes números de prueba se perdieron levemente: 123 se convirtió en 0.12k (resultado ideal: 123, ya que siguen siendo 3 letras y por lo tanto está bien); 123456 se convirtió en 0,12 m (ideal: 0,1 m, ya que 0,12 m son 4 letras, menos puntos); lo mismo para 0.12b que con las modificaciones ahora se convierte en 0.1b. ¡De nuevo, gracias por tu ayuda!
Philipp Lenssen

1
Acabo de hacer un pequeño ajuste (aún no ha sido revisado por pares). Agregué valor = Math.round (valor); hasta el comienzo de la función. Al hacer eso, no se rompe cuando "valor" tiene lugares decimales, por ejemplo. 10025.26584 se convertirá en 10k en lugar de NaN
Daniel Tonon

2
shortNumno está definido en la edición de @ PhilippLenssen al finalif (shortValue % 1 != 0) shortNum = shortValue.toFixed(1);
Honza

47

Esto también maneja valores muy grandes y es un poco más conciso y eficiente.

abbreviate_number = function(num, fixed) {
  if (num === null) { return null; } // terminate early
  if (num === 0) { return '0'; } // terminate early
  fixed = (!fixed || fixed < 0) ? 0 : fixed; // number of decimal places to show
  var b = (num).toPrecision(2).split("e"), // get power
      k = b.length === 1 ? 0 : Math.floor(Math.min(b[1].slice(1), 14) / 3), // floor at decimals, ceiling at trillions
      c = k < 1 ? num.toFixed(0 + fixed) : (num / Math.pow(10, k * 3) ).toFixed(1 + fixed), // divide by power
      d = c < 0 ? c : Math.abs(c), // enforce -0 is 0
      e = d + ['', 'K', 'M', 'B', 'T'][k]; // append power
  return e;
}

Resultados:

for(var a='', i=0; i < 14; i++){ 
    a += i; 
    console.log(a, abbreviate_number(parseInt(a),0)); 
    console.log(-a, abbreviate_number(parseInt(-a),0)); 
}

0 0
-0 0
01 1
-1 -1
012 12
-12 -12
0123 123
-123 -123
01234 1.2K
-1234 -1.2K
012345 12.3K
-12345 -12.3K
0123456 123.5K
-123456 -123.5K
01234567 1.2M
-1234567 -1.2M
012345678 12.3M
-12345678 -12.3M
0123456789 123.5M
-123456789 -123.5M
012345678910 12.3B
-12345678910 -12.3B
01234567891011 1.2T
-1234567891011 -1.2T
0123456789101112 123.5T
-123456789101112 -123.5T
012345678910111213 12345.7T
-12345678910111212 -12345.7T

Me gusta este enfoque porque maneja los no enteros mucho mejor que las otras respuestas.
Grant H.

1
Mucho mejor solución
Danillo Corvalan

Gracias. Simplemente puse esto en mi proyecto y funciona como un encanto. Solución impresionante, muy sólida.
spieglio

1
Para valores muy pequeños (p 5e-18. Ej. ), Devuelve "0T", por lo que agregué esto:if (num < 1e-3) { return '~0'; }
Paul Razvan Berg

Recientemente me encontré con el mismo problema en Python y rápidamente se me ocurrió un enfoque mucho más simple (supongo que mi forma de pensar ha cambiado en los últimos 5 años). En caso de que sea útil, aquí está; Me encantaría verlo en JS. stackoverflow.com/a/65084005/1438550
D.Deriso

19

Muchas respuestas en este hilo se vuelven bastante complicadas, usando Mathobjetos, objetos de mapa, bucles for, etc. Pero esos enfoques en realidad no mejoran mucho el diseño: introducen más líneas de código, más complejidad y más memoria. . Después de evaluar varios enfoques, creo que el enfoque manual es el más fácil de entender y proporciona el mayor rendimiento.

const formatCash = n => {
  if (n < 1e3) return n;
  if (n >= 1e3 && n < 1e6) return +(n / 1e3).toFixed(1) + "K";
  if (n >= 1e6 && n < 1e9) return +(n / 1e6).toFixed(1) + "M";
  if (n >= 1e9 && n < 1e12) return +(n / 1e9).toFixed(1) + "B";
  if (n >= 1e12) return +(n / 1e12).toFixed(1) + "T";
};

console.log(formatCash(1235000));


Esta debería ser definitivamente la respuesta aceptada. ¡Con mucho! Gracias @tfmontague
Pedro Ferreira

15

Esto es lo que creo que es una solución bastante elegante. No intenta lidiar con números negativos :

const COUNT_ABBRS = [ '', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ];

function formatCount(count, withAbbr = false, decimals = 2) {
    const i     = 0 === count ? count : Math.floor(Math.log(count) / Math.log(1000));
    let result  = parseFloat((count / Math.pow(1000, i)).toFixed(decimals));
    if(withAbbr) {
        result += `${COUNT_ABBRS[i]}`; 
    }
    return result;
}

Ejemplos :

   formatCount(1000, true);
=> '1k'
   formatCount(100, true);
=> '100'
   formatCount(10000, true);
=> '10k'
   formatCount(10241, true);
=> '10.24k'
   formatCount(10241, true, 0);
=> '10k'
   formatCount(10241, true, 1)
=> '10.2k'
   formatCount(1024111, true, 1)
=> '1M'
   formatCount(1024111, true, 2)
=> '1.02M'

6
Esto no resuelve la pregunta tal como se planteó, pero es justo lo que estoy buscando. Gracias.
izquierda

Me gustan los símbolos SI más que k, m, b. 1b en América / Reino Unido es 1e9, pero 1e12 en muchos otros países.
Christiaan Westerbeek

14

Creo que no puedes probar este numeraljs /

Si quieres convertir 1000 a 1k

console.log(numeral(1000).format('0a'));

y si quieres convertir 123400 a 123.4k prueba esto

console.log(numeral(123400).format('0.0a'));

Solución fantástica si está dispuesto a instalar una biblioteca de terceros
Colby Cox

¿Hay alguna forma de usar 0.0apero 1kno mostrar 1.0kcuando el número es 1000? (Recortando los ceros)
Paul Razvan Berg

8

El 'sin código' forma moderna, fácil, incorporado, altamente personalizable, y: Intl.FormatNumber 's formato de la función ( gráfico de compatibilidad )

var numbers = [98721, 9812730,37462,29,093484620123, 9732,0283737718234712]
for(let num of numbers){
  console.log(new Intl.NumberFormat( 'en-US', { maximumFractionDigits: 1,notation: "compact" , compactDisplay: "short" }).format(num));
}
98.7K
9.8M
37.5K
29
93.5B
9.7K
283.7T

Notas:


6

Según mi respuesta en https://stackoverflow.com/a/10600491/711085 , su respuesta es en realidad un poco más corta de implementar, utilizando .substring(0,3):

function format(n) {
    with (Math) {
        var base = floor(log(abs(n))/log(1000));
        var suffix = 'kmb'[base-1];
        return suffix ? String(n/pow(1000,base)).substring(0,3)+suffix : ''+n;
    }
}

(Como de costumbre, no use Math a menos que sepa exactamente lo que está haciendo; asignar var pow=...y similares causaría errores locos. Vea el enlace para una forma más segura de hacer esto).

> tests = [-1001, -1, 0, 1, 2.5, 999, 1234, 
           1234.5, 1000001, Math.pow(10,9), Math.pow(10,12)]
> tests.forEach(function(x){ console.log(x,format(x)) })

-1001 "-1.k"
-1 "-1"
0 "0"
1 "1"
2.5 "2.5"
999 "999"
1234 "1.2k"
1234.5 "1.2k"
1000001 "1.0m"
1000000000 "1b"
1000000000000 "1000000000000"

Deberá detectar el caso en el que el resultado es> = 1 billón, si su requisito de 3 caracteres es estricto, de lo contrario, corre el riesgo de crear datos corruptos, lo que sería muy malo.


Buena solucion. También apoyos locos por usar la withdeclaración obsoleta :)
stwilz

3

Código

const SI_PREFIXES = [
  { value: 1, symbol: '' },
  { value: 1e3, symbol: 'k' },
  { value: 1e6, symbol: 'M' },
  { value: 1e9, symbol: 'G' },
  { value: 1e12, symbol: 'T' },
  { value: 1e15, symbol: 'P' },
  { value: 1e18, symbol: 'E' },
]

const abbreviateNumber = (number) => {
  if (number === 0) return number

  const tier = SI_PREFIXES.filter((n) => number >= n.value).pop()
  const numberFixed = (number / tier.value).toFixed(1)

  return `${numberFixed}${tier.symbol}`
}

abbreviateNumber(2000) // "2.0k"
abbreviateNumber(2500) // "2.5k"
abbreviateNumber(255555555) // "255.6M"

Prueba:

import abbreviateNumber from './abbreviate-number'

test('abbreviateNumber', () => {
  expect(abbreviateNumber(0)).toBe('0')
  expect(abbreviateNumber(100)).toBe('100')
  expect(abbreviateNumber(999)).toBe('999')

  expect(abbreviateNumber(1000)).toBe('1.0k')
  expect(abbreviateNumber(100000)).toBe('100.0k')
  expect(abbreviateNumber(1000000)).toBe('1.0M')
  expect(abbreviateNumber(1e6)).toBe('1.0M')
  expect(abbreviateNumber(1e10)).toBe('10.0G')
  expect(abbreviateNumber(1e13)).toBe('10.0T')
  expect(abbreviateNumber(1e16)).toBe('10.0P')
  expect(abbreviateNumber(1e19)).toBe('10.0E')

  expect(abbreviateNumber(1500)).toBe('1.5k')
  expect(abbreviateNumber(1555)).toBe('1.6k')

  expect(abbreviateNumber(undefined)).toBe('0')
  expect(abbreviateNumber(null)).toBe(null)
  expect(abbreviateNumber('100')).toBe('100')
  expect(abbreviateNumber('1000')).toBe('1.0k')
})

Después de probar todas las soluciones anteriores en este hilo, esta es la única que funciona correctamente. ¡Gracias Danilo!
ty.

3

Aquí hay otra versión. Quería que 123456 fuera 123.4K en lugar de 0.1M

function convert(value) {

    var length = (value + '').length,
        index = Math.ceil((length - 3) / 3),
        suffix = ['K', 'M', 'G', 'T'];

    if (length < 4) return value;
    
    return (value / Math.pow(1000, index))
           .toFixed(1)
           .replace(/\.0$/, '') + suffix[index - 1];

}

var tests = [1234, 7890, 123456, 567890, 800001, 2000000, 20000000, 201234567, 801234567, 1201234567];
for (var i in tests)
    document.writeln('<p>convert(' + tests[i] + ') = ' + convert(tests[i]) + '</p>');


1

Después de jugar un poco, este enfoque parece cumplir con los criterios requeridos. Se inspira en la respuesta de @ chuckator.

function abbreviateNumber(value) {

    if (value <= 1000) {
        return value.toString();
    }

    const numDigits = (""+value).length;
    const suffixIndex = Math.floor(numDigits / 3);

    const normalisedValue = value / Math.pow(1000, suffixIndex);

    let precision = 2;
    if (normalisedValue < 1) {
        precision = 1;
    }

    const suffixes = ["", "k", "m", "b","t"];
    return normalisedValue.toPrecision(precision) + suffixes[suffixIndex];
}

Creo que esta respuesta es genial. Tuve un caso ligeramente diferente en el que no quería mostrar valores con ceros a la izquierda. Agregué if (valor ajustado.charAt (0) === '0') {return valor ajustado * 1000 + sufijos [índiceIndex - 1]; } else {return valor ajustado + sufijos [sufijoIndex]; }
user3162553

1

Estoy usando esta función para obtener estos valores.

function Converter(number, fraction) {
    let ranges = [
      { divider: 1, suffix: '' },
      { divider: 1e3, suffix: 'K' },
      { divider: 1e6, suffix: 'M' },
      { divider: 1e9, suffix: 'G' },
      { divider: 1e12, suffix: 'T' },
      { divider: 1e15, suffix: 'P' },
      { divider: 1e18, suffix: 'E' },
    ]
    //find index based on number of zeros
    let index = (Math.abs(number).toString().length / 3).toFixed(0)
    return (number / ranges[index].divider).toFixed(fraction) + ranges[index].suffix
}

Cada 3 dígitos tiene un sufijo diferente, eso es lo que estoy tratando de encontrar en primer lugar.

Entonces, elimine el símbolo negativo si existe , luego encuentre cuántos 3 dígitos hay en este número.

después de eso, busque el sufijo apropiado basado en el cálculo anterior agregado al número dividido.

Converter(1500, 1)

Regresará:

1.5K

algo está mal. El convertidor (824492, 1) devolverá 0.8M. Debería devolver 824k
Sebastian SALAMANCA

0

Intl es el 'paquete' estándar de Javascript para los comportamientos internacionalizados implementados. Intl.NumberFormatter es específicamente el formateador de números localizado. Entonces, este código realmente respeta los miles y separadores decimales configurados localmente.

intlFormat(num) {
    return new Intl.NumberFormat().format(Math.round(num*10)/10);
}

abbreviateNumber(value) {
    let num = Math.floor(value);
    if(num >= 1000000000)
        return this.intlFormat(num/1000000000)+'B';
    if(num >= 1000000)
        return this.intlFormat(num/1000000)+'M';
    if(num >= 1000)
        return this.intlFormat(num/1000)+'k';
    return this.intlFormat(num);
}

abbreviateNumber (999999999999) // Da 999B

Pregunta relacionada: abreviar un número localizado en JavaScript para miles (1k) y millones (1m)


Pero entonces los sufijos no están localizados.
glen-84

0
            function converse_number (labelValue) {

                    // Nine Zeroes for Billions
                    return Math.abs(Number(labelValue)) >= 1.0e+9

                    ? Math.abs(Number(labelValue)) / 1.0e+9 + "B"
                    // Six Zeroes for Millions 
                    : Math.abs(Number(labelValue)) >= 1.0e+6

                    ? Math.abs(Number(labelValue)) / 1.0e+6 + "M"
                    // Three Zeroes for Thousands
                    : Math.abs(Number(labelValue)) >= 1.0e+3

                    ? Math.abs(Number(labelValue)) / 1.0e+3 + "K"

                    : Math.abs(Number(labelValue));

                }

alerta (número_conversación (100000000000));


0

@nimesaram

Su solución no será deseable para el siguiente caso:

Input 50000
Output 50.0k

La siguiente solución funcionará bien.

const convertNumberToShortString = (
  number: number,
  fraction: number
) => {
  let newValue: string = number.toString();
  if (number >= 1000) {
    const ranges = [
      { divider: 1, suffix: '' },
      { divider: 1e3, suffix: 'k' },
      { divider: 1e6, suffix: 'm' },
      { divider: 1e9, suffix: 'b' },
      { divider: 1e12, suffix: 't' },
      { divider: 1e15, suffix: 'p' },
      { divider: 1e18, suffix: 'e' }
    ];
    //find index based on number of zeros
    const index = Math.floor(Math.abs(number).toString().length / 3);
    let numString = (number / ranges[index].divider).toFixed(fraction);
    numString =
      parseInt(numString.substring(numString.indexOf('.') + 1)) === 0
        ? Math.floor(number / ranges[index].divider).toString()
        : numString;
    newValue = numString + ranges[index].suffix;
  }
  return newValue;
};

// Input 50000
// Output 50k
// Input 4500
// Output 4.5k

Nota: excepto k para Kilo, todos los prefijos están en mayúsculas. Comparar en.wikipedia.org/wiki/Unit_prefix
Wiimm

¡Oh gracias! En realidad, mi caso de uso necesita todo en letras pequeñas.
Rishabh

0

Usar como prototipo numérico

Para fácil y directo. Simplemente haz un prototipo. Aquí hay un ejemplo.

Number.prototype.abbr = function (decimal = 2): string {
  const notations = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'],
    i = Math.floor(Math.log(this) / Math.log(1000));
  return `${parseFloat((this / Math.pow(1000, i)).toFixed(decimal))}${notations[i]}`;
};

0

Formato de moneda india a (K, L, C) mil, lakh, crore

const formatCash = n => {
  if (n < 1e3) return n;
  if (n >= 1e3 && n < 1e5) return +(n / 1e3).toFixed(1) + "K";
  if (n >= 1e5 && n <= 1e6) return +(n / 1e5).toFixed(1) + "L";
  if (n >= 1e6 && n <= 1e9) return +(n / 1e7).toFixed(1) + "C";
};
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.