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

2760

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

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

10
1.7777777
9.1

Вывод:

10
1.78
9.1

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

stinkycheeseman
источник
22
Я сделал скрипку со многими из техник, предложенных здесь как решения ... так что вы можете сравнить: fiddle
dsdsdsdsd
37
Кажется, никто не знает об этом Number.EPSILON. Использование Math.round( num * 100 + Number.EPSILON ) / 100.
cronvel
3
Для новых читателей вы не можете сделать это, если у вас нет результата строкового типа . Математические вычисления с плавающей точкой на двоичных внутренних представлениях чисел означают, что всегда есть числа, которые не могут быть представлены как аккуратные десятичные дроби.
Уолф
9
@cronvel Можете ли вы объяснить причину использования Number.EPSILONздесь?
Брюс Сан
5
Я упал в кроличью нору и проверил некоторые наиболее интересные ответы на этой странице (только на первой странице). Вот Codepen . Подсказка: чем больше откликов имеет ответ, тем меньше шансов, что он будет работать правильно.
Адам Ягош

Ответы:

3498

использование Math.round(num * 100) / 100

Изменить: чтобы гарантировать, что такие вещи, как 1.005 раунд правильно, мы используем

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

Брайан Устас
источник
395
Хотя это будет работать в большинстве случаев, оно не будет работать для 1.005, который в итоге окажется 1 вместо 1,01
Джеймс
83
@ Джеймс Вау, это действительно странно - я работаю в консоли разработчика Chrome и замечаю, что 1.005 * 100 = 100.49999999999999. Math.round (100.49999999999999) оценивается в 100, тогда как Math.round (100.5) оценивается в 101. IE9 делает то же самое. Это из-за странности
stinkycheeseman
153
Простой обходной путь. Для 2 дп используйте Math.round((num + 0.00001) * 100) / 100. Попробуйте Math.round((1.005 + 0.00001) * 100) / 100иMath.round((1.0049 + 0.00001) * 100) / 100
mrkschan
31
@mrkschan Почему это работает, и это надежно для всех чисел?
CMCDragonkai
86
Для тех из вас, кто этого не понимает, этот метод называется масштабированием. По сути, ответ здесь состоит в том, чтобы перевести две цифры через десятичную точку, превратив ее в целое число, чтобы избежать всех сумасшедших проблем с плавающей запятой, округлить их, а затем перевести обратно в то, что было раньше, разделив на 100, и вы получите ответ на 2dp.
Alex_Nabu
3064

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

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 - это что-то вроде плавающего 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

ПРИМЕЧАНИЕ: это не универсальное решение для всех. Существует несколько разных алгоритмов округления, ваша реализация может быть разной, в зависимости от ваших требований. https://en.wikipedia.org/wiki/Rounding

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

Кунин
источник
81
Этот подход (toFixed) хорош и работает для меня, но он, в частности, не соответствует первоначальному запросу «только при необходимости». (Округляет 1,5 до 1,50, что нарушает спецификацию.)
Per Lundberg
29
Для требования «когда необходимо» сделайте следующее: parseFloat(number.toFixed(decimalPlaces)); @PerLundberg
Onur Yıldırım
36
parseFloat("55.555").toFixed(2)возвращается "55.55"в консоли разработчика Chrome.
Леви Ботельо
22
Нет никакого преимущества использования toFixed вместо Math.round; toFixed приводит к тем же проблемам округления (попробуйте их с 5.555 и 1.005), но примерно в 500 раз (без шуток) медленнее, чем Math.round ... Похоже, ответ @MarkG здесь более точный.
Пьер
17
toFixed не «иногда» возвращает строку, он всегда возвращает строку.
McGuireV10
464

Ты можешь использовать

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

