Truncar (no redondear) números decimales en javascript


93

Estoy tratando de truncar números decimales a lugares decimales. Algo como esto:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)hace lo correcto pero redondea el valor. No necesito redondear el valor. Espero que esto sea posible en javascript.


6
jQuery es solo un marco y su problema no está relacionado con jQuery. Se trata más de hacer algunos cálculos básicos en JavaScript. Espero que también esté satisfecho con una solución que no es de jQuery.
Felix Kling

Descubrí que era demasiado trabajo lograr que mis cálculos devolvieran solo 2 decimales usando Javascript. En su lugar, pude hacerlo fácilmente en la vista de mi base de datos. Me doy cuenta de que este método no se adapta a todas las situaciones, pero quiero publicarlo aquí porque podría ahorrarle a alguien mucho tiempo.
MsTapp

Respuestas:


51

upd :

Entonces, después de todo, resultó que los errores de redondeo siempre lo perseguirán, sin importar cuánto intente compensarlos. Por tanto, el problema debe resolverse representando los números exactamente en notación decimal.

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

[   5.467.toFixedDown(2),
    985.943.toFixedDown(2),
    17.56.toFixedDown(2),
    (0).toFixedDown(1),
    1.11.toFixedDown(1) + 22];

// [5.46, 985.94, 17.56, 0, 23.1]

Solución antigua propensa a errores basada en la compilación de otras:

Number.prototype.toFixedDown = function(digits) {
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

4
Sí, los prototipos no funcionan de manera confiable en todos los navegadores. En lugar de definir esta función (de propósito limitado) a través del sistema de tipos, de una manera que no funciona de manera confiable, ¿por qué no ponerla en una biblioteca?
Thomas W

3
Esto no funciona como excepción. Pruebe el número 17,56 y dígitos = 2. Debería ser 17,56, pero esta función devuelve 17,55.
shendz

2
Dos inconsistencias con esta función: esta función devuelve una cadena, por lo que 1.11.toFixedDown(1) + 22termina como en 1.122lugar de 23.1. También 0.toFixedDown(1)debería producir 0pero en su lugar produce -0.1.
Nick Knowlson

5
Tenga en cuenta que esta función elimina el signo negativo. Ej (-10.2131).toFixedDown(2) // ==> 10.21. : .
rgajrawala

4
Además (1e-7).toFixedDown(0) // ==> 1e-7,. Lo hace por 1e-(>=7)(ex: 1e-8, 1e-9, ...).
rgajrawala

60

La respuesta de Dogbert es buena, pero si su código tiene que lidiar con números negativos, Math.floorpor sí solo puede dar resultados inesperados.

Por ejemplo Math.floor(4.3) = 4, peroMath.floor(-4.3) = -5

Utilice una función auxiliar como esta para obtener resultados consistentes:

truncateDecimals = function (number) {
    return Math[number < 0 ? 'ceil' : 'floor'](number);
};

// Applied to Dogbert's answer:
var a = 5.467;
var truncated = truncateDecimals(a * 100) / 100; // = 5.46

Aquí hay una versión más conveniente de esta función:

truncateDecimals = function (number, digits) {
    var multiplier = Math.pow(10, digits),
        adjustedNum = number * multiplier,
        truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);

    return truncatedNum / multiplier;
};

// Usage:
var a = 5.467;
var truncated = truncateDecimals(a, 2); // = 5.46

// Negative digits:
var b = 4235.24;
var truncated = truncateDecimals(b, -2); // = 4200

Si ese no es el comportamiento deseado, inserte una llamada a Math.absen la primera línea:

var multiplier = Math.pow(10, Math.abs(digits)),

EDITAR: shendz señala correctamente que el uso de esta solución con a = 17.56producirá incorrectamente 17.55. Para obtener más información sobre por qué sucede esto, lea Lo que todo informático debe saber sobre la aritmética de coma flotante . Desafortunadamente, escribir una solución que elimine todas las fuentes de error de punto flotante es bastante complicado con javascript. En otro idioma, usarías enteros o tal vez un tipo decimal, pero con javascript ...

Esta solución debe ser 100% precisa, pero también será más lenta:

function truncateDecimals (num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    return parseFloat(finalResult);
}

