Zaokrąglij do maksymalnie 2 miejsc po przecinku (tylko w razie potrzeby)

2756

Chciałbym zaokrąglić maksymalnie 2 miejsca po przecinku, ale tylko w razie potrzeby .

Wejście:

10
1.7777777
9.1

Wynik:

10
1.78
9.1

Jak mogę to zrobić w JavaScript?

śmierdzacy
źródło
22
Zrobiłem skrzypce wieloma technikami oferowanymi tutaj jako rozwiązania ... więc możesz porównać: skrzypce
dsdsdsdsd
37
Nikt nie zdaje sobie z tego sprawy Number.EPSILON. Zastosowanie Math.round( num * 100 + Number.EPSILON ) / 100.
cronvel
3
W przypadku nowych czytelników nie możesz tego zrobić, chyba że masz wynik typu ciąg . Matematyka zmiennoprzecinkowa na binarnych wewnętrznych reprezentacjach liczb oznacza, że zawsze istnieją liczby, które nie mogą być reprezentowane jako czyste miejsca po przecinku.
Walf
9
@cronvel Czy możesz wyjaśnić powód używania Number.EPSILONtutaj?
Bruce Sun,
5
Upadłem do króliczej nory i przetestowałem niektóre z ciekawszych odpowiedzi na tej stronie (tylko pierwsza strona). Oto Codepen . Wskazówka: im więcej głosów pozytywnych ma odpowiedź, tym mniejsze są szanse, że zadziała ona poprawnie.
Adam Jagosz

Odpowiedzi:

3488

Posługiwać się Math.round(num * 100) / 100

Edycja: aby upewnić się, że rzeczy takie jak 1.005 są prawidłowe, używamy

Math.round((num + Number.EPSILON) * 100) / 100

Brian Ustas
źródło
395
Chociaż będzie to działać w większości przypadków, nie zadziała w przypadku 1.005, co ostatecznie skończy się na 1 zamiast 1,01
James
83
@James Wow, to naprawdę dziwne - pracuję w konsoli deweloperskiej Chrome i zauważam, że 1,005 * 100 = 100,49999999999999. Math.round (100.49999999999999) ocenia na 100, podczas gdy Math.round (100.5) ocenia na 101. IE9 robi to samo. Wynika to z dziwności zmiennoprzecinkowej w javascript
stinkycheeseman
153
Proste obejście. Dla 2 dp użyj Math.round((num + 0.00001) * 100) / 100. Spróbuj Math.round((1.005 + 0.00001) * 100) / 100iMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan,
31
@mrkschan Dlaczego to działa i czy jest niezawodny dla wszystkich liczb?
CMCDragonkai
86
Dla tych, którzy tego nie rozumieją, technika ta nazywana jest skalowaniem. Zasadniczo odpowiedzią na to pytanie jest przeniesienie dwóch liczb w przecinku dziesiętnym, zamieniając cyfrę na liczbę całkowitą, aby uniknąć wszystkich zwariowanych liczb zmiennoprzecinkowych, zaokrąglić to, a następnie przełożyć z powrotem na to, co było wcześniej, dzieląc przez 100, a masz odpowiedź na 2dp.
Alex_Nabu
3061

Jeśli wartość jest typem tekstu:

parseFloat("123.456").toFixed(2);

Jeśli wartość jest liczbą:

var numb = 123.23454;
numb = numb.toFixed(2);

Minusem jest to, że wartości takie jak 1,5 dadzą „1,50” jako wynik. Poprawka sugerowana przez @minitech:

var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.

Wydaje się, że Math.roundjest to lepsze rozwiązanie. Ale to nie jest! W niektórych przypadkach NIE zaokrągli się poprawnie:

Math.round(1.005 * 1000)/1000 // Returns 1 instead of expected 1.01!

toFixed () również NIE będzie poprawnie zaokrąglać w niektórych przypadkach (testowane w Chrome v.55.0.2883.87)!

Przykłady:

parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.

1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.

Myślę, że to dlatego, że 1.555 jest właściwie czymś w rodzaju pływaka 1.55499994 za kulisami.

Rozwiązaniem 1 jest użycie skryptu z wymaganym algorytmem zaokrąglania, na przykład:

function roundNumber(num, scale) {
  if(!("" + num).includes("e")) {
    return +(Math.round(num + "e+" + scale)  + "e-" + scale);
  } else {
    var arr = ("" + num).split("e");
    var sig = ""
    if(+arr[1] + scale > 0) {
      sig = "+";
    }
    return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
  }
}

https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview

UWAGA: To nie jest uniwersalne rozwiązanie dla wszystkich. Istnieje kilka różnych algorytmów zaokrąglania, implementacja może być różna, zależy od wymagań. https://en.wikipedia.org/wiki/Rounding

Rozwiązaniem 2 jest uniknięcie obliczeń frontonu i pobranie zaokrąglonych wartości z serwera zaplecza.

Kunin
źródło
81
To podejście (toFixed) jest dobre i działało dla mnie, ale w szczególności nie jest zgodne z pierwotną prośbą „tylko w razie potrzeby”. (Zaokrągla 1,5 do 1,50, co łamie specyfikację.)
Per Lundberg
29
Aby spełnić wymóg „w razie potrzeby”: parseFloat(number.toFixed(decimalPlaces)); @PerLundberg
Onur Yıldırım
36
parseFloat("55.555").toFixed(2)powraca "55.55"w konsoli programisty Chrome.
Levi Botelho,
22
Nie ma żadnej korzyści z używania toFixed zamiast Math.round; toFixed prowadzi do tych samych problemów z zaokrąglaniem (wypróbuj je z 5.555 i 1.005), ale jest o 500x (bez żartów) wolniejszy niż Math.round ... Wydaje się, że odpowiedź @MarkG jest tutaj bardziej dokładna.
Pierre
17
toFixed nie „czasami” zwraca łańcucha, zawsze zwraca łańcuch.
McGuireV10,
464

Możesz użyć

function roundToTwo(num) {    
    return +(Math.round(num + "e+2")  + "e-2");
}