Я нашел это на MDN . Их путь позволяет избежать проблемы с 1.005, о которой упоминалось .

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57
MarkG
источник
13
@Redsandro, +(val)является принуждением, эквивалентным использованию Number(val). Конкатенация «e-2» с числом привела к появлению строки, которую необходимо преобразовать обратно в число.
Джек,
41
Помните, что для больших и крошечных поплавков, которые будут производить NaN, например, + "1e-21 + 2" не будут проанализированы правильно.
Пьер
16
Вы «решили» проблему «1,005», но представили новую: теперь в консоли Chrome roundToTwo(1.0049999999999999)появляется значение 1,01 (неизбежно с тех пор 1.0049999999999999 == 1.005). Мне кажется, что число с плавающей запятой, которое вы получите, если вы наберете num = 1.005«очевидно», должно округляться до 1,00, потому что точное значение num меньше 1,005. Конечно, мне также кажется, что строка «1.005» «очевидно» должна быть округлена до 1.01. Тот факт, что разные люди, кажется, имеют разные представления о том, каково на самом деле правильное поведение, является частью того, почему это сложно.
Марк Амери
35
Между ними нет числа с плавающей запятой, 1.0049999999999999и 1.005, по определению, это одно и то же число. Это называется dedekind cut.
Азмисов
6
@ Азмисов прав. Пока 1.00499 < 1.005есть true, 1.0049999999999999 < 1.005оценивает до false.
Falconepl
146

Ответ 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');
  });
})
Lavamantis
источник
20
Пьер поднял серьезную проблему с ответом MarkG.
dsjoerg
9
Примечание: если вы не хотите изменять прототип Number.pro - просто напишите это как функцию: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); }
Филипп Ципман
2
Классный способ расширить это. Вы можете проверить, является ли десятичное число отрицательным, затем инвертировать e + и e-. Тогда n = 115; n.round (-2); будет производить 100
Ли Лувьер
3
Попробуйте n: 1e + 19 - он возвращает NaN
DavidJ
3
Этот алгоритм всегда округляется (вместо нуля). Таким образом, в случае отрицательных чисел результат будет не таким, как вы могли ожидать:(-1.005).round(2) === -1
Алексей Комаров
124

Вы должны использовать:

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

Кажется, никто не знает Number.EPSILON.

Также стоит отметить, что это не странность JavaScript, как утверждают некоторые люди.

Это просто способ, которым числа с плавающей запятой работают в компьютере. Как и в 99% языков программирования, JavaScript не имеет самодельных чисел с плавающей точкой; для этого он использует CPU / FPU. Компьютер использует двоичный код, а в двоичном коде нет таких чисел, как0.1 , а просто двоичное приближение для этого. Почему? По той же причине, что 1/3 нельзя записать в десятичном виде: его значение равно 0,33333333 ... с бесконечностью троек.

Вот иди Number.EPSILON. Это число представляет собой разницу между 1 и следующим числом, существующим в числах с плавающей запятой двойной точности. Вот и все: между 11 и + нет номера Number.EPSILON.

РЕДАКТИРОВАТЬ:

Как просили в комментариях, давайте уточним одну вещь: добавление Number.EPSILON имеет значение только тогда, когда значение округления является результатом арифметической операции, поскольку оно может поглотить некоторую дельту ошибки с плавающей запятой.

Это не полезно, когда значение исходит из прямого источника (например, литерал, пользовательский ввод или датчик).

РЕДАКТИРОВАТЬ (2019):

Как @maganap и некоторые люди указали, лучше добавить Number.EPSILONперед умножением:

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

РЕДАКТИРОВАТЬ (декабрь 2019 года):

В последнее время я использую функцию, похожую на эту, для сравнения чисел с учетом 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 ;
}

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

Фактически, в коде, который я использую, ESPILON_RATE = 1 + 4 * Number.EPSILONи EPSILON_ZERO = 4 * Number.MIN_VALUE(в четыре раза больше эпсилона), потому что я хочу, чтобы средство проверки равенства было достаточно свободным для накопления ошибки с плавающей запятой.

Пока это выглядит идеально для меня. Надеюсь, это поможет.

cronvel
источник
1
@palota, вы могли бы определить действительно число зубьев, как упомянул cronvel EPSILON
Daniel San
3
Это правильный ответ! Сама проблема связана с тем, как числа с плавающей запятой работают внутри, а не с javascript
Даниэль Сан,
22
На самом деле, вы должны добавить эпсилон ПЕРЕД умножением на 100 Math.round( (num + Number.EPSILON) * 100) / 100. Я согласен также, что это правильный метод для правильного округления (хотя это не совсем то, что было задано в этом вопросе).
maganap
2
@cronvel Хм. Вы правы; выбор направления имеет смысл. Поэтому я думаю, что мое оставшееся возражение состоит в том, что делать это имеет смысл только в том случае, если вы работаете в области, где у вас есть принципиальная причина думать, что ваше значение является «круглым числом». Если вы знаете, что ваш ввод является результатом простых арифметических операций над числами с небольшим числом десятичных разрядов, то, конечно, вы, вероятно, имеете 0.004999999999999999в результате только сложную ошибку с плавающей запятой, и математически правильный результат был, вероятно, 0,005. Если это чтение с датчика? Не так много.
Марк Амери
1
@marchaos Он не подведет: половинки всегда округляется вверх . Например, Math.round(1.5)= 2, но Math.round(-1.5)= -1. Так что это совершенно соответствует. Здесь -1 больше -2, как -1000 больше -1000.01. Не путать с большими абсолютными числами.
cronvel
84

