Troncare (non arrotondare) i numeri decimali in javascript


93

Sto cercando di troncare i numeri decimali in posizioni decimali. Qualcosa come questo:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)fa quasi la cosa giusta ma arrotonda il valore. Non ho bisogno di arrotondare il valore. Spero che questo sia possibile in javascript.


6
jQuery è solo un framework e il tuo problema non è correlato a jQuery. Si tratta più di fare alcuni calcoli di base in JavaScript. Spero che anche tu sia soddisfatto di una soluzione non jQuery.
Felix Kling

Ho scoperto che è troppo faticoso fare in modo che i miei calcoli restituiscano solo 2 decimali utilizzando Javascript. Invece sono stato in grado di farlo facilmente nella mia visualizzazione del database. Mi rendo conto che questo metodo non si adatta a tutte le situazioni, ma voglio pubblicarlo qui perché potrebbe far risparmiare molto tempo a qualcuno.
MsTapp

Risposte:


51

aggiornamento :

Quindi, dopo tutto si è scoperto, gli insetti arrotondati ti perseguiteranno sempre, non importa quanto cerchi di compensarli. Quindi il problema dovrebbe essere affrontato rappresentando i numeri esattamente in notazione decimale.

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]

Vecchia soluzione soggetta a errori basata sulla compilazione di altri:

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ì, i prototipi non funzionano in modo affidabile su più browser. Invece di definire questa funzione (scopo limitato) attraverso il sistema dei tipi, in un modo che non funziona in modo affidabile, perché non metterla semplicemente in una libreria.
Thomas W

3
Questo non funziona come escluso. Prova il numero 17,56 e le cifre = 2. Dovrebbe essere 17,56, ma questa funzione restituisce 17,55.
shendz

2
Due incongruenze con questa funzione: Questa funzione restituisce una stringa quindi 1.11.toFixedDown(1) + 22finisce come 1.122invece di 23.1. Inoltre 0.toFixedDown(1)dovrebbe produrre 0ma invece produce -0.1.
Nick Knowlson

5
Notare che questa funzione rimuove il segno negativo. Es: (-10.2131).toFixedDown(2) // ==> 10.21.
rgajrawala

4
Inoltre, (1e-7).toFixedDown(0) // ==> 1e-7. Ritiene che per 1e-(>=7)(es: 1e-8, 1e-9, ...).
rgajrawala

60

La risposta di Dogbert è buona, ma se il tuo codice potrebbe avere a che fare con numeri negativi, Math.floorda solo potrebbe dare risultati inaspettati.

Ad esempio Math.floor(4.3) = 4, maMath.floor(-4.3) = -5

Usa invece una funzione di supporto come questa per ottenere risultati coerenti:

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

Ecco una versione più conveniente di questa funzione:

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 questo non è il comportamento desiderato, inserisci una chiamata a Math.absnella prima riga:

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

EDIT: shendz sottolinea correttamente che l'utilizzo di questa soluzione con a = 17.56produrrà in modo errato 17.55. Per ulteriori informazioni sul motivo per cui ciò accade, leggi Quello che ogni informatico dovrebbe sapere sull'aritmetica in virgola mobile . Sfortunatamente, scrivere una soluzione che elimini tutte le fonti di errore in virgola mobile è piuttosto complicato con javascript. In un'altra lingua useresti numeri interi o forse un tipo Decimal, ma con javascript ...

Questa soluzione dovrebbe essere precisa al 100%, ma sarà anche più 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);
}

Per coloro che hanno bisogno di velocità ma vogliono anche evitare errori in virgola mobile, prova qualcosa come BigDecimal.js . Puoi trovare altre librerie BigDecimal javascript in questa domanda SO: "Esiste una buona libreria BigDecimal Javascript?" ed ecco un buon post sul blog sulle librerie matematiche per Javascript


Perché inaspettato? Cambiare la direzione dell'arrotondamento quando si scende al di sotto di 0 causa tutti i tipi di artefatti aritmetici e calcoli scadenti. Ad esempio, il doppio dei numeri verrà arrotondato a 0 rispetto a qualsiasi altro numero intero. Per la grafica, la contabilità e molti altri usi, otterrai risultati scadenti. A dire la verità, sarebbe più difficile dire che cosa il vostro suggerimento è bene a dire di cosa si tratta non è .
Thomas W

È utile esattamente per quello che dice, quando vuoi troncare i decimali anziché arrotondare.
Nick Knowlson

1
Non funzionerà con 17.56 perché il browser fornisce 17,56 * 100 = 1755,9999999999998 non 1756
shendz