Znalazłem to w MDN . Ich sposób unika się problemu z 1,005, który został wymieniony .

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57
MarkG
źródło
13
@ Redsandro, +(val)jest odpowiednikiem użycia przymusu Number(val). Łączenie „e-2” z liczbą zaowocowało ciągiem, który musiał zostać przekonwertowany z powrotem na liczbę.
Jack
41
Uwaga: dla dużych i małych pływaków, które produkowałyby NaN, ponieważ np. + „1e-21 + 2” nie zostanie poprawnie przeanalizowane.
Pierre
16
„Rozwiązałeś” problem „1.005”, ale wprowadziłeś nowy: teraz, w konsoli Chrome, roundToTwo(1.0049999999999999)pojawia się jako 1.01 (nieuchronnie od tego czasu 1.0049999999999999 == 1.005). Wydaje mi się, że liczba zmiennoprzecinkowa, którą otrzymujesz, jeśli wpiszesz num = 1.005„oczywiście”, powinna zaokrąglić do 1,00, ponieważ dokładna wartość parametru num jest mniejsza niż 1,005. Oczywiście wydaje mi się również, że ciąg „1.005” „oczywiście” powinien zostać zaokrąglony do 1,01. Fakt, że różni ludzie wydają się mieć różne intuicje na temat tego, jakie jest właściwe prawidłowe zachowanie, jest częścią tego, dlaczego jest to skomplikowane.
Mark Amery
35
Nie ma (zmiennoprzecinkowe) Numer pomiędzy 1.0049999999999999i 1.005tak z definicji, jest to ta sama liczba. Nazywa się to cięciem dedekindowym.
Azmisov
6
@Azmisov ma rację. Chociaż 1.00499 < 1.005jest true, 1.0049999999999999 < 1.005ocenia na false.
falconepl,
146

Odpowiedź MarkG jest poprawna. Oto ogólne rozszerzenie dla dowolnej liczby miejsc po przecinku.

Number.prototype.round = function(places) {
  return +(Math.round(this + "e+" + places)  + "e-" + places);
}

Stosowanie:

var n = 1.7777;    
n.round(2); // 1.78

Test jednostkowy:

it.only('should round floats to 2 places', function() {

  var cases = [
    { n: 10,      e: 10,    p:2 },
    { n: 1.7777,  e: 1.78,  p:2 },
    { n: 1.005,   e: 1.01,  p:2 },
    { n: 1.005,   e: 1,     p:0 },
    { n: 1.77777, e: 1.8,   p:1 }
  ]

  cases.forEach(function(testCase) {
    var r = testCase.n.round(testCase.p);
    assert.equal(r, testCase.e, 'didn\'t get right number');
  });
})
Lavamantis
źródło
20
Pierre podniósł poważny problem z odpowiedzią MarkG.
dsjoerg
9
Uwaga: jeśli nie chcesz zmieniać prototypu Number.prototype - po prostu napisz to jako funkcję: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Philipp Tsipman,
2
Świetny sposób na rozwinięcie tego. Możesz sprawdzić, czy liczba dziesiętna jest ujemna, a następnie odwrócić e + i e-. Następnie n = 115; n. runda (-2); wyprodukuje 100
Lee Louviere
3
Spróbuj n: 1e + 19 - zwraca NaN
DavidJ
3
Algorytm ten zawsze zaokrągla w górę (zamiast od zera). Tak więc, w przypadku liczb ujemnych, wynik nie jest tym, czego można się spodziewać:(-1.005).round(2) === -1
Aleksej Komarov
123

Powinieneś użyć:

Math.round( num * 100 + Number.EPSILON ) / 100

Nikt nie zdaje sobie z tego sprawy Number.EPSILON.

Warto również zauważyć, że nie jest to dziwność JavaScript, jak niektórzy twierdzą.

Jest to po prostu sposób, w jaki liczby zmiennoprzecinkowe działają w komputerze. Podobnie jak 99% języków programowania, JavaScript nie ma domowych liczb zmiennoprzecinkowych; w tym celu wykorzystuje procesor / FPU. Komputer używa binarnych, a w binarnych nie ma żadnych liczb takich 0.1, ale jedynie binarne przybliżenie. Dlaczego? Z tego samego powodu, co 1/3, nie można zapisać dziesiętnie: jego wartość wynosi 0.33333333 ... z nieskończonością trzech.

Tu przyjść Number.EPSILON. Liczba ta jest różnicą między 1 a kolejną liczbą występującą w liczbach zmiennoprzecinkowych podwójnej precyzji. To wszystko: nie ma liczby między 1a 1 + Number.EPSILON.

EDYTOWAĆ:

Jak pytamy w komentarzach, wyjaśnijmy jedną rzecz: dodawanie Number.EPSILONjest istotne tylko wtedy, gdy wartość do zaokrąglenia jest wynikiem operacji arytmetycznej, ponieważ może połknąć pewną deltę błędu zmiennoprzecinkowego.

Nie jest to przydatne, gdy wartość pochodzi z bezpośredniego źródła (np. Dosłowność, dane wejściowe użytkownika lub czujnik).

EDYCJA (2019):

Jak zauważyli @maganap i niektórzy ludzie, najlepiej dodać Number.EPSILONprzed pomnożeniem:

Math.round( ( num + Number.EPSILON ) * 100 ) / 100

EDYCJA (grudzień 2019):

Ostatnio używam funkcji podobnej do tej do porównywania liczb świadomych epsilon:

const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;

function epsilonEquals( a , b ) {
  if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
    return false ;
  }
  if ( a === 0 || b === 0 ) {
    return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
  }
  return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}

Mój przypadek użycia to biblioteka asercji + sprawdzania poprawności, którą rozwijam od wielu lat.

W rzeczywistości w kodzie używam ESPILON_RATE = 1 + 4 * Number.EPSILONi EPSILON_ZERO = 4 * Number.MIN_VALUE(czterokrotnie epsilon), ponieważ chcę, aby moduł sprawdzania równości był wystarczająco luźny, aby kumulować błąd zmiennoprzecinkowy.

Jak na razie wygląda mi to idealnie. Mam nadzieję, że to pomoże.

cronvel
źródło
1
@palota możesz zdefiniować naprawdę liczbę zębów, o której wspomina EPSILON cronvel
Daniel San
3
To właściwa odpowiedź! Sam problem jest związany z tym, jak liczby zmiennoprzecinkowe działają wewnętrznie, a nie o javascript
Daniel San
22
W rzeczywistości należy dodać epsilon PRZED pomnożeniem przez 100 Math.round( (num + Number.EPSILON) * 100) / 100.. Zgadzam się również, że jest to właściwa metoda prawidłowego zaokrąglania (chociaż nie jest to dokładnie to, o co pytano w tym pytaniu).
maganap,
2
@cronvel Hmm. Masz rację; wybór kierunku ma sens. Sądzę więc, że mój pozostały sprzeciw polega na tym, że robienie tego ma sens tylko wtedy, gdy pracujesz w domenie, w której masz zasadniczy powód, by myśleć, że twoją wartością jest „okrągła liczba”. Jeśli wiesz, że twoje dane wejściowe są wynikiem prostych operacji arytmetycznych na liczbach z małą liczbą miejsc dziesiętnych, to na pewno prawdopodobnie masz tylko 0.004999999999999999wynik złożonego błędu zmiennoprzecinkowego, a matematycznie poprawny wynik to prawdopodobnie 0,005. Czy to odczyt z czujnika? Nie tak bardzo.
Mark Amery
1
@marchaos Nie zawodzi: połówki są zawsze zaokrąglane w górę . Np. Math.round(1.5)= 2, ale Math.round(-1.5)= -1. Jest to więc całkowicie spójne. Tutaj -1 jest większe niż -2, tak jak -1000 jest większe niż -1000.01. Nie mylić z większymi liczbami bezwzględnymi .
cronvel
84