Можно использовать .toFixed(NumberOfDecimalPlaces).

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
Гурав Сингла
источник
4
Кроме того, потому что он добавляет конечные нули, а это не то, о чем просил первоначальный вопрос .
Аластер Мо
2
Но конечные нули легко удаляются с помощью регулярного выражения, то есть `Number (10.10000.toFixed (2) .replace (/ 0 + $ /, ''))` => 10,1
Чад,
1
@daniel верхний ответ такой же (как и сейчас), но он не всегда корректно, попробуйте, +(1.005).toFixed(2)который возвращает1 вместо 1.01.
Эмиль Бержерон
3
@ChadMcElligott: Ваше регулярное выражение плохо работает с целыми числами: Number(9).toFixed(2).replace(/0+$/, '')=> "9".
Джейкоб ван Линген
Кажется, это не округляется. Это просто усекает в основном. В некоторых случаях достаточно хорошо! Спасибо.
Iedmrc
78

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

Предположим, у нас есть функция, roundTo2DP(num)которая принимает в качестве аргумента значение с плавающей точкой и возвращает значение, округленное до 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 ее невозможно реализовать, чтобы дать эти очевидные ответы, поскольку все три переданных ей числа - это одно и то же число . Двоичные числа с плавающей точкой 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 , цифры которого будут использоваться в строковом представлении m :

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

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

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

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

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

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

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

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

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

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

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

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

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

Здесь мы можем просто использовать встроенный toFixedметод. Обратите внимание, что при вызове Number()String, возвращаемого функцией toFixed, мы получаем число, чье представление 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://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);
}
Марк Эмери
источник
Это не работает в некоторых крайних случаях: try ( jsfiddle ) с roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2). Ожидаемый результат (как в PHP echo round(362.42499999999995, 2)) 362.43. Фактический результат:362.42
Доктор Джанлуиджи Зане Занеттини
1
@ Dr.GianluigiZaneZanettini Да. Weird. Я не уверен, почему PHP roundдает 362,43. Это кажется интуитивно неправильным, поскольку 362.42499999999995 меньше 362,425 (в математике и в коде - 362.42499999999995 < 362.425верно как для JS, так и для PHP). Ответ PHP также не минимизирует расстояние между исходным и округленным числами с плавающей точкой, поскольку 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42. Согласно php.net/manual/en/function.round.php , PHP roundсоответствует стандарту C99; Я должен рискнуть в страну C, чтобы понять, что происходит.
Марк Амери
1
Посмотрев на реализацию округления в исходном коде PHP , я понятия не имею, что он делает. Это ужасно сложная реализация, полная макросов и ветвей, а также преобразований строк и ветвлений с жестко закодированными значениями магической точности. Я не уверен, что сказать, кроме того, что «ответ PHP явно неправильный, и мы должны подать отчет об ошибке». Как вы нашли номер 362.42499999999995, кстати?
Марк Амери
1
@ Dr.GianluigiZaneZanettini Я создал отчет об ошибке: bugs.php.net/bug.php?id=75644
Марк Эмери
2
@ Dr.GianluigiZaneZanettini " Есть ли смысл?" - нет; это не то, как работает округление. 1.000005 заканчивается на пять, но если округлено до ближайшего целого числа, ответ должен быть 1, а не 2. Аналогично, 1499995 заканчивается на пять, но если округлено до ближайшего миллиона, результат должен быть 1000000, а не 2000000. В случае 362.42499999999995 при округлении до 2 DP, направление округления должно быть третьим десятичным
Марк Amery
76

Рассмотрим .toFixed()и .toPrecision():

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

AceCorban
источник
1
toFixed добавляет десятичные точки к каждому значению независимо от того, что.
stinkycheeseman
13
Оба здесь бесполезны
Esailija
К сожалению, обе функции добавят дополнительные десятичные разряды, которые @stinkycheeseman, по-видимому, не хочет.
Jackwanders
2
они возвращают строки, а не числа, поэтому они форматируют, а не вычисляют ..
saimiris_devel
63