Para aquellos que necesitan velocidad pero también quieren evitar errores de punto flotante, pruebe algo como BigDecimal.js . Puede encontrar otras bibliotecas Javascript BigDecimal en esta pregunta SO: "¿Existe una buena biblioteca Javascript BigDecimal?" y aquí hay una buena publicación de blog sobre bibliotecas matemáticas para Javascript


¿Por qué inesperado? Cambiar la dirección del redondeo cuando baja de 0 provoca todo tipo de artefactos aritméticos y malas matemáticas. Por ejemplo, el doble de números se redondeará a 0, como cualquier otro entero. Para gráficos, contabilidad y muchos otros usos, obtendrá resultados horribles. Para decirte la verdad, sería más difícil decir para qué sirve tu sugerencia que decir lo que no .
Thomas W

Es bueno exactamente para lo que dice: cuando desea truncar decimales en lugar de redondear.
Nick Knowlson

1
No va a funcionar con 17.56 porque el navegador da 17.56 * 100 = 1755.9999999999998 no 1756
shendz

Buen punto shendz. Actualicé mi respuesta con una solución que elimina todos los errores de punto flotante para aquellos que lo necesitan.
Nick Knowlson

1
Esto no funcionará para números menores que 1 si no desea decimales; truncateDecimals (.12345, 0) da como resultado NaN a menos que agregue una marca: if(isNAN(result) result = 0; Depende del comportamiento que desee.
Jeremy Witmer

35
var a = 5.467;
var truncated = Math.floor(a * 100) / 100; // = 5.46

5
Esto funciona bien, pero dará resultados que probablemente no sean deseables si él (o alguien que vea esta respuesta más adelante) tiene que lidiar con números negativos. Ver stackoverflow.com/a/9232092/224354
Nick Knowlson

3
¿Por qué indeseable? Cambiar la dirección del redondeo cuando baja de 0 provoca todo tipo de artefactos aritméticos.
Thomas W

8
Existe una diferencia entre redondear y truncar. Truncar es claramente el comportamiento que busca esta pregunta. Si llamo truncate(-3.14)y recibo de -4vuelta, definitivamente lo llamaría indeseable.
Nick Knowlson

Estoy de acuerdo con Thomas. La diferencia de perspectiva puede surgir con si normalmente se trunca para visualización o para cálculo. Desde una perspectiva computacional, esto evita "artefactos aritméticos"

3
var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 Por lo tanto, esta no es una solución correcta
Sanyam Jain

22

Puede corregir el redondeo restando 0.5 para toFixed, por ejemplo

(f - 0.005).toFixed(2)

1
Aviso: ya que esto no funciona para números muy pequeños, números con más de 3 posiciones decimales o números negativos. Prueba .0045, 5.4678 y -5.467
Nick Knowlson

Esto funcionará siempre que iguale el valor que está restando con la longitud que desea tener. todo lo que pase a toFixed () debe ser el número de ceros después del decimal.
dmarra

18

Considere aprovechando la doble tilde:~~ .

Anote el número. Multiplique por dígitos significativos después del decimal para que pueda truncar a cero con ~~. Divide ese multiplicador de vuelta. Lucro.

function truncator(numToTruncate, intDecimalPlaces) {    
    var numPower = Math.pow(10, intDecimalPlaces); // "numPowerConverter" might be better
    return ~~(numToTruncate * numPower)/numPower;
}

Estoy tratando de resistirme a envolver la ~~llamada en parens; El orden de las operaciones debería hacer que funcione correctamente, creo.

alert(truncator(5.1231231, 1)); // is 5.1

alert(truncator(-5.73, 1)); // is -5.7

alert(truncator(-5.73, 0)); // is -5

Enlace JSFiddle .

EDITAR: Mirando hacia atrás, sin querer también he manejado casos para redondear a la izquierda del decimal.

alert(truncator(4343.123, -2)); // gives 4300.

La lógica es un poco loca para ese uso y puede beneficiarse de una refactorización rápida. Pero sigue funcionando. Más suerte que buena.


Esta es la mejor respuesta. Si extiende el Mathprototipo con esto y verifica los NaN-s antes de ejecutarlo, sería perfecto.
Bartłomiej Zalewski

truncator((10 * 2.9) / 100, 2)return 0.28 en lugar de 0.29 ... jsfiddle.net/25tgrzq1
Alex

14

Buena solución de una línea:

function truncate (num, places) {
  return Math.trunc(num * Math.pow(10, places)) / Math.pow(10, places);
}

Entonces llámalo con:

truncate(3.5636232, 2); // returns 3.56
truncate(5.4332312, 3); // returns 5.433
truncate(25.463214, 4); // returns 25.4632

2
Me gusta esta solución, pero tenga en cuenta que no es totalmente compatible con todos los navegadores. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Gene Parcellano

11

Pensé en lanzar una respuesta usando |ya que es simple y funciona bien.

truncate = function(number, places) {
  var shift = Math.pow(10, places);

  return ((number * shift) | 0) / shift;
};

1
Buena llamada. El uso de un operador bit a bit convierte el valor en un int, e oring con 0 significa "simplemente conservar lo que ya tengo". Hace lo que hace mi ~~respuesta, pero con una sola operación bit a bit. Aunque también tiene la misma limitación que la escrita: no podemos pasar de 2 ^ 31 .
ruffin

1
no es correcto cuando truncate((10 * 2.9) / 100);este código devuelve 0.28 en lugar de 0.29 jsfiddle.net/9pf0732d
Alex

@Alex Como supongo que te darás cuenta ... ¡ bienvenido a JavaScript! . Hay arreglos. ¿Quizás te gustaría compartir uno? : D
ruffin

@ruffin Sé sobre este problema =) Pensé que esta respuesta era la solución a este problema. Desafortunadamente, todavía no he encontrado una solución exacta, en todas partes hay tal problema.
Alex