Można użyć .toFixed(NumberOfDecimalPlaces).

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
Gourav Singla
źródło
4
Również dlatego, że dodaje końcowe zera, co nie jest tym , o które pytało oryginalne pytanie .
Alastair Maw
2
Ale końcowe zera są łatwo usuwane za pomocą wyrażenia regularnego, tj. „Number (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10.1
Czad
1
@daniel top odpowiedź jest taka sama (jak na razie), ale to nie zawsze dostawaliśmy runda poprawnie, spróbuj +(1.005).toFixed(2)która zwraca 1zamiast 1.01.
Emile Bergeron,
3
@ChadMcElligott: Twoje wyrażenie regularne nie działa dobrze z liczbami całkowitymi: Number(9).toFixed(2).replace(/0+$/, '')=> „9.”
Jacob van Lingen
Wygląda na to, że to nie zaokrągla. Zasadniczo to po prostu obcina. W niektórych przypadkach wystarczy! Dzięki.
iedmrc
78

To pytanie jest skomplikowane.

Załóżmy, że mamy funkcję, roundTo2DP(num)która przyjmuje zmienną jako argument i zwraca wartość zaokrągloną do 2 miejsc po przecinku. Co powinno oceniać każde z tych wyrażeń?

  • roundTo2DP(0.014999999999999999)
  • roundTo2DP(0.0150000000000000001)
  • roundTo2DP(0.015)

„Oczywistą” odpowiedzią jest to, że pierwszy przykład powinien zaokrąglić do 0,01 (ponieważ jest bliżej 0,01 niż do 0,02), podczas gdy pozostałe dwa powinny zaokrąglić do 0,02 (ponieważ 0,0150000000000000001 jest bliższy 0,02 niż do 0,01, a ponieważ 0,015 jest dokładnie w połowie drogi między i istnieje konwencja matematyczna, że ​​takie liczby są zaokrąglane w górę).

Haczyk, który być może zgadłeś, polega na tym, że roundTo2DP nie można go wprowadzić w celu udzielenia tych oczywistych odpowiedzi, ponieważ wszystkie trzy przekazane mu liczby są tej samej liczby . Binarne liczby zmiennoprzecinkowe IEEE 754 (używane przez JavaScript) nie mogą dokładnie reprezentować większości liczb niecałkowitych, więc wszystkie trzy literały liczbowe powyżej są zaokrąglane do pobliskiej prawidłowej liczby zmiennoprzecinkowej. Ta liczba, jak to się dzieje, jest dokładnie

0,01499999999999999944488848768742172978818416595458984375

co jest bliższe 0,01 niż 0,02.

Możesz zobaczyć, że wszystkie trzy liczby są takie same w konsoli przeglądarki, powłoce węzła lub innym tłumaczu JavaScript. Po prostu porównaj je:

> 0.014999999999999999 === 0.0150000000000000001
true

Więc kiedy piszę m = 0.0150000000000000001, dokładna wartość tegom , z czym się skończyłem, jest bliższa 0.01niż jest 0.02. A jednak, jeśli przekonwertuję mna ciąg znaków ...

> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015

... Dostaję 0,015, co powinno zaokrąglić do 0,02, i który wyraźnie nie jest liczbą z 56 miejsc po przecinku, którą wcześniej powiedziałem, że wszystkie te liczby są dokładnie równe. Co to za mroczna magia?

Odpowiedź można znaleźć w specyfikacji ECMAScript, w sekcji 7.1.12.1: ToString zastosowane do typu Number . Ustanowiono tu zasady konwertowania pewnej liczby m na ciąg znaków. Kluczową częścią jest punkt 5, w którym generowana jest liczba całkowita s, której cyfry zostaną wykorzystane w reprezentacji ciągu m :

niech n , k i s będą liczbami całkowitymi takimi, że k ≥ 1, 10 k -1s <10 k , wartość liczbowa dla s × 10 n - k wynosi m , a k jest tak małe, jak to możliwe. Zauważ, że k jest liczbą cyfr w reprezentacji dziesiętnej s , że s nie jest podzielna przez 10, i że najmniej znacząca cyfra s niekoniecznie jest jednoznacznie określona przez te kryteria.

Kluczową częścią jest tutaj wymóg, aby „ k było tak małe, jak to możliwe”. Co wymóg ten wynosi Jest to wymóg, że otrzymuje numer m, wartość String(m)musi mieć możliwie najmniejszą liczbę cyfr , a jednocześnie spełniający wymagania tego Number(String(m)) === m. Ponieważ już to wiemy 0.015 === 0.0150000000000000001, teraz jest jasne, dlaczego String(0.0150000000000000001) === '0.015'musi to być prawda.

Oczywiście, żadna z tych dyskusji nie odpowiedziała bezpośrednio na to, co roundTo2DP(m) powinno powrócić. Jeśli mdokładna wartość wynosi 0,01499999999999999944488848768742172978818416595458984375, ale jej ciąg reprezentuje „0,015”, to jaka jest prawidłowa odpowiedź - matematycznie, praktycznie, filozoficznie lub cokolwiek innego - kiedy zaokrąglamy ją do dwóch miejsc po przecinku?

Nie ma jednej poprawnej odpowiedzi na to pytanie. To zależy od twojego przypadku użycia. Prawdopodobnie chcesz uszanować reprezentację ciągu i zaokrąglić w górę, gdy:

  • Reprezentowana wartość jest z natury dyskretna, np. Ilość waluty w walucie z 3 miejscami po przecinku, takiej jak dinary. W tym przypadku prawdziwa wartość liczby takiej jak 0,015 wynosi 0,015, a reprezentacja 0,0149999999 ... w binarnym zmiennoprzecinkowym jest błędem zaokrąglenia. (Oczywiście wielu argumentuje rozsądnie, że do obsługi takich wartości należy używać biblioteki dziesiętnej i nigdy nie przedstawiać ich jako liczb binarnych zmiennoprzecinkowych.)
  • Wartość została wpisana przez użytkownika. W tym przypadku ponownie dokładna wprowadzona liczba dziesiętna jest bardziej „prawdziwa” niż najbliższa binarna reprezentacja zmiennoprzecinkowa.

Z drugiej strony, prawdopodobnie chcesz przestrzegać binarnej wartości zmiennoprzecinkowej i zaokrąglać w dół, gdy twoja wartość pochodzi z naturalnie ciągłej skali - na przykład, jeśli jest to odczyt z czujnika.

Te dwa podejścia wymagają innego kodu. Aby uszanować reprezentację ciągu w postaci liczby, możemy (z dość rozsądnie subtelnym kodem) zaimplementować własne zaokrąglanie, które działa bezpośrednio na reprezentację ciągu, cyfra po cyfrze, przy użyciu tego samego algorytmu, którego używałbyś w szkole uczono, jak zaokrąglać liczby. Poniżej znajduje się przykład, który spełnia wymóg OP dotyczący reprezentowania liczby do 2 miejsc po przecinku „tylko w razie potrzeby” poprzez usunięcie końcowych zer po przecinku; możesz oczywiście dostosować go do swoich konkretnych potrzeb.

/**
 * Converts num to a decimal string (if it isn't one already) and then rounds it
 * to at most dp decimal places.
 *
 * For explanation of why you'd want to perform rounding operations on a String
 * rather than a Number, see http://stackoverflow.com/a/38676273/1709587
 *
 * @param {(number|string)} num
 * @param {number} dp
 * @return {string}
 */
function roundStringNumberWithoutTrailingZeroes (num, dp) {
    if (arguments.length != 2) throw new Error("2 arguments required");

    num = String(num);
    if (num.indexOf('e+') != -1) {
        // Can't round numbers this large because their string representation
        // contains an exponent, like 9.99e+37
        throw new Error("num too large");
    }
    if (num.indexOf('.') == -1) {
        // Nothing to do
        return num;
    }

    var parts = num.split('.'),
        beforePoint = parts[0],
        afterPoint = parts[1],
        shouldRoundUp = afterPoint[dp] >= 5,
        finalNumber;

    afterPoint = afterPoint.slice(0, dp);
    if (!shouldRoundUp) {
        finalNumber = beforePoint + '.' + afterPoint;
    } else if (/^9+$/.test(afterPoint)) {
        // If we need to round up a number like 1.9999, increment the integer
        // before the decimal point and discard the fractional part.
        finalNumber = Number(beforePoint)+1;
    } else {
        // Starting from the last digit, increment digits until we find one
        // that is not 9, then stop
        var i = dp-1;
        while (true) {
            if (afterPoint[i] == '9') {
                afterPoint = afterPoint.substr(0, i) +
                             '0' +
                             afterPoint.substr(i+1);
                i--;
            } else {
                afterPoint = afterPoint.substr(0, i) +
                             (Number(afterPoint[i]) + 1) +
                             afterPoint.substr(i+1);
                break;
            }
        }

        finalNumber = beforePoint + '.' + afterPoint;
    }

    // Remove trailing zeroes from fractional part before returning
    return finalNumber.replace(/0+$/, '')
}

Przykładowe użycie:

> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'

Powyższa funkcja jest prawdopodobnie tym, czego chcesz użyć, aby uniknąć zauważania przez użytkowników nieprawidłowo zaokrąglonego numeru.

(Alternatywnie można również wypróbować bibliotekę round10, która zapewnia podobnie zachowującą się funkcję z zupełnie inną implementacją).

Ale co, jeśli masz drugi rodzaj liczby - wartość zaczerpnięta z ciągłej skali, gdzie nie ma powodu sądzić, że przybliżone reprezentacje dziesiętne z mniejszą liczbą miejsc dziesiętnych są dokładniejsze niż te z większą liczbą? W takim przypadku nie chcemy respektować reprezentacji String, ponieważ ta reprezentacja (jak wyjaśniono w specyfikacji) jest już nieco zaokrąglona; nie chcemy popełnić błędu mówiąc „0,014999999 ... 375 zaokrągla w górę do 0,015, co zaokrągla w górę do 0,02, a więc 0,014999999 ... 375 zaokrągla w górę do 0,02”.

Tutaj możemy po prostu użyć wbudowanej toFixedmetody. Zauważ, że wywołując Number()zwrócony ciąg toFixed, otrzymujemy liczbę, której reprezentacja ciągu nie ma zer zerowych (dzięki temu, jak JavaScript oblicza reprezentację ciągu liczby, omówioną wcześniej w tej odpowiedzi).

/**
 * Takes a float and rounds it to at most dp decimal places. For example
 *
 *     roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
 *
 * returns 1.234
 *
 * Note that since this treats the value passed to it as a floating point
 * number, it will have counterintuitive results in some cases. For instance,
 * 
 *     roundFloatNumberWithoutTrailingZeroes(0.015, 2)
 *
 * gives 0.01 where 0.02 might be expected. For an explanation of why, see
 * http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
 * roundStringNumberWithoutTrailingZeroes function there instead.
 *
 * @param {number} num
 * @param {number} dp
 * @return {number}
 */
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
    var numToFixedDp = Number(num).toFixed(dp);
    return Number(numToFixedDp);
}
Mark Amery
źródło
To nie działa w niektórych przypadkach krawędzi: spróbuj ( jsfiddle ) z roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2). Oczekiwany rezultat (jak w PHP echo round(362.42499999999995, 2)) 362.43. Rzeczywisty wynik:362.42
Dr. Gianluigi Zane Zanettini,
1
@ Dr.GianluigiZaneZanettini Huh. Dziwne. Nie jestem pewien, dlaczego PHP rounddaje 362,43. To wydaje się intuicyjnie niepoprawne, ponieważ 362.42499999999995 jest mniejszy niż 362,425 (w matematyce i kodzie - 362.42499999999995 < 362.425jest to prawda zarówno w JS, jak i PHP). Od tego czasu odpowiedź PHP nie minimalizuje odległości między oryginalnymi a zaokrąglonymi liczbami zmiennoprzecinkowymi 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42. Według php.net/manual/en/function.round.php , PHP jest roundzgodne ze standardem C99; Będę musiał zapuścić się do krainy C, aby zrozumieć, co się dzieje.
Mark Amery,
1
Po przyjrzeniu się implementacji zaokrąglania w źródle PHP , nie mam zielonego pojęcia, co robi. To strasznie skomplikowana implementacja pełna makr i rozgałęzień oraz konwersji ciągów i rozgałęzień na mocno zakodowanych wartościach magicznej precyzji. Nie jestem pewien, co powiedzieć poza „odpowiedzią PHP jest rażąco błędna i powinniśmy zgłosić błąd”. Przy okazji, jak znalazłeś numer 362.42499999999995?
Mark Amery,
1
@ Dr.GianluigiZaneZanettini Stworzyłem raport o błędzie: bugs.php.net/bug.php?id=75644
Mark Amery
2
@ Dr.GianluigiZaneZanettini „Czy mam jakiś sens?” - nie; nie tak działa zaokrąglanie. 1,000005 kończy się na pięć, ale jeśli zostanie zaokrąglona do najbliższej liczby całkowitej, odpowiedź powinna wynosić 1, a nie 2. Podobnie, 1499995 kończy się na pięć, ale jeśli zostanie zaokrąglona do najbliższego miliona, wynik powinien wynosić 1000000, a nie 2000000. W przypadku 362,42424999999995 po zaokrągleniu do 2 DP, tym, co powinno określić kierunek zaokrąglania, jest trzecie miejsce po przecinku, które wynosi 4.
Mark Amery
76