Точный метод округления. Источник: Мозилла

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

Примеры:

// 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
пользователь
источник
Кто-то поместил это и на GitHub, и на npm: github.com/jhohlfeld/round10
Джо Лисс
Нет, Math.round10(3544.5249, -2)возвращается 3544,52 вместо 3544,53
Матия
2
@Matija Wolfram alpha тоже говорит 3544,52. Вы хотите минимизировать ошибку между текущим числом и округленным приближением. Ближайшее приближение 3544.5249к двум десятичным разрядам 3544.52(ошибка = 0,0049). Если это так 3544.53, ошибка будет 0,0051. Вы выполняете последовательное округление, то есть Math.round10 (Math.round10 (3544.5249, -3), -2), которое дает большую ошибку округления и, следовательно, нежелательно.
Пользователь
3
@Matija, с математической точки зрения, округлено 3544,5249 - это 3544,52, а не 3544,53, поэтому этот код правильный. Если вы хотите, чтобы это округлилось до 3544,53 в этом и подобных случаях (даже если это неправильно), сделайте что-то вроде этого:number += 0.00011
Божидар Сиканджик
@Matija: Я думаю, что функция работает как надо. Может быть, вы хотите Math.round10( Math.round10(3544.5249, -3) , -2)
повторить
60

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

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

Math.ceil(num * 100)/100;
machineaddict
источник
15
Пример ввода и вывода показывает, что, хотя в вопросе говорилось «округлить вверх ...», на самом деле он был задуман как «округлить до…».
JayDM
2
@stinkycheeseman указывал на ошибку в конкретном случае, он не хотел всегда округлять, как это делает ceil, он просто хотел, чтобы 0,005
округлилось
9
Обнаружил странную ошибку при тестировании Math.ceil(1.1 * 100)/100;- он возвращает 1.11, потому что 1.1 * 100 110.00000000000001соответствует новым современным браузерам Firefox, Chrome, Safari и Opera ... IE по-прежнему думает 1.1*100=1100.
skobaljic
1
@skobaljic попробуйтеMath.ceil(num.toFixed(4) * 100) / 100
treeface
1
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100также вернется 1.11в Firefox, проблема / ошибка современных браузеров связана с умножением, и люди должны знать об этом (например, в то время я работал над лотереей).
skobaljic
47

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

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);
}
JayDM
источник
2
Это решение является неправильным, см stackoverflow.com/questions/38322372/... , если вы вводите 156893,145 и вокруг него с вышеописанной функции вы получаете 156893.14 вместо 156893.15 !!!
saimiris_devel
2
@saimiris_devel, вам удалось найти случай, когда числовое представление чисел в JavaScript не работает. Вы правы, что он округляется неправильно. Но это потому, что ваш номер выборки, умноженный на 100, уже сломан (15689314.499999998). Действительно, для полнофункционального ответа потребуется библиотека, специально разработанная для учета несоответствий в обработке реальных чисел в JavaScript. В противном случае вы могли бы сделать недействительным любой из ответов, которые были даны на этот вопрос.
JayDM
36
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12
user3711536
источник
1
Это не всегда даст те же результаты, которые вы получили бы, если бы вы взяли строковое представление вашего числа и округлили его. Например, +(0.015).toFixed(2) == 0.01.
Марк Амери
35

Для меня Math.round () не давал правильного ответа. Я нашел toFixed (2) работает лучше. Ниже приведены примеры обоих:

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

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

Викасдип Сингх
источник
Важно отметить, что toFixed не выполняет округление, а Math.round просто округляет до ближайшего целого числа. Поэтому, чтобы сохранить десятичные дроби, нам нужно умножить исходное число на количество степеней десяти, нули которых представляют желаемое количество десятичных знаков, а затем разделить результат на то же число. В вашем случае: Math.round (43000/80000 * 100 * 100) / 100. Наконец, toFixed (2) может быть применен для обеспечения того, чтобы в результате всегда было два знака после запятой (с конечными нулями, где это необходимо) - идеально для выравнивания правого ряда чисел, представленных вертикально :)
Turbo
7
Также важно отметить, что .toFIxed () выводит строку, а не число.
carpiediem
2
Это все еще не решает округление для 1.005. (1.005).toFixed(2)все еще приводит к 1.00.
DPac
34