7

La respuesta de @ Dogbert se puede mejorar con Math.trunc, que trunca en lugar de redondear.

Existe una diferencia entre redondear y truncar. Truncar es claramente el comportamiento que busca esta pregunta. Si llamo a truncar (-3.14) y recibo -4 de vuelta, definitivamente lo llamaría indeseable. - @NickKnowlson

var a = 5.467;
var truncated = Math.trunc(a * 100) / 100; // = 5.46
var a = -5.467;
var truncated = Math.trunc(a * 100) / 100; // = -5.46

1
Eso no funciona en todos los casos, es decir, console.log (Math.trunc (9.28 * 100) / 100); // 9.27
Mike Makuch

@MikeMakuch eso no es un problema con Math.trunc, sino que 9.28 * 100es en 927.9999lugar de 928. Es posible que desee leer The Perils of Floating Point
zurfyx

5

Escribí una respuesta usando un método más corto. Esto es lo que se me ocurrió

function truncate(value, precision) {
    var step = Math.pow(10, precision || 0);
    var temp = Math.trunc(step * value);

    return temp / step;
}

El método se puede utilizar así

truncate(132456.25456789, 5)); // Output: 132456.25456
truncate(132456.25456789, 3)); // Output: 132456.254
truncate(132456.25456789, 1)); // Output: 132456.2   
truncate(132456.25456789));    // Output: 132456

O, si quieres una sintaxis más corta, aquí tienes

function truncate(v, p) {
    var s = Math.pow(10, p || 0);
    return Math.trunc(s * v) / s;
}

este es el método que hubiera esperado usar
taxilian

4
Number.prototype.trim = function(decimals) {
    var s = this.toString();
    var d = s.split(".");
    d[1] = d[1].substring(0, decimals);
    return parseFloat(d.join("."));
}

console.log((5.676).trim(2)); //logs 5.67

Me gusta que esto funcione con cadenas, eliminando así los matices de los números de coma flotante. ¡Gracias!
iCode

4

Creo que esta función podría ser una solución simple:

function trunc(decimal,n=2){
  let x = decimal + ''; // string 
  return x.lastIndexOf('.')>=0?parseFloat(x.substr(0,x.lastIndexOf('.')+(n+1))):decimal; // You can use indexOf() instead of lastIndexOf()
}

console.log(trunc(-241.31234,2));
console.log(trunc(241.312,5));
console.log(trunc(-241.233));
console.log(trunc(241.2,0));  
console.log(trunc(241));


Dos años después de que esto se publicó, pero me encontré con esto cuando estaba tratando de encontrar la mejor manera usando Math.trunc, regex, etc. Me gusta mucho esta solución. Muy simple pero funciona perfectamente (para mi caso de uso de todos modos).
giles123

