Truncar (não arredondar) números decimais em javascript


95

Estou tentando truncar números decimais em casas decimais. Algo assim:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)faz exatamente a coisa certa, mas arredonda o valor. Eu não preciso do valor arredondado. Espero que isso seja possível em javascript.


6
jQuery é apenas uma estrutura e seu problema não está relacionado a jQuery. É mais sobre como fazer alguns cálculos básicos em JavaScript. Espero que você também esteja satisfeito com uma solução não jQuery.
Felix Kling

Achei muito trabalhoso fazer meus cálculos retornarem apenas 2 decimais usando Javascript. Consegui fazer isso facilmente na visualização do meu banco de dados. Sei que esse método não se aplica a todas as situações, mas quero colocá-lo aqui porque pode economizar muito tempo de alguém.
MsTapp 01 de

Respostas:


51

upd :

Então, afinal de contas, os bugs circulares sempre irão persegui-lo, não importa o quanto você tente compensá-los. Portanto, o problema deve ser atacado representando os números exatamente em notação 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]

Antiga solução sujeita a erros baseada na compilação de outros:

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
Sim, os protótipos não funcionam de forma confiável em navegadores diferentes. Em vez de definir essa função (finalidade limitada) por meio do sistema de tipos, de uma forma que não funciona de forma confiável, por que não colocá-la em uma biblioteca.
Thomas W

3
Isso não funciona com exceção. Tente o número 17,56 e dígitos = 2. Deve ser 17,56, mas esta função retorna 17,55.
shendz

2
Duas inconsistências com esta função: Esta função retorna uma string e 1.11.toFixedDown(1) + 22termina como em 1.122vez de 23.1. Também 0.toFixedDown(1)deve produzir, 0mas em vez disso, produz -0.1.
Nick Knowlson

5
Observe que esta função remove o sinal negativo. Ex: (-10.2131).toFixedDown(2) // ==> 10.21.
rgajrawala

4
Além disso, (1e-7).toFixedDown(0) // ==> 1e-7. Faz isso para 1e-(>=7)(ex: 1e-8, 1e-9, ...).
rgajrawala

63

A resposta de Dogbert é boa, mas se seu código tiver que lidar com números negativos, Math.floorpor si só pode fornecer resultados inesperados.

Por exemplo Math.floor(4.3) = 4, masMath.floor(-4.3) = -5

Use uma função auxiliar como esta para obter 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

Esta é uma versão mais conveniente desta função:

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

Se esse não for o comportamento desejado, insira uma chamada para Math.absna primeira linha:

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

EDIT: shendz corretamente aponta que usar esta solução com a = 17.56produzirá incorretamente 17.55. Para saber mais sobre por que isso acontece, leia O que todo cientista da computação deve saber sobre aritmética de ponto flutuante . Infelizmente, escrever uma solução que elimina todas as fontes de erro de ponto flutuante é muito complicado com javascript. Em outro idioma você usaria inteiros ou talvez um tipo decimal, mas com javascript ...

Esta solução deve ser 100% precisa, mas também será mais 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 quem precisa de velocidade, mas também deseja evitar erros de ponto flutuante, tente algo como BigDecimal.js . Você pode encontrar outras bibliotecas javascript BigDecimal nesta pergunta do SO: "Existe uma boa biblioteca Javascript BigDecimal?" e aqui está uma boa postagem no blog sobre bibliotecas matemáticas para Javascript


Por que inesperado? Mudar a direção do arredondamento quando você vai abaixo de 0, causa todos os tipos de artefatos aritméticos e matemática de baixa qualidade. Por exemplo, duas vezes mais números serão arredondados para 0, como qualquer outro número inteiro. Para gráficos, contabilidade e muitos outros usos, você obterá resultados ruins. Para dizer a verdade, seria mais difícil dizer o que sua sugestão é boa para a ponto de dizer o que é não .
Thomas W

É bom exatamente para o que diz - quando você deseja truncar decimais em vez de arredondar.
Nick Knowlson

1
Não funcionará com 17,56 porque o navegador dá 17,56 * 100 = 1755.9999999999998 não 1756
shendz

Bom ponto, shendz. Atualizei minha resposta com uma solução que elimina todos os erros de ponto flutuante para quem precisa disso.
Nick Knowlson