Используйте эту функцию Number(x).toFixed(2);

Harish.bazee
источник
8
Оберните все это Numberснова, если вы не хотите, чтобы оно возвращалось в виде строки:Number(Number(x).toFixed(2));
5
NumberВызов не нужно, x.toFixed(2)работает.
bgusach
3
@bgusach Требуется номер вызова, так как оператор x.toFixed (2) возвращает строку, а не число. Чтобы снова преобразовать в число, нам нужно
Мохан Рам
2
При использовании этого метода (1).toFixed(2)возвращается 1.00, но спрашивающий нужен 1в этом случае.
Евгений Мала
1
Это не работает, 1.005.toFixed(2)дает, "1"когда это должно быть "1.01".
Адам Ягош
33

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

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

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

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');
мимон
источник
3
Метод toFixed возвращает строку. Если вы хотите получить числовой результат, вам нужно отправить результат toFixed в parseFloat.
Замбонилли
@Zambonilli Или просто умножьте на 1, если это необходимо. но поскольку фиксированное число в большинстве случаев предназначено для отображения, а не для расчета, правильный формат
строка Мимон
2
-1; не только было toFixedпредложено несколько ответов за несколько лет до вашего, но он не удовлетворяет условию «только при необходимости» в вопросе; (1).toFixed(2)дает, "1.00"где пожелал аскер "1".
Марк Амери
Хорошо понял. Я добавляю решение и для этого случая
Pery Mimon
Если вы используете lodash, это еще проще: _.round (number, decimalPlace) Удален мой последний комментарий, потому что у него есть проблема. Lodash _.round работает, хотя. 1,005 с десятичным разрядом 2 преобразует в 1,01.
Девин Филдс
32

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

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

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222
petermeissner
источник
Кто-нибудь знает, есть ли разница между этим и return Number(x.toFixed(digits))?
1
@JoeRocc ... не должно иметь никакого значения, насколько я могу видеть, так как .toFixed()в любом случае допускает только числа.
petermeissner
4
Этот ответ имеет ту же проблему, что упоминалась несколько раз на этой странице. Попробуйте round(1.005, 2)увидеть результат 1вместо 1.01.
MilConDoin
кажется больше проблема алгоритма округления? - их больше, чем можно себе представить: en.wikipedia.org/wiki/Rounding ... round(0.995, 2) => 0.99; round(1.006, 2) => 1.01; round(1.005, 2) => 1
petermeissner
31

Есть несколько способов сделать это. Для таких, как я, вариант Лодаша

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

Применение:

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

Если ваш проект использует jQuery или lodash, вы также можете найти подходящий roundметод в библиотеках.

Обновление 1

Я удалил вариант n.toFixed(2), потому что это не правильно. Спасибо @ avalanche1

stanleyxu2005
источник
Второй вариант вернет строку с двумя точками после запятой. Вопрос требует десятичных знаков только в случае необходимости. Первый вариант лучше в этом случае.
Маркос Лима
@MarcosLima Number.toFixed()вернет строку, но с символом плюс перед ней, интерпретатор JS преобразует строку в число. Это синтаксис сахара.
stanleyxu2005
На Firefox alert((+1234).toFixed(2))показывает "1234.00".
Маркос Лима
На Firefox alert(+1234.toFixed(2))кидает SyntaxError: identifier starts immediately after numeric literal. Я придерживаюсь 1-го варианта.
Маркос Лима
Это не работает в некоторых крайних случаях: try ( jsfiddle ) с 362.42499999999995. Ожидаемый результат (как в PHP echo round(362.42499999999995, 2)) 362.43. Фактический результат:362.42
Доктор Джанлуиджи Зане Занеттини
26

Если вы используете библиотеку lodash, вы можете использовать метод lodash round, как показано ниже.

_.round(number, precision)

Например:

_.round(1.7777777, 2) = 1.78
Мадура Прадип
источник
@Peter Набор функций, которые предоставляет Lodash, действительно хорош по сравнению со стандартным Javascript. Однако я слышал, что у Lodash есть некоторые проблемы с производительностью по сравнению со стандартным JS. codeburst.io/…
Мадура Прадип
1
Я согласен с вашей точкой зрения, что при использовании lodash существуют недостатки в производительности. Я думаю, что эти проблемы являются общими для многих абстракций. Но посмотрите, сколько ответов есть в этой теме, и как интуитивные решения не подходят для крайних случаев. Мы видели этот шаблон с jQuery, и проблема с корнем была решена, когда браузеры приняли общий стандарт, который решал большинство наших сценариев использования. Затем узкие места в производительности были перенесены в браузерные движки. Я думаю, то же самое должно случиться с Лодашем. :)
Питер
26

