[Javascript] Раунд до не более 2 знаков после запятой (только при необходимости)


Answers

Если значение является типом текста:

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

Если значение - это число:

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

Существует недостаток, что значения, такие как 1,5, будут давать «1,50» в качестве выхода. Исправление, предложенное @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.

Кажется, Math.round - лучшее решение. Но это не так! В некоторых случаях он НЕ будет округлять правильно:

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

toFixed () также не будет корректно проверяться в некоторых случаях (проверено в Chrome v.55.0.2883.87)!

Примеры:

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.

Я думаю, это потому, что 1.555 на самом деле что-то вроде float 1.55499994 за кулисами.

Решение 1 - использовать скрипт с требуемым алгоритмом округления, например:

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

Решение 2 заключается в том, чтобы избежать вычислений переднего конца и вытащить округленные значения с серверного сервера.

Question

Я хотел бы округлить максимум 2 десятичных знака, но только при необходимости .

Входные данные:

10
1.7777777
9.1

Вывод:

10
1.78
9.1

Как я могу сделать это в JavaScript?




Этот вопрос сложный.

Предположим, что у нас есть функция roundTo2DP(num) , которая принимает float в качестве аргумента и возвращает значение, округленное до 2 десятичных знаков. На что должно оцениваться каждое из этих выражений?

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

«Очевидным» ответом является то, что первый пример должен округлить до 0,01 (потому что он ближе к 0,01, чем к 0,02), в то время как остальные два должны округляться до 0,02 (поскольку 0.0150000000000000001 ближе к 0,02, чем к 0,01, а потому, что 0,015 находится на полпути между их и существует математическое соглашение о том, что такие числа округляются).

roundTo2DP , о которой вы, возможно, догадались, заключается в том, что roundTo2DP не может быть реализован, чтобы дать эти очевидные ответы, потому что все три числа, переданные ему, имеют одинаковое число . Бинарные числа с плавающей запятой IEEE 754 (тип, используемый JavaScript) не могут точно представлять большинство нецелочисленных чисел, поэтому все три числовых литерала выше округляются до близкого действительного числа с плавающей запятой. Это число, как это происходит, точно

0,01499999999999999944488848768742172978818416595458984375

который ближе к 0,01, чем к 0,02.

Вы можете видеть, что все три номера одинаковы на вашей консоли браузера, оболочке Node или другом интерпретаторе JavaScript. Просто сравните их:

> 0.014999999999999999 === 0.0150000000000000001
true

Поэтому, когда я пишу m = 0.0150000000000000001 , точное значение m которое я заканчиваю, ближе к 0.01 чем к 0.02 . И все же, если я преобразую m в строку ...

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

... Я получаю 0,015, который должен округляться до 0,02, и это заметно не 56-значное число, которое я ранее говорил, что все эти цифры были в точности равны. Какая темная магия?

Ответ можно найти в спецификации ECMAScript, в разделе 7.1.12.1: ToString применяется к типу Number . Здесь устанавливаются правила преобразования некоторого числа m в строку. Ключевой частью является точка 5, в которой генерируется целое число s , цифры которого будут использоваться в представлении String m :

пусть n , k и s - целые числа, такие, что k ≥ 1, 10 k -1s <10 k , Числовое значение для s × 10 n - k равно m , а k как можно меньше. Заметим, что k - это число цифр в десятичном представлении s , что s не делится на 10 и что наименее значимая цифра s не обязательно однозначно определяется этими критериями.

Ключевой частью здесь является требование, чтобы « k было как можно меньше». Это требование является требованием, чтобы при заданном Number m значение String(m) должно иметь наименьшее возможное количество цифр, при этом все еще удовлетворяющее требованию, чтобы Number(String(m)) === m . Поскольку мы уже знаем, что 0.015 === 0.0150000000000000001 , теперь понятно, почему String(0.0150000000000000001) === '0.015' должно быть правдой.

Разумеется, ни одно из этих обсуждений не ответило напрямую на вопрос roundTo2DP(m) . Если точное значение m - 0,0149999999999999999944488848768742172978818416595458984375, но его строковое представление «0,015», то каков правильный ответ - математически, практически, философски или что-то еще - когда мы округлим его до двух знаков после запятой?