Rozważ .toFixed()i .toPrecision():

http://www.javascriptkit.com/javatutors/formatnumber.shtml

AceCorban
źródło
1
toFixed dodaje kropki dziesiętne do każdej wartości bez względu na wszystko.
stinkycheeseman
13
Oba są tutaj bezużyteczne
Esailija,
Niestety obie funkcje dodają dodatkowe miejsca dziesiętne, których @stinkycheeseman wydaje się nie chcieć.
jackwanders
2
zwracają ciągi, a nie liczby, więc formatują i nie obliczają ...
saimiris_devel
63

Precyzyjna metoda zaokrąglania. Źródło: Mozilla

(function(){

    /**
     * Decimal adjustment of a number.
     *
     * @param   {String}    type    The type of adjustment.
     * @param   {Number}    value   The number.
     * @param   {Integer}   exp     The exponent (the 10 logarithm of the adjustment base).
     * @returns {Number}            The adjusted value.
     */
    function decimalAdjust(type, value, exp) {
        // If the exp is undefined or zero...
        if (typeof exp === 'undefined' || +exp === 0) {
            return Math[type](value);
        }
        value = +value;
        exp = +exp;
        // If the value is not a number or the exp is not an integer...
        if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
            return NaN;
        }
        // Shift
        value = value.toString().split('e');
        value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
        // Shift back
        value = value.toString().split('e');
        return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
    }

    // Decimal round
    if (!Math.round10) {
        Math.round10 = function(value, exp) {
            return decimalAdjust('round', value, exp);
        };
    }
    // Decimal floor
    if (!Math.floor10) {
        Math.floor10 = function(value, exp) {
            return decimalAdjust('floor', value, exp);
        };
    }
    // Decimal ceil
    if (!Math.ceil10) {
        Math.ceil10 = function(value, exp) {
            return decimalAdjust('ceil', value, exp);
        };
    }
})();