Начиная с ES6, существует «правильный» способ (без переопределения статики и создания обходных путей) сделать это с помощью 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));

тогда вы можете просто parseFloatи нули «уйдут».

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

Однако это не решает «проблему округления 1.005», поскольку это присуще тому, как обрабатываются дробные дроби .

console.log(1.005 - 0.005);

Если вы открыты для библиотек, вы можете использовать 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>

Матас Вайткявичюс
источник
3
(1.005).toPrecision(3)все еще возвращается 1.00вместо 1.01фактически.
Джакомо
toPrecisionвозвращает строку, которая изменяет желаемый тип вывода.
adamduren
@Giacomo Это не недостаток .toPrecisionметода, это специфика чисел с плавающей точкой (какие числа в JS) - попробуйте 1.005 - 0.005, он вернется 0.9999999999999999.
Шау-котэ
1
(1).toPrecision(3)возвращает «1,00», но спрашивающий хотел иметь 1в этом случае.
Евгений Мала
1
Как сказал @Giacomo, этот ответ, кажется, смешивает «значащие цифры» с «округлением до десятичных знаков». toPrecisionделает формат, а не последний, и не является ответом на вопрос ОП, хотя на первый взгляд может показаться уместным, он получает много ошибок. См. En.wikipedia.org/wiki/Significant_figures . Например, Number(123.4).toPrecision(2)возвращается "1.2e+2"и Number(12.345).toPrecision(2)возвращается "12". Я также согласен с тем, что @ adamduren возвращает строку, которая нежелательна (не большая проблема, но нежелательная).
Neek
23

MarkG и Lavamantis предложили гораздо лучшее решение, чем то, которое было принято. Жаль, что они не получают больше голосов!

Вот функция, которую я использую для решения проблем десятичных чисел с плавающей запятой, также основанных на MDN . Это даже более универсально (но менее кратко), чем решение Lavamantis:

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

Используйте это с:

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

По сравнению с решением Lavamantis, мы можем сделать ...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123
astorije
источник
2
Ваше решение не распространяется на некоторые случаи, в отличие от решения MDN. Хотя это может быть короче, это не точно ...
astorije
1
раунд (-1835.665,2) => -1835.66
Хорхе Сампайо
21

Это может помочь вам:

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

Для получения дополнительной информации, вы можете посмотреть по этой ссылке

Math.round (num) против num.toFixed (0) и несоответствия браузера

Тоттен
источник
1
Почему в мире принятый ответ имеет гораздо больше голосов, чем этот, поскольку они практически одинаковы, но этот был опубликован через 1 минуту после принятого?
Цитата Дэйв
18

Самый простой подход - использовать toFixed, а затем удалить конечные нули с помощью функции Number:

const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
Марцин Ванаго
источник
это не работает для всех случаев. сделать обширные тесты, прежде чем отправлять ответы.
Бабурао
@baburao Пожалуйста, опубликуйте случай, в котором вышеупомянутое решение не работает
Marcin Wanago
постоянное число = 15; Число (Number.toFixed (2)); //15.00 вместо 15
Кевин Джангиани
1
@KevinJhangiani const number = 15; Число (Number.toFixed (2)); // 15 - Я тестировал его как на новейшем Chrome, так и на Firefox
Марцин Ванаго
@KevinJhangiani как ты получаешь 15.00? Числа в JS не хранят десятичные разряды, и любое отображение автоматически усекает лишние десятичные разряды (любые нули в конце).
ВЛАЗ
16
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2) здесь 2 - число цифр, до которого мы хотим округлить это число.

Ритеш Дхури
источник
это .toFixed () проще в реализации. просто пройди через это один раз.
Ритеш Дхури
14

Самый простой способ:

+num.toFixed(2)

Он преобразует его в строку, а затем обратно в целое число / число с плавающей точкой.

bigpotato
источник
Спасибо за этот самый простой ответ. Однако что такое «+» в + num? Это не сработало для меня, когда десятичный вал пришел в строке. Я сделал: (num * 1). ToFixed (2).
Итан
@momo просто измените аргумент toFixed()на 3. Так и будет +num.toFixed(3). Это работает так, как должно, 1.005 округляется до 1.00, что равно 1
bigpotato
1
@ Edmund Он должен вернуть 1,01, а не 1,00
ммм
13