Buon punto shendz. Ho aggiornato la mia risposta con una soluzione che elimina tutti gli errori in virgola mobile per coloro che ne hanno bisogno.
Nick Knowlson

1
Questo non funzionerà per i numeri inferiori a 1 se non vuoi decimali: truncateDecimals (.12345, 0) restituisce NaN a meno che tu non aggiunga un segno di spunta: if(isNAN(result) result = 0; dipende dal comportamento che desideri.
Jeremy Witmer

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

5
Funziona bene ma darà risultati che sono probabilmente indesiderabili se lui (o qualcun altro che guarda questa risposta in seguito) deve affrontare numeri negativi. Vedi stackoverflow.com/a/9232092/224354
Nick Knowlson

3
Perché indesiderabile? Cambiare la direzione dell'arrotondamento quando si scende al di sotto di 0 causa tutti i tipi di artefatti aritmetici.
Thomas W,

8
C'è una differenza tra l'arrotondamento e il troncamento. Il troncamento è chiaramente il comportamento che questa domanda sta cercando. Se chiamo truncate(-3.14)e ricevo in risposta -4, lo definirei sicuramente indesiderabile.
Nick Knowlson

Sono d'accordo con Thomas. La differenza di prospettiva può derivare dal fatto che di solito stai troncando per la visualizzazione o per il calcolo. Da una prospettiva computazionale, questo evita "artefatti aritmetici"

3
var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 Quindi questa non è una soluzione corretta
Sanyam Jain

22

È possibile correggere l'arrotondamento sottraendo 0,5 per toFixed, ad es

(f - 0.005).toFixed(2)

1
Attenzione: poiché è questo non funziona per numeri molto piccoli, numeri con più di 3 decimali o numeri negativi. Prova .0045, 5.4678 e -5.467
Nick Knowlson

Questo funzionerà fintanto che abbini il valore che stai sottraendo con la lunghezza che desideri avere. qualunque cosa passi a toFixed () deve essere il numero di 0 dopo il decimale.
dmarra

18

Considerare approfittando del doppio tilde:~~ .

Prendi il numero. Moltiplicare per cifre significative dopo il decimale in modo da poter troncare a zero posizioni con ~~. Dividi di nuovo quel moltiplicatore. Profitto.

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

Sto cercando di resistere a racchiudere la ~~chiamata in parentesi; l'ordine delle operazioni dovrebbe farlo funzionare correttamente, credo.

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

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

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

Collegamento JSFiddle .

EDIT: Ripensando, ho involontariamente gestito anche i casi per arrotondare anche a sinistra del decimale.

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

La logica è un po 'stravagante alla ricerca di tale utilizzo e potrebbe trarre vantaggio da un rapido refactoring. Ma funziona ancora. Più fortunato che buono.


Questa è la migliore risposta. Se estendi il Mathprototipo con questo e controlli NaN-s prima dell'esecuzione, sarebbe semplicemente perfetto.
Bartłomiej Zalewski

truncator((10 * 2.9) / 100, 2)restituisci 0,28 invece di 0,29 ... jsfiddle.net/25tgrzq1
Alex

14

Bella soluzione a una riga:

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

Quindi chiamalo con:

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

2
Mi piace questa soluzione, ma tieni presente che non è completamente supportata da tutti i browser. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Gene Parcellano

11

Ho pensato di inserire una risposta usando |poiché è semplice e funziona bene.

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

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

1
Ottima scelta. L'uso di un operatore bit per bit forza il valore in un int, e oring con 0 significa "mantieni solo quello che ho già". Fa quello che fa la mia ~~risposta, ma con un'unica operazione bit per bit. Sebbene abbia anche la stessa limitazione di quanto scritto: non possiamo andare oltre 2 ^ 31 .
ruffin

1
non corretto quando truncate((10 * 2.9) / 100);questo codice restituisce 0.28 invece di 0.29 jsfiddle.net/9pf0732d
Alex

@Alex Come immagino ti rendi conto ... benvenuto in JavaScript! . Ci sono soluzioni. Forse ti piacerebbe condividerne uno? : D
ruffin

@ Ruffin So di questo problema =) Ho pensato che questa risposta fosse la soluzione a questo problema. Sfortunatamente, non ho ancora trovato una soluzione esatta, ovunque ci sia un problema del genere.
Alex


7

La risposta di @ Dogbert può essere migliorata con Math.trunc, che tronca invece di arrotondare.