Przykłady:

// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50
użytkownik
źródło
Ktoś umieścił to również na GitHub i npm: github.com/jhohlfeld/round10
Jo Liss
Nie, Math.round10(3544.5249, -2)zwraca 3544,52 zamiast 3544,53
Matija
2
@Matija Wolfram alpha mówi również 3544.52. Chcesz zminimalizować błąd między bieżącą liczbą a zaokrąglonym przybliżeniem. Najbliższe przybliżenie 3544.5249do 2 miejsc po przecinku to 3544.52(błąd = 0,0049). Gdyby tak było 3544.53, błąd wynosiłby 0,0051. Robisz kolejne zaokrąglanie, tj. Math.round10 (Math.round10 (3544.5249, -3), -2), co daje większy błąd zaokrąglania i dlatego nie jest pożądane.
użytkownik
3
@Matija, z matematycznego punktu widzenia, zaokrąglony 3544.5249 ma wartość 3544.52, a nie 3544.53, więc ten kod jest poprawny. Jeśli chcesz zaokrąglić go do 3544,53 w tym przypadku i takie przypadki (nawet trudne jest niepoprawne), zrób coś takiego:number += 0.00011
Bozidar Sikanjic
@Matija: Myślę, że funkcja działa tak, jak powinna. Może chcesz Math.round10( Math.round10(3544.5249, -3) , -2)
powtórzyć
60

Żadna z odpowiedzi tutaj nie jest poprawna . @stinkycheeseman poprosił o zaokrąglenie w górę , wszyscy zaokrągliliście liczbę.

Aby zaokrąglić w górę, użyj tego:

Math.ceil(num * 100)/100;
machineaddict
źródło
15
Przykładowe dane wejściowe i wyjściowe pokazują, że chociaż pytanie brzmiało „zaokrąglić w górę ...”, w rzeczywistości miało być „zaokrąglone do ...”.
JayDM
2
@stinkycheeseman zwrócił uwagę na błąd w konkretnym przypadku, nie chciał zawsze zaokrąglać w górę tak jak robi to
sufit
9
Znaleziono dziwny błąd podczas testowania Math.ceil(1.1 * 100)/100;- zwraca 1.11, ponieważ 1,1 * 100 jest 110.00000000000001zgodny z nowymi nowoczesnymi przeglądarkami Firefox, Chrome, Safari i Opera ... IE, w starym stylu, wciąż myśli 1.1*100=1100.
skobaljic
1
@skobaljic tryMath.ceil(num.toFixed(4) * 100) / 100
treeface
1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100powróci również 1.11w Firefoksie, problem współczesnych przeglądarek / błąd związany jest z mnożeniem i ludzie powinni o tym wiedzieć (pracowałem na przykład nad loterią).
skobaljic
47

Oto prosty sposób, aby to zrobić:

Math.round(value * 100) / 100

Możesz jednak zrobić osobną funkcję, aby to zrobić za Ciebie:

function roundToTwo(value) {
    return(Math.round(value * 100) / 100);
}

Wtedy po prostu podasz wartość.

Możesz go zwiększyć do zaokrąglania do dowolnej liczby miejsc po przecinku, dodając drugi parametr.

function myRound(value, places) {
    var multiplier = Math.pow(10, places);

    return (Math.round(value * multiplier) / multiplier);
}
JayDM
źródło
2
To rozwiązanie jest złe, patrz stackoverflow.com/questions/38322372/... jeśli wprowadzisz 156893.145 i zaokrąglisz go powyższą funkcją, otrzymasz 156893.14 zamiast 156893.15 !!!
saimiris_devel
2
@saimiris_devel, udało Ci się znaleźć instancję, w której liczbowa reprezentacja liczb w JavaScript nie działa. Masz rację, że zaokrągla niepoprawnie. Ale - dzieje się tak, ponieważ liczba próbek po pomnożeniu przez 100 jest już zepsuta (15689314.499999998). Naprawdę, w pełni funkcjonalna odpowiedź wymagałaby biblioteki, która została specjalnie opracowana w celu uwzględnienia niespójności w obsłudze liczb rzeczywistych przez JavaScript. W przeciwnym razie możesz prawdopodobnie unieważnić dowolną z odpowiedzi udzielonych na to pytanie.
JayDM
36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
użytkownik3711536
źródło
1
Nie zawsze da to takie same wyniki, jakie uzyskałbyś, gdybyś wziął reprezentację ciągu swojego numeru i zaokrąglił go. Na przykład +(0.015).toFixed(2) == 0.01.
Mark Amery
35