На это нет единого правильного ответа. Это зависит от вашего варианта использования. Вы, вероятно, хотите уважать представление String и кругом вверх, когда:

  • Представленная стоимость по своей природе является дискретной, например, валютой в валюте с тремя знаками после запятой, например, динарами. В этом случае истинное значение числа, такого как 0.015, равно 0,015, а представление 0.0149999999 ..., которое оно получает в двоичной с плавающей запятой, является ошибкой округления. (Конечно, многие будут рассуждать разумно, что вы должны использовать десятичную библиотеку для обработки таких значений и никогда не представлять их как двоичные числа с плавающей запятой в первую очередь.)
  • Значение было введено пользователем. В этом случае снова введенное точное десятичное число больше «истинно», чем ближайшее двоичное представление с плавающей запятой.

С другой стороны, вы, вероятно, хотите уважать двоичное значение с плавающей запятой и округлить вниз, когда ваше значение происходит из непрерывного масштаба - например, если это считывание с датчика.

Эти два подхода требуют разного кода. Чтобы уважать строковое представление числа, мы можем (с довольно небольшим количеством разумно тонкого кода) реализовать наше собственное округление, которое действует непосредственно на строковое представление, по цифре, используя тот же алгоритм, который вы использовали бы в школе, когда вы учили, как округлить числа. Ниже приведен пример, который соблюдает требование OP о представлении числа до двух знаков после запятой «только при необходимости» путем удаления конечных нулей после десятичной точки; вы, конечно, можете настроить его на точные нужды.

/**
 * 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://.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+$/, '')
}

Пример использования:

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

Вышеупомянутая функция, вероятно, вы хотите использовать, чтобы пользователи никогда не видели числа, которые они ввели, округленные неправильно.

(В качестве альтернативы вы также можете попробовать библиотеку github.com/jhohlfeld/round10 которая обеспечивает аналогичную работу с совершенно другой реализацией).

Но что, если у вас есть второй вид Number - значение, взятое из непрерывного масштаба, где нет оснований полагать, что приблизительные десятичные представления с меньшим количеством знаков после запятой более точны, чем те, у кого больше? В этом случае мы не хотим уважать представление String, потому что это представление (как поясняется в спецификации) уже имеет округлую форму; мы не хотим ошибаться, говоря «0.014999999 ... 375 раундов до 0,015, которые округляются до 0,02, поэтому 0,014999999 ... 375 раундов до 0,02».

Здесь мы можем просто использовать встроенный метод toFixed . Обратите внимание, что, вызывая Number() в String, возвращаемом toFixed , мы получаем Number, представление String которого не имеет конечных нулей (благодаря тому, как JavaScript вычисляет строковое представление числа, обсуждавшегося ранее в этом ответе).

/**
 * 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://.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);
}






To not deal with many 0s, use this variant:

Math.round(num * 1e2) / 1e2



I'll add one more approach to this.

number = 16.6666666;
console.log(parseFloat(number.toFixed(2)));
"16.67"

number = 16.6;
console.log(parseFloat(number.toFixed(2)));
"16.6"

number = 16;
console.log(parseFloat(number.toFixed(2)));
"16"

.toFixed(2) returns a string with exactily 2 decimal points, that may or may not be trailing zeros. Doing a parseFloat() will eliminate those trailing zeros.










Here is a prototype method:

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

var yournum = 10.55555;
yournum = yournum.round(2);



MarkG and Lavamantis offered a much better solution than the one that has been accepted. It's a shame they don't get more upvotes!

Here is the function I use to solve the floating point decimals issues also based on MDN . It is even more generic (but less concise) than Lavamantis's solution:

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

Use it with:

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

Compared to Lavamantis's solution, we can do...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123



2017
Просто используйте собственный код .toFixed()

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

Если вам нужно быть строгим и добавлять цифры, то при необходимости он может replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');



Попробуйте это легкое решение:

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

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222



Вот простой способ сделать это:

Math.round(value * 100) / 100

Возможно, вы захотите сделать еще одну функцию, чтобы сделать это для вас:

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

Тогда вы просто передадите значение.

Вы можете увеличить его до округления до любого произвольного числа десятичных знаков, добавив второй параметр.

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

    return (Math.round(value * multiplier) / multiplier);
}



Ни один из найденных здесь ответов не является правильным . @stinkycheeseman попросил округлить , вы все округлили число.

Чтобы округлить, используйте это:

Math.ceil(num * 100)/100;



A simpler ES6 way is

const round = (x, n) => 
  parseFloat(Math.round(x * Math.pow(10, n)) / Math.pow(10, n)).toFixed(n);

This pattern also returns the precision asked for.

например:

round(44.7826456, 4)  // yields 44.7826
round(78.12, 4)       // yields 78.1200



Ответ MarkG правильный. Вот общее расширение для любого количества десятичных знаков.

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

Применение:

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

Модульный тест:

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