1
Isso não funcionará para números menores que 1 se você não quiser decimais - truncateDecimals (.12345, 0) resulta em NaN a menos que você adicione uma verificação: if(isNAN(result) result = 0; Depende do comportamento que você deseja.
Jeremy Witmer

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

5
Isso funciona bem, mas dará resultados que são provavelmente indesejáveis ​​se ele (ou outra pessoa que olhar para esta resposta mais tarde) tiver que lidar com números negativos. Consulte stackoverflow.com/a/9232092/224354
Nick Knowlson

3
Por que indesejável? Alterar a direção do arredondamento quando você vai abaixo de 0, causa todos os tipos de artefatos aritméticos.
Thomas W

8
Há uma diferença entre arredondamento e truncamento. Truncar é claramente o comportamento que essa pergunta está procurando. Se eu ligar truncate(-3.14)e receber de -4volta, definitivamente considerarei isso indesejável.
Nick Knowlson

Eu concordo com Thomas. A diferença de perspectiva pode vir com o fato de você estar geralmente truncando para exibição ou para computação. De uma perspectiva computacional, isso evita "artefatos aritméticos"

3
var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 Portanto, esta não é uma solução correta
Sanyam Jain

21

Você pode corrigir o arredondamento subtraindo 0,5 de toFixed, por exemplo

(f - 0.005).toFixed(2)

1
Atenção: como é, isso não funciona para números muito pequenos, números com mais de 3 casas decimais ou números negativos. Tente 0,0045, 5,4678 e -5,467
Nick Knowlson

Isso funcionará, contanto que você corresponda o valor que está subtraindo com o comprimento que deseja ter. tudo o que você passar para toFixed () precisa ser o número de zeros após o decimal.
dmarra,

18

Considere aproveitando a dupla til:~~ .

Pegue o número. Multiplique por dígitos significativos após o decimal para que você possa truncar para zero casas com ~~. Divida esse multiplicador de volta. Lucro.

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

Estou tentando resistir a envolver a ~~chamada entre parênteses; a ordem das operações deve fazer com que funcione corretamente, eu acredito.

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

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

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

Link JSFiddle .

EDIT: Olhando para trás, eu também lidei sem querer com casos para arredondar à esquerda do decimal.

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

A lógica é um pouco maluca procurando por esse uso e pode se beneficiar de uma refatoração rápida. Mas ainda funciona. Melhor sorte do que bem.


Esta é a melhor resposta. Se você estender o Mathprotótipo com isso e verificar se há NaN-s antes de executá-lo, será perfeito.
Bartłomiej Zalewski

truncator((10 * 2.9) / 100, 2)retorna 0,28 em vez de 0,29 ... jsfiddle.net/25tgrzq1
Alex

Não está funcionando para mim, o truncador (1000.12345678, 7) retorna 141.1299975
HelpPanda

@Alex Se você quiser evitar erros de ponto flutuante, use um tipo decimal . Ou acho que poderíamos reescrever para usar a manipulação de cordas, mas isso parece loucura. Você notará que respostas semelhantes têm o mesmo problema.
ruffin de

@HelpfulPanda Isso ocorre porque o JavaScript usa ints de 32 bits para operadores bit a bit . O int máximo de 32 bits é 2.147.483.647 e 100012345678é significativamente maior que 2147483647. Se você realmente tiver números maiores do que 32 bits (em vez disso, se precisar de tantos dígitos significativos), este não é o andróide que você está procurando. Esta é uma resposta rápida e suja (e rápida) para, digamos, 4-5 dígitos de sigilo. Para perfeição potencialmente exagerada, tente a resposta aceita . ; ^ D
ruffin de

14

Boa solução de uma linha:

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

Em seguida, chame-o com:

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

2
Eu gosto dessa solução, mas lembre-se de que ela não é totalmente compatível com todos os navegadores. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Gene Parcellano

Adoraria se funcionasse 100%, mas Math.trunc (0,29 * Math.pow (10, 2)) / Math.pow (10, 2) dá 0,28. Para ser honesto, a única maneira confiável que vi até agora é dividindo / cortando cordas.
helpfulPanda

11

Pensei em adicionar uma resposta usando, |pois é simples e funciona bem.

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

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

1
Boa decisão. Usar um operador bit a bit força o valor em um int, e oring com 0 significa "apenas manter o que já tenho". Faz o que minha ~~resposta faz, mas com uma única operação bit a bit. Embora também tenha a mesma limitação escrita: Não podemos ultrapassar 2 ^ 31 .
ruffin

1
incorreto quando truncate((10 * 2.9) / 100);este código retorna 0,28 em vez de 0,29 jsfiddle.net/9pf0732d
Alex

@Alex Suponho que você perceba ... bem-vindo ao JavaScript! . Existem soluções. Talvez você queira compartilhar um? : D
ruffin

@ruffin eu sei sobre esse problema =) Eu pensei que essa resposta fosse a solução para esse problema. Infelizmente, ainda não encontrei uma solução exata, em todos os lugares onde existe esse problema.
Alex


7