Dla mnie Math.round () nie dawał poprawnej odpowiedzi. Odkryłem, że toFixed (2) działa lepiej. Poniżej znajdują się przykłady obu:

console.log(Math.round(43000 / 80000) * 100); // wrong answer

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer

Vikasdeep Singh
źródło
Ważne, aby pamiętać, że toFixed nie wykonuje zaokrąglania, a Math.round zaokrągla do najbliższej liczby całkowitej. Aby zachować liczby dziesiętne, musimy pomnożyć pierwotną liczbę przez liczbę potęg dziesięciu, których zera reprezentują pożądaną liczbę miejsc dziesiętnych, a następnie podzielić wynik przez tę samą liczbę. W twoim przypadku: Math.round (43000/80000 * 100 * 100) / 100. W końcu można zastosować toFixed (2), aby mieć pewność, że w wyniku zawsze pojawią się dwa miejsca po przecinku (w razie potrzeby z zerami) - idealne za wyrównanie do prawej szeregu liczb przedstawionych pionowo :)
Turbo
7
Należy również zauważyć, że .toFIxed () wyświetla ciąg, a nie liczbę.
carpiediem
2
To wciąż nie rozwiązuje problemu zaokrąglania 1.005. (1.005).toFixed(2)nadal wyniki dla 1.00.
DPac
34

Użyj tej funkcji Number(x).toFixed(2);

Harish.bazee
źródło
8
Zawiń to wszystko Numberponownie, jeśli nie chcesz, aby był zwracany jako ciąg:Number(Number(x).toFixed(2));
5
NumberPołączenie nie jest konieczne, x.toFixed(2)działa.
bgusach
3
@bgusach Wymagane wywołanie numeru, ponieważ instrukcja x.toFixed (2) zwraca ciąg znaków, a nie liczbę. Aby ponownie przekonwertować na liczbę, musimy owinąć Liczba
Mohan Ram
2
Podczas korzystania z tej metody (1).toFixed(2)zwraca 1.00, ale 1w tym przypadku potrzebny jest pytający .
Eugene Mala
1
To nie działa, 1.005.toFixed(2)daje, "1"kiedy powinno być "1.01".
Adam Jagosz
33

2017
Wystarczy użyć kodu natywnego.toFixed()

number = 1.2345;
number.toFixed(2) // "1.23"

Jeśli musisz być ścisły i dodawać cyfry tylko w razie potrzeby, możesz go użyć replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');
pery mimon
źródło
3
Metoda toFixed zwraca ciąg znaków. Jeśli chcesz uzyskać wynik liczbowy, musisz wysłać wynik toFixed do parseFloat.
Zambonilli,
@Zambonilli Lub po prostu pomnóż przez 1, jeśli to konieczne. ale ponieważ stała liczba jest w większości przypadków do wyświetlenia, a nie do łańcucha obliczeniowego, jest to właściwy format
pery mimon
2
-1; nie tylko toFixedzasugerowało wiele odpowiedzi wiele lat wcześniej, ale nie spełnia warunku „tylko w razie potrzeby” w pytaniu; (1).toFixed(2)daje, "1.00"gdzie pytający chciał "1".
Mark Amery
Ok, rozumiem. Dodaję też rozwiązanie dla tej sprawy
pery mimon
Jeśli używasz lodash, jest to jeszcze łatwiejsze: _.round (number, decimalPlace) Usunąłem mój ostatni komentarz, ponieważ ma problem. Ale Lodash _.round DZIAŁA. 1,005 z miejscem dziesiętnym 2 przekształca na 1,01.
Devin Fields
32

Wypróbuj to lekkie rozwiązanie:

function round(x, digits){
  return parseFloat(x.toFixed(digits))
}

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222
petermeissner
źródło
Czy ktoś wie, czy jest jakaś różnica między tym a return Number(x.toFixed(digits))?
1
@JoeRocc ... nie powinno mieć znaczenia, o ile widzę, ponieważ i tak .toFixed()dopuszcza tylko liczby.
petermeissner,
4
Ta odpowiedź ma ten sam problem, co kilkakrotnie wspomniany na tej stronie. Spróbuj round(1.005, 2)zobaczyć wynik 1zamiast 1.01.
MilConDoin,
wydaje się bardziej problem z zaokrąglaniem algo? - jest więcej niż można sobie wyobrazić: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1
petermeissner,
31

Można to zrobić na kilka sposobów. Dla ludzi takich jak ja wariant Lodasha

function round(number, precision) {
    var pair = (number + 'e').split('e')
    var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
    pair = (value + 'e').split('e')
    return +(pair[0] + 'e' + (+pair[1] - precision))
}

Stosowanie:

round(0.015, 2) // 0.02
round(1.005, 2) // 1.01

Jeśli twój projekt używa jQuery lub lodash, możesz również znaleźć odpowiednią roundmetodę w bibliotekach.

Aktualizacja 1

Usunąłem wariant n.toFixed(2), ponieważ jest nieprawidłowy. Dziękuję @ lawina 1

stanleyxu2005
źródło
Druga opcja zwróci ciąg z dokładnie dwoma punktami dziesiętnymi. Pytanie wymaga kropki dziesiętnej tylko w razie potrzeby. Pierwsza opcja jest lepsza w tym przypadku.
Marcos Lima,
@MarcosLima Number.toFixed()zwróci ciąg, ale przed nim znak plus, interpreter JS przekształci ciąg na liczbę. To jest cukier składniowy.
stanleyxu2005
W przeglądarce Firefox alert((+1234).toFixed(2))pokazuje „1234.00”.
Marcos Lima,
W Firefox alert(+1234.toFixed(2))rzuca SyntaxError: identifier starts immediately after numeric literal. Trzymam się pierwszej opcji.
Marcos Lima,
To nie działa w niektórych przypadkach krawędzi: spróbuj ( jsfiddle ) z 362.42499999999995. Oczekiwany rezultat (jak w PHP echo round(362.42499999999995, 2)) 362.43. Rzeczywisty wynik:362.42
Dr. Gianluigi Zane Zanettini,
26

Jeśli używasz biblioteki lodash, możesz użyć okrągłej metody lodash w następujący sposób.

_.round(number, precision)

Na przykład:

_.round(1.7777777, 2) = 1.78
Madura Pradeep
źródło
@Peter Zestaw funkcji, które zapewnia Lodash, jest naprawdę dobry w porównaniu do standardowego Javascript. Jednak słyszałem, że Lodash ma pewne problemy z wydajnością w porównaniu do standardowego JS. codeburst.io/…
Madura Pradeep
1
Zgadzam się z tym, że korzystanie z lodash ma wady wydajności. Myślę, że te kwestie są wspólne wielu abstrakcjom. Ale spójrz tylko, ile odpowiedzi znajduje się w tym wątku i jak zawodzą intuicyjne rozwiązania dla przypadków skrajnych. Widzieliśmy ten wzór w jQuery i problem root został rozwiązany, gdy przeglądarki przyjęły wspólny standard, który rozwiązał większość naszych przypadków użycia. Wąskie gardła wydajności zostały następnie przeniesione do silników przeglądarki. Myślę, że to samo powinno się zdarzyć. :)
Peter
26

Od wersji ES6 istnieje „właściwy” sposób (bez zastępowania statyki i tworzenia obejść), aby to zrobić za pomocą toPrecision

var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));

var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));

var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4));

wtedy możesz po prostu parseFloati zero zniknie.

console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));

Nie rozwiązuje to jednak „problemu zaokrąglania 1.005” - ponieważ jest ono nieodłącznie związane z przetwarzaniem ułamków swobodnych .

console.log(1.005 - 0.005);

Jeśli jesteś otwarty na biblioteki, możesz użyć bignumber.js

console.log(1.005 - 0.005);
console.log(new BigNumber(1.005).minus(0.005));

console.log(new BigNumber(1.005).round(4));
console.log(new BigNumber(1.005).round(3));
console.log(new BigNumber(1.005).round(2));
console.log(new BigNumber(1.005).round(1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"></script>

Matas Vaitkevicius
źródło
3
(1.005).toPrecision(3)wciąż powraca 1.00zamiast 1.01faktycznie.
Giacomo
toPrecisionzwraca ciąg, który zmienia pożądany typ wyjścia.
adamduren
@Giacomo Nie jest to wada .toPrecisionmetody, jest ona swoistością liczb zmiennoprzecinkowych (którymi są liczby w JS) - spróbuj 1.005 - 0.005, zwróci 0.9999999999999999.
shau-kote
1
(1).toPrecision(3)zwraca „1,00”, ale pytający chciał mieć 1w tym przypadku.
Eugene Mala
1
Jak powiedział @Giacomo, ta odpowiedź wydaje się mylić „cyfry znaczące” z „zaokrągleniem do liczby miejsc po przecinku”. toPrecisionrobi ten format, a nie ten drugi, i nie jest odpowiedzią na pytanie PO, choć może się wydawać na pierwszy rzut oka trafny, robi się bardzo źle. Zobacz en.wikipedia.org/wiki/Sinentant_figures . Na przykład Number(123.4).toPrecision(2)zwroty "1.2e+2"i Number(12.345).toPrecision(2)zwroty "12". Zgadzam się również z twierdzeniem @ adamduren, że zwraca ciąg, który nie jest pożądany (nie jest to duży problem, ale nie jest pożądany).
Neek,
23

MarkG i Lavamantis zaproponowali znacznie lepsze rozwiązanie niż to, które zostało zaakceptowane. Szkoda, że ​​nie dostają więcej głosów pozytywnych!

Oto funkcja, której używam do rozwiązywania problemów z liczbami zmiennoprzecinkowymi również na podstawie MDN . Jest nawet bardziej ogólny (ale mniej zwięzły) niż rozwiązanie Lavamantisa:

function round(value, exp) {
  if (typeof exp === 'undefined' || +exp === 0)
    return Math.round(value);

  value = +value;
  exp  = +exp;

  if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
    return NaN;

  // Shift
  value = value.toString().split('e');
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));

  // Shift back
  value = value.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}

Używaj go z:

round(10.8034, 2);      // Returns 10.8
round(1.275, 2);        // Returns 1.28
round(1.27499, 2);      // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46

W porównaniu z rozwiązaniem Lavamantis możemy zrobić ...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123
astorije
źródło
2
Twoje rozwiązanie nie obejmuje niektórych przypadków, w przeciwieństwie do rozwiązania MDN. Chociaż może być krótszy, nie jest dokładny ...
astorije,
1
okrągły (-1835,665,2) => -1835,66
Jorge Sampayo
21

To może ci pomóc:

var result = Math.round(input*100)/100;

Aby uzyskać więcej informacji, możesz spojrzeć na ten link

Math.round (num) vs num.toFixed (0) i niespójności przeglądarki

skazany
źródło
1
Dlaczego na świecie zaakceptowana odpowiedź ma o wiele więcej głosów niż ta, skoro są praktycznie tym samym, ale ta została opublikowana 1 minutę po zaakceptowanej?
Cytuj Dave'a
18

Najłatwiejszym rozwiązaniem byłoby użycie toFixed, a następnie usunięcie końcowych zer za pomocą funkcji Number:

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
Marcin Wanago
źródło
nie działa to we wszystkich przypadkach. wykonaj obszerne testy przed opublikowaniem odpowiedzi.
baburao
@buraura Proszę zamieścić skrzynkę, w której powyższe rozwiązanie nie działa
Marcin Wanago,
stała liczba = 15; Number (number.toFixed (2)); 15.00 zamiast 15
Kevin Jhangiani
1
@KevinJhangiani const number = 15; Number (number.toFixed (2)); // 15 - Testowałem to zarówno na najnowszym Chrome, jak i Firefox
Marcin Wanago
@KevinJhangiani jak się dostać 15.00? Liczby w JS nie przechowują miejsc dziesiętnych, a każdy wyświetlacz automatycznie obcina nadmiar miejsc dziesiętnych (dowolne zera na końcu).
VLAZ
16
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2) tutaj 2 to liczba cyfr w górę, którą chcemy zaokrąglić tę liczbę.

Ritesh Dhuri
źródło
ten .toFixed () jest prostszy do wdrożenia. po prostu przejrzyj to raz.
Ritesh Dhuri,
14

Najprostszy sposób:

+num.toFixed(2)

Konwertuje go na ciąg, a następnie z powrotem na liczbę całkowitą / liczbę zmiennoprzecinkową.

bigpotato
źródło
Dzięki za tę najprostszą odpowiedź. Czym jednak jest „+” w + num? Nie działało to dla mnie, gdy wartość dziesiętna była ciągiem. Zrobiłem: (num * 1) .toFixed (2).
Ethan
@momo wystarczy zmienić argument toFixed()na 3. Tak więc byłoby +num.toFixed(3).
Działa
1
@Edmund Ma zwrócić 1,01, a nie 1,00
mmm
13

Oto prototypowa metoda:

Number.prototype.round = function(places){
    places = Math.pow(10, places); 
    return Math.round(this * places)/places;
}

var yournum = 10.55555;
yournum = yournum.round(2);
arielf
źródło
13