C'è una differenza tra l'arrotondamento e il troncamento. Il troncamento è chiaramente il comportamento che questa domanda sta cercando. Se chiamo truncate (-3.14) e ricevo -4 indietro, lo definirei sicuramente indesiderabile. - @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
Questo non funziona in tutti i casi, cioè console.log (Math.trunc (9.28 * 100) / 100); // 9.27
Mike Makuch

@ MikeMakuch che non è un problema con Math.trunc, ma piuttosto che 9.28 * 100è 927.9999piuttosto che 928. Potresti leggere The Perils of Floating Point
zurfyx

5

Ho scritto una risposta usando un metodo più breve. Ecco cosa mi è venuto in mente

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

    return temp / step;
}

Il metodo può essere utilizzato in questo modo

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

Oppure, se vuoi una sintassi più breve, ecco qui

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

questo è il metodo che mi sarei aspettato di utilizzare
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

Mi piace che funzioni con le stringhe, eliminando così le sfumature dei numeri in virgola mobile. Grazie!
iCode

4

Penso che questa funzione possa essere una soluzione semplice:

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


Due anni dopo che questo è stato pubblicato, ma mi sono imbattuto in questo quando stavo cercando di elaborare il modo migliore usando Math.trunc, regex, ecc. Mi piace molto questa soluzione. Morto semplice ma funziona perfettamente (per il mio caso d'uso comunque).
giles123

Non dimenticare di tenere conto di n = 0 però.
giles123

3

Ho trovato un problema: considerando la prossima situazione: 2.1 o 1.2 o -6.4

E se vuoi sempre 3 decimali o due o wharever, quindi, devi completare gli zeri iniziali a destra

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

Questa è la funzione fissa di 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.0000il test truncateDecimals (x, 2)fallisce. ritorna 0. non come previsto0.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 risposta di @kirilloid sembra essere la risposta corretta, tuttavia, il codice principale deve essere aggiornato. La sua soluzione non si prende cura dei numeri negativi (che qualcuno ha menzionato nella sezione commenti ma non è stato aggiornato nel codice principale).

Aggiornandolo a una soluzione finale testata 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();
};

Utilizzo di esempio:

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

Violino: numero JS arrotondato per difetto

PS: non abbastanza repo per commentare quella soluzione.


2

Ecco una funzione semplice ma funzionante per troncare il numero fino a 2 cifre decimali.

           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

Il tipo risultante rimane un numero ...

/* 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 ha alcuni metodi di utilità matematici che possono arrotondare , pavimento e soffitto un numero per una determinata precisione decimale. Questo esclude gli zeri finali.

Adottano un approccio interessante, utilizzando l'esponente di un numero. Apparentemente questo evita problemi di arrotondamento.

(Nota: funcè Math.roundo ceilo floornel codice sottostante)

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

Collegamento al codice sorgente


1

Ecco la mia opinione sull'argomento:

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

È solo una versione leggermente più elaborata di

(f - 0.005).toFixed(2)

1

Quella contrassegnata come soluzione è la soluzione migliore che ho trovato fino ad oggi, ma ha un problema serio con 0 (ad esempio, 0.toFixedDown (2) dà -0.01). Quindi suggerisco di usare questo:

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

Ecco cosa 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

lavora con:

  • numeri negativi
  • numeri molto piccoli (precisione Number.EPSILON)

0

solo per indicare una soluzione semplice che ha funzionato per me

convertirlo in stringa e quindi regexarlo ...

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)

Può essere abbreviato in

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

0

Ecco un codice ES6 che fa quello che vuoi

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

Puoi lavorare con le stringhe. Controlla se "." esiste, quindi rimuove parte della stringa.

troncare (7.88, 1) -> 7.8

troncare (7.889, 2) -> 7.89

troncare (-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

Sono un po 'confuso sul motivo per cui ci sono così tante risposte diverse a una domanda così fondamentalmente semplice; ci sono solo due approcci che ho visto che sembrava valesse la pena guardare. Ho fatto un rapido benchmark per vedere la differenza di velocità usando https://jsbench.me/ .

Questa è la soluzione che è attualmente (26/9/2020) contrassegnata come risposta:

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

Tuttavia, questo sta facendo cose di stringa e regex, che di solito non è molto efficiente, e c'è una funzione Math.trunc che fa esattamente ciò che l'OP vuole solo senza decimali. Pertanto, puoi facilmente usarlo più un po 'di aritmetica extra per ottenere la stessa cosa.

Ecco un'altra soluzione che ho trovato su questo thread, che è quella che userei:

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

Il primo metodo è "99,92% più lento" del secondo, quindi il secondo è sicuramente quello che consiglierei di usare.

Ok, torniamo a trovare altri modi per evitare il lavoro ...

screenshot del benchmark

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.