Sin embargo, no olvide tener en cuenta n = 0.
giles123

3

Encontré un problema: considerando la siguiente situación: 2.1 o 1.2 o -6.4

¿Qué pasa si quieres siempre 3 decimales o dos o lo que sea? Entonces, debes completar los ceros iniciales a la derecha

// 3 decimals numbers
0.5 => 0.500

// 6 decimals
0.1 => 0.10000

// 4 decimales
-2.1 => -2.1000

// truncate to 3 decimals
3.11568 => 3.115

Esta es la función fija de Nick Knowlson

function truncateDecimals (num, digits) 
{
    var numS = num.toString();
    var decPos = numS.indexOf('.');
    var substrLength = decPos == -1 ? numS.length : 1 + decPos + digits;
    var trimmedResult = numS.substr(0, substrLength);
    var finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    // adds leading zeros to the right
    if (decPos != -1){
        var s = trimmedResult+"";
        decPos = s.indexOf('.');
        var decLength = s.length - decPos;

            while (decLength <= digits){
                s = s + "0";
                decPos = s.indexOf('.');
                decLength = s.length - decPos;
                substrLength = decPos == -1 ? s.length : 1 + decPos + digits;
            };
        finalResult = s;
    }
    return finalResult;
};

https://jsfiddle.net/huttn155/7/


x = 0.0000la prueba truncateDecimals (x, 2)falla. devuelve 0. no como se esperaba0.00
Ling Loeng

3
function toFixed(number, digits) {
    var reg_ex = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)")
    var array = number.toString().match(reg_ex);
    return array ? parseFloat(array[1]) : number.valueOf()
}

var test = 10.123456789
var __fixed = toFixed(test, 6)
console.log(__fixed)
// => 10.123456

3

La respuesta de @kirilloid parece ser la respuesta correcta, sin embargo, el código principal debe actualizarse. Su solución no se ocupa de los números negativos (que alguien mencionó en la sección de comentarios pero no se ha actualizado en el código principal).

Actualizando eso a una completa solución final probada:

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("([-]*\\d+\\.\\d{" + digits + "})(\\d)"),
    m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

Uso de muestra:

var x = 3.1415629;
Logger.log(x.toFixedDown(2)); //or use whatever you use to log

Violín: Número JS redondeado hacia abajo

PD: No hay suficientes repositorios para comentar sobre esa solución.


2

Aquí hay una función simple pero funcional para truncar números hasta 2 lugares decimales.

           function truncateNumber(num) {
                var num1 = "";
                var num2 = "";
                var num1 = num.split('.')[0];
                num2 = num.split('.')[1];
                var decimalNum = num2.substring(0, 2);
                var strNum = num1 +"."+ decimalNum;
                var finalNum = parseFloat(strNum);
                return finalNum;
            }

2

El tipo resultante sigue siendo un número ...

/* Return the truncation of n wrt base */
var trunc = function(n, base) {
    n = (n / base) | 0;
    return base * n;
};
var t = trunc(5.467, 0.01);

2

Lodash tiene algunos métodos de utilidad matemática que pueden redondear , piso y techo un número a una precisión decimal dada. Esto deja ceros finales.

Adoptan un enfoque interesante, utilizando el exponente de un número. Aparentemente, esto evita problemas de redondeo.

(Nota: funces Math.roundo ceilo flooren el código siguiente)

// Shift with exponential notation to avoid floating-point issues.
var pair = (toString(number) + 'e').split('e'),
    value = func(pair[0] + 'e' + (+pair[1] + precision));

pair = (toString(value) + 'e').split('e');
return +(pair[0] + 'e' + (+pair[1] - precision));

Enlace al código fuente


1

Aquí mi opinión sobre el tema:

convert.truncate = function(value, decimals) {
  decimals = (decimals === undefined ? 0 : decimals);
  return parseFloat((value-(0.5/Math.pow(10, decimals))).toFixed(decimals),10);
};

Es solo una versión un poco más elaborada de

(f - 0.005).toFixed(2)

1

El que está marcado como solución es la mejor solución que he encontrado hasta hoy, pero tiene un problema grave con 0 (por ejemplo, 0.toFixedDown (2) da -0.01). Entonces sugiero usar esto:

Number.prototype.toFixedDown = function(digits) {
  if(this == 0) {
    return 0;
  }
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

1

Esto es lo que uso:

var t = 1;
for (var i = 0; i < decimalPrecision; i++)
    t = t * 10;

var f = parseFloat(value);
return (Math.floor(f * t)) / t;

1
const TO_FIXED_MAX = 100;

function truncate(number, decimalsPrecison) {
  // make it a string with precision 1e-100
  number = number.toFixed(TO_FIXED_MAX);

  // chop off uneccessary digits
  const dotIndex = number.indexOf('.');
  number = number.substring(0, dotIndex + decimalsPrecison + 1);

  // back to a number data type (app specific)
  return Number.parseFloat(number);
}

// example
truncate(0.00000001999, 8);
0.00000001

funciona con:

  • números negativos
  • números muy pequeños (precisión numérica EPSILON)

0

solo para señalar una solución simple que funcionó para mí

convertirlo a cadena y luego regex it ...

var number = 123.45678;
var number_s = '' + number;
var number_truncated_s = number_s.match(/\d*\.\d{4}/)[0]
var number_truncated = parseFloat(number_truncated_s)

Se puede abreviar como

var number_truncated = parseFloat(('' + 123.4568908).match(/\d*\.\d{4}/)[0])

0

Aquí hay un código ES6 que hace lo que quieres

const truncateTo = (unRouned, nrOfDecimals = 2) => {
      const parts = String(unRouned).split(".");

      if (parts.length !== 2) {
          // without any decimal part
        return unRouned;
      }

      const newDecimals = parts[1].slice(0, nrOfDecimals),
        newString = `${parts[0]}.${newDecimals}`;

      return Number(newString);
    };

// your examples 

 console.log(truncateTo(5.467)); // ---> 5.46

 console.log(truncateTo(985.943)); // ---> 985.94

// other examples 

 console.log(truncateTo(5)); // ---> 5

 console.log(truncateTo(-5)); // ---> -5

 console.log(truncateTo(-985.943)); // ---> -985.94


0
Number.prototype.truncate = function(places) {
  var shift = Math.pow(10, places);

  return Math.trunc(this * shift) / shift;
};

0

Puedes trabajar con cadenas. Comprueba si '.' existe y luego elimina parte de la cadena.

truncar (7.88, 1) -> 7.8

truncar (7.889, 2) -> 7.89

truncar (-7.88, 1) -> -7.88

function  truncate(number, decimals) {
    const tmp = number + '';
    if (tmp.indexOf('.') > -1) {
        return +tmp.substr(0 , tmp.indexOf('.') + decimals+1 );
    } else {
        return +number
    }
 }

0

Estoy un poco confundido acerca de por qué hay tantas respuestas diferentes a una pregunta tan fundamentalmente simple; sólo hay dos enfoques que vi que merecen ser analizados. Hice una evaluación comparativa rápida para ver la diferencia de velocidad usando https://jsbench.me/ .

Esta es la solución que actualmente (26/9/2020) está marcada como la respuesta:

function truncate(n, digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = n.toString().match(re);
    return m ? parseFloat(m[1]) : n.valueOf();
};

[   truncate(5.467,2),
    truncate(985.943,2),
    truncate(17.56,2),
    truncate(0, 1),
    truncate(1.11, 1) + 22];

Sin embargo, esto está haciendo cosas de cadenas y expresiones regulares, lo que generalmente no es muy eficiente, y hay una función Math.trunc que hace exactamente lo que el OP quiere sin decimales. Por lo tanto, puede usarlo fácilmente más un poco de aritmética adicional para obtener lo mismo.

Aquí hay otra solución que encontré en este hilo, que es la que usaría:

function truncate(n, digits) {
    var step = Math.pow(10, digits || 0);
    var temp = Math.trunc(step * n);

    return temp / step;
}

[   truncate(5.467,2),
    truncate(985.943,2),
    truncate(17.56,2),
    truncate(0, 1),
    truncate(1.11, 1) + 22];
    

El primer método es "99,92% más lento" que el segundo, por lo que el segundo es definitivamente el que recomendaría usar.

Bien, volvamos a buscar otras formas de evitar el trabajo ...

captura de pantalla del punto de referencia

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.