Użyj czegoś takiego: „parseFloat (parseFloat (wartość) .toFixed (2))”

parseFloat(parseFloat("1.7777777").toFixed(2))-->1.78 
parseFloat(parseFloat("10").toFixed(2))-->10 
parseFloat(parseFloat("9.1").toFixed(2))-->9.1
Arulraj
źródło
1
nie, jeśli niedokładność jest nieodłącznym elementem reprezentacji zmiennoprzecinkowej. po prostu go usuniesz, a następnie ponownie wprowadzisz ten sam błąd, ponownie konwertując go na zmiennoprzecinkowy!
Ben McIntyre
12

Jednym ze sposobów osiągnięcia takiego zaokrąglenia tylko w razie konieczności jest użycie Number.prototype.toLocaleString () :

myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})

Zapewni to dokładnie oczekiwany wynik, ale jako ciągi znaków. Nadal możesz przekonwertować je z powrotem na liczby, jeśli nie jest to oczekiwany typ danych.

Javarome
źródło
Jest to jak dotąd najczystsze rozwiązanie, które omija wszystkie skomplikowane problemy zmiennoprzecinkowe, ale obsługa MDN jest wciąż niekompletna - Safari nie obsługuje jeszcze przekazywania argumentów toLocaleString.
Mark Amery
@MarkAmery Na razie tylko przeglądarka Android ma pewne problemy: caniuse.com/#search=toLocaleString
ptyskju
12

Po przejściu przez różne iteracje wszystkich możliwych sposobów osiągnięcia prawdziwej dokładności zaokrąglania dziesiętnego, jasne jest, że najdokładniejszym i najskuteczniejszym rozwiązaniem jest użycie Number.EPSILON. To zapewnia prawdziwe matematyczne rozwiązanie problemu precyzji obliczeń zmiennoprzecinkowych. Można go łatwo wypełnić, jak pokazano tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON w celu obsługi wszystkich pozostałych użytkowników IE (wtedy znowu może powinien przestać to robić).

Zaadaptowano z rozwiązania podanego tutaj: https://stackoverflow.com/a/48850944/6910392

Proste rozwiązanie, które zapewnia dokładne zaokrąglanie dziesiętne, podłogi i sufit, z opcjonalną zmienną dokładności bez dodawania całej biblioteki.

AKTUALIZACJA: Jak zauważył Siergiej w komentarzach, istnieje ograniczenie tej (lub dowolnej) metody, na którą warto zwrócić uwagę. W przypadku liczb takich jak 0,01499999999999999999 nadal będą występować niedokładności, które są wynikiem uderzenia w absolutną krawędź ograniczeń dokładności dla przechowywania wartości zmiennoprzecinkowych. Nie ma matematyki ani innego rozwiązania, które można by zastosować do tego, ponieważ sama wartość jest natychmiast oceniana jako 0,015. Możesz to potwierdzić, po prostu wywołując tę ​​wartość samodzielnie w konsoli. Z powodu tego ograniczenia nie byłoby nawet możliwe użycie manipulacji ciągiem w celu zmniejszenia tej wartości, ponieważ jego reprezentacja ciągu to po prostu „0,015”. Każde rozwiązanie tego wymagałoby logicznego zastosowania u źródła danych przed zaakceptowaniem wartości w skrypcie,

var DecimalPrecision = (function(){
        if (Number.EPSILON === undefined) {
            Number.EPSILON = Math.pow(2, -52);
        }
        this.round = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.round((n + r) * o) / o;
        }
        this.ceil = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.ceil((n + r) * o) / o;
        }
        this.floor = function(n, p=2){
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n < 0)
                o *= -1;
            return Math.floor((n + r) * o) / o;
        }
        return this;
    })();
    console.log(DecimalPrecision.round(1.005));
    console.log(DecimalPrecision.ceil(1.005));
    console.log(DecimalPrecision.floor(1.005));
    console.log(DecimalPrecision.round(1.0049999));
    console.log(DecimalPrecision.ceil(1.0049999));
    console.log(DecimalPrecision.floor(1.0049999));
    console.log(DecimalPrecision.round(2.175495134384,7));
    console.log(DecimalPrecision.round(2.1753543549,8));
    console.log(DecimalPrecision.round(2.1755465135353,4));

KFish
źródło
1
(DecimalPrecision.round (0,014999999999999999, 2)) // zwraca 0,02
Siergiej
Dobry chwyt! Problem polega na przechowywaniu zmiennoprzecinkowym w JS, zawsze będą jakieś przypadki brzegowe. Dobrą wiadomością jest to, że matematyka, którą zastosujesz do Number.EPSILON, może być bardziej precyzyjnie dostrojona, aby wypychać te skrzynki krawędziowe na krawędzi. Jeśli nie chcesz zagwarantować żadnej możliwości dla przypadków krawędziowych, jedynym prawdziwym rozwiązaniem będzie manipulacja ciągiem znaków, a następnie matematyka. W momencie wykonania dowolnego obliczenia matematycznego wartości (nawet przy próbie przesunięcia dziesiętnego), błąd został już wygenerowany.
KFish
W rzeczywistości, przy dalszej kontroli, nie jest to spowodowane żadną matematyką, ale problem pojawia się natychmiast po wywołaniu określonej wartości. Możesz to potwierdzić, po prostu wpisując ten numer w konsoli i przekonując się, że natychmiast zmienia się na 0,015. Dlatego reprezentowałoby to absolutną krawędź dokładności dla dowolnej liczby zmiennoprzecinkowej w JS. W tym przypadku nie można nawet przekonwertować na ciąg znaków i manipulować, ponieważ wartość ciągu będzie wynosić „0,015”
KFish
11

To najprostsze, bardziej eleganckie rozwiązanie (a ja jestem najlepszy na świecie;):

function roundToX(num, X) {    
    return +(Math.round(num + "e+"+X)  + "e-"+X);
}
//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9
Soldeplata Saketos
źródło
4
To dobry sposób na przepisanie zaakceptowanej odpowiedzi, aby zaakceptować argument za pomocą Enotacji.
AxelH,
1
To nie działa w niektórych przypadkach krawędzi: try ( jsfiddle ) roundToX(362.42499999999995, 2). Oczekiwany rezultat (jak w PHP echo round(362.42499999999995, 2)) 362.43. Rzeczywisty wynik:362.42
Dr. Gianluigi Zane Zanettini,
6
IMHO, twój wynik PHP jest zły. Bez względu na to, co nastąpi po trzecim miejscu po przecinku, jeśli trzeci po przecinku jest mniejszy niż 5, to drugi po przecinku powinien pozostać taki sam. To jest matematyczna definicja.
Soldeplata Saketos,
1
Aby być jeszcze bardziej zwięzłym, „e +” może być po prostu „e”.
Lonnie Best