A resposta de @Dogbert pode ser melhorada com Math.trunc, que trunca em vez de arredondar.

Há uma diferença entre arredondar e truncar. Truncar é claramente o comportamento que essa pergunta está procurando. Se eu chamar truncate (-3,14) e receber -4 de volta, eu definitivamente consideraria isso indesejável. - @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
Isso não funciona em todos os casos, ou seja, console.log (Math.trunc (9.28 * 100) / 100); // 9.27
Mike Makuch,

@MikeMakuch isso não é um problema com Math.trunc, mas em vez de 9.28 * 100é 927.9999melhor que 928. Você pode querer ler The Perils of Floating Point
zurfyx

5

Escrevi uma resposta usando um método mais curto. Aqui está o que eu inventei

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

    return temp / step;
}

O método pode ser usado assim

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

Ou, se você quiser uma sintaxe mais curta, aqui está

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

este é o método que eu esperava usar
taxilian

Mesmo problema apontado por outros, truncar (0,29, 2) resulta em 0,28.
helpfulPanda

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

Eu gosto que isso funcione com strings, eliminando assim as nuances dos números de ponto flutuante. Obrigado!
iCode

console.log ((- 5) .trim (2)); throws Uncaught TypeError: d [1] is undefined
helpfulPanda

4

Acho que essa função pode ser uma solução simples:

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


Dois anos depois que isso foi postado, mas me deparei com isso quando estava tentando descobrir a melhor maneira usando Math.trunc, regex, etc. Eu realmente gosto dessa solução. É muito simples, mas funciona perfeitamente (pelo menos para o meu caso de uso).
giles123

Não se esqueça de levar em consideração n = 0.
giles123

3

Encontrei um problema: considerando a próxima situação: 2.1 ou 1.2 ou -6.4

E se você quiser sempre 3 casas decimais ou duas ou qualquer outra coisa, então, você tem que completar os zeros à direita

// 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 é a função fixa 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.0000o teste truncateDecimals (x, 2)falha. retorna 0. não como esperado0.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

A resposta de @kirilloid parece ser a resposta correta, no entanto, o código principal precisa ser atualizado. Sua solução não trata de números negativos (que alguém mencionou na seção de comentários, mas não foram atualizados no código principal).

Atualizando para uma solução testada final completa:

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

Amostra de uso:

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

Violino: número JS arredondado para baixo

PS: Repo insuficiente para comentar essa solução.


2

Aqui está uma função simples, mas funcional para truncar o número até 2 casas decimais.

           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

O tipo resultante permanece um 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 tem alguns métodos de utilitário de matemática que pode rodada , chão , e ceil um número para uma determinada precisão decimal. Isso deixa os zeros à direita.

Eles têm uma abordagem interessante, usando o expoente de um número. Aparentemente, isso evita problemas de arredondamento.

(Nota: funcé Math.roundou ceilou floorno código abaixo)

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

Link para o código-fonte


1

Aqui está minha opinião sobre o assunto:

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

É apenas uma versão um pouco mais elaborada de

(f - 0.005).toFixed(2)

1

Aquela que está marcada como solução é a melhor solução que encontrei até hoje, mas está com um problema sério com 0 (por exemplo, 0.toFixedDown (2) dá -0,01). Então, sugiro usar isso:

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

Aqui está o que eu 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 com:

  • números negativos
  • números muito pequenos (precisão Number.EPSILON)

0

apenas para apontar uma solução simples que funcionou para mim

convertê-lo em string e regexá-lo ...

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)

Pode ser abreviado para

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

0

Aqui está um código ES6 que faz o que você deseja

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

Você pode trabalhar com cordas. Ele verifica se '.' existe e, em seguida, remove parte da string.

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

Estou um pouco confuso sobre por que existem tantas respostas diferentes para uma pergunta tão fundamentalmente simples; eu vi apenas duas abordagens que pareciam valer a pena olhar. Fiz um benchmark rápido para ver a diferença de velocidade usando https://jsbench.me/ .

Esta é a solução que está atualmente (26/09/2020) sinalizada como a resposta:

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

No entanto, isso está fazendo coisas de string e regex, o que geralmente não é muito eficiente, e há uma função Math.trunc que faz exatamente o que o OP deseja, mas sem decimais. Portanto, você pode facilmente usar isso mais um pouco de aritmética extra para obter a mesma coisa.

Aqui está outra solução que encontrei neste tópico, que eu usaria:

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

O primeiro método é "99,92% mais lento" do que o segundo, então o segundo é definitivamente aquele que eu recomendaria usar.

Ok, voltando a encontrar outras maneiras de evitar o trabalho ...

captura de tela do benchmark

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.