Вот метод-прототип:

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
источник
13

Используйте что-то вроде этого "parseFloat (parseFloat (value) .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
источник
1
нет, если неточность присуща представлению с плавающей точкой. Вы просто удалили бы его, а затем снова повторили ту же ошибку, снова преобразовав ее в float!
Бен Макинтайр
12

Один из способов добиться такого округления только при необходимости - использовать Number.prototype.toLocaleString () :

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

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

Javarome
источник
Это самое чистое решение, которое существует на сегодняшний день и позволяет обойти все сложные проблемы с плавающей запятой, но поддержка для каждого MDN все еще не завершена - Safari пока не поддерживает передачу аргументов toLocaleString.
Марк Амери
@MarkAmery На данный момент некоторые проблемы есть только в браузере Android: caniuse.com/#search=toLocaleString
ptyskju
12

После прохождения различных итераций всех возможных способов достижения по-настоящему точной точности округления десятичных чисел становится ясно, что наиболее точным и эффективным решением является использование Number.EPSILON. Это обеспечивает истинное математическое решение проблемы математической точности с плавающей запятой. Он может быть легко заполнен, как показано здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON для поддержки всех последних оставшихся пользователей IE (тогда, опять же, возможно, мы должен прекратить делать это).

Адаптировано из решения, представленного здесь: https://stackoverflow.com/a/48850944/6910392

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

ОБНОВЛЕНИЕ: Как отметил Сергей в комментариях, существует ограничение на этот (или любой) метод, на который стоит обратить внимание. В случае чисел, таких как 0,014999999999999999, вы все равно будете испытывать неточности, которые являются результатом достижения абсолютного предела ограничений точности для хранения значений с плавающей запятой. Не существует математического или другого решения, которое можно было бы применить для учета этого, поскольку само значение немедленно оценивается как 0,015. Вы можете подтвердить это, просто вызвав это значение в консоли. Из-за этого ограничения было бы невозможно даже использовать манипуляции со строками для уменьшения этого значения, поскольку его строковое представление просто «0,015». Любое решение для учета этого должно быть применено логически в источнике данных, прежде чем принимать значение в сценарии,

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
источник
1
(DecimalPrecision.round (0.014999999999999999, 2)) // возвращает 0,02
Сергей
Хороший улов! Проблема с хранилищем с плавающей точкой в ​​JS, всегда будут некоторые крайние случаи. Хорошей новостью является то, что математика, которую вы сначала примените к Number.EPSILON, может быть более точно настроена, чтобы продвинуть эти крайние случаи дальше на край. Если вы хотите гарантировать отсутствие возможности для крайних случаев, единственным реальным решением будет манипулирование строками, а затем математика. В тот момент, когда вы выполняете какое-либо математическое вычисление значения (даже пытаясь переместить десятичную дробь), вы уже создали ошибку.
KFish
На самом деле, при дальнейшей проверке, это не из-за какой-либо математики, а скорее проблема проявляется сразу после вызова указанного значения. Вы можете подтвердить это, просто набрав это число в консоли и увидев, что оно сразу оценивается в 0,015. Следовательно, это будет представлять абсолютный предел точности для любого числа с плавающей запятой в JS. В этом случае вы даже не можете конвертировать в строку и манипулировать, поскольку значение строки будет «0,015»
KFish
11

Это самое простое, более элегантное решение (а я лучший в мире;):

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
Солдеплата Сакетос
источник
4
Это хороший способ переписать принятый ответ, чтобы принять аргумент, используя Eнотацию.
AxelH
1
Это не работает в некоторых крайних случаях: try ( jsfiddle ) roundToX(362.42499999999995, 2). Ожидаемый результат (как в PHP echo round(362.42499999999995, 2)) 362.43. Фактический результат:362.42
Доктор Джанлуиджи Зане Дзанеттини
6
ИМХО, ваш PHP-результат неверен. Независимо от того, что идет после третьего десятичного знака, если третье десятичное число меньше 5, то второе десятичное число должно оставаться неизменным. Это математическое определение.
Солдеплата Сакетос
1
Чтобы быть еще более кратким, «е +» может быть просто «е» вместо этого.
Лонни Бест