Обрезать (не округлить) десятичные числа в javascript


93

Я пытаюсь усечь десятичные числа до десятичных знаков. Что-то вроде этого:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)делает примерно то, что нужно, но округляет значение. Мне не нужно округлять значение. Надеюсь, это возможно в javascript.


6
jQuery - это просто фреймворк, и ваша проблема не связана с jQuery. Это больше касается выполнения некоторых базовых вычислений в JavaScript. Надеюсь, вас тоже устраивает решение, отличное от jQuery.
Феликс Клинг,

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

Ответы:


51

UPD :

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

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

[   5.467.toFixedDown(2),
    985.943.toFixedDown(2),
    17.56.toFixedDown(2),
    (0).toFixedDown(1),
    1.11.toFixedDown(1) + 22];

// [5.46, 985.94, 17.56, 0, 23.1]

Старое подверженное ошибкам решение, основанное на компиляции чужих:

Number.prototype.toFixedDown = function(digits) {
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

4
Да, прототипы не работают в кросс-браузере. Вместо того, чтобы определять эту (ограниченную цель) функцию через систему типов таким образом, чтобы она не работала надежно, почему бы просто не поместить ее в библиотеку.
Thomas W

3
Это не работает как исключение. Попробуйте ввести число 17,56 и цифры = 2. Оно должно быть 17,56, но эта функция возвращает 17,55.
shendz

2
Две несоответствия с этой функцией: эта функция возвращает строку, которая 1.11.toFixedDown(1) + 22заканчивается как 1.122вместо 23.1. Также 0.toFixedDown(1)должен производить, 0но вместо этого производит -0.1.
Ник Ноулсон,

5
Обратите внимание, что эта функция удаляет отрицательный знак. Пример: (-10.2131).toFixedDown(2) // ==> 10.21.
rgajrawala

4
Также (1e-7).toFixedDown(0) // ==> 1e-7. Есть ли что 1e-(>=7)(например: 1e-8, 1e-9, ...).
rgajrawala

60

Ответ Догберта хорош, но если ваш код может иметь дело с отрицательными числами, Math.floorсам по себе может дать неожиданные результаты.

Например Math.floor(4.3) = 4, ноMath.floor(-4.3) = -5

Вместо этого используйте вспомогательную функцию, подобную этой, чтобы получить согласованные результаты:

truncateDecimals = function (number) {
    return Math[number < 0 ? 'ceil' : 'floor'](number);
};

// Applied to Dogbert's answer:
var a = 5.467;
var truncated = truncateDecimals(a * 100) / 100; // = 5.46

Вот более удобный вариант этой функции:

truncateDecimals = function (number, digits) {
    var multiplier = Math.pow(10, digits),
        adjustedNum = number * multiplier,
        truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);

    return truncatedNum / multiplier;
};

// Usage:
var a = 5.467;
var truncated = truncateDecimals(a, 2); // = 5.46

// Negative digits:
var b = 4235.24;
var truncated = truncateDecimals(b, -2); // = 4200

Если это нежелательное поведение, вставьте вызов Math.absв первую строку:

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

РЕДАКТИРОВАТЬ: shendz правильно указывает, что использование этого решения a = 17.56будет неправильно производить 17.55. Чтобы узнать больше о том, почему это происходит, прочтите « Что должен знать каждый компьютерный ученый об арифметике с плавающей запятой». . К сожалению, написать решение, устраняющее все источники ошибок с плавающей запятой, с javascript довольно сложно. На другом языке вы бы использовали целые числа или, возможно, десятичный тип, но с javascript ...

Это решение должно быть на 100% точным, но оно будет и медленнее:

function truncateDecimals (num, digits) {
    var numS = num.toString(),
        decPos = numS.indexOf('.'),
        substrLength = decPos == -1 ? numS.length : 1 + decPos + digits,
        trimmedResult = numS.substr(0, substrLength),
        finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    return parseFloat(finalResult);
}

Для тех, кому нужна скорость, но при этом нужно избегать ошибок с плавающей запятой, попробуйте что-нибудь вроде BigDecimal.js . Вы можете найти другие библиотеки BigDecimal для javascript в этом вопросе SO: «Есть ли хорошая библиотека BigDecimal для Javascript?» и вот хороший пост в блоге о математических библиотеках для Javascript


Почему неожиданно? Изменение направления округления, когда вы опускаетесь ниже 0, вызывает всевозможные арифметические артефакты и дрянную математику. Например, вдвое больше чисел будет округлено до 0, чем любое другое целое число. Для графики, бухгалтерского учета и многих других целей вы получите дрянные результаты. По правде говоря, было бы сложнее сказать, для чего ваше предложение полезно, чем сказать, что это не так .
Thomas W

Это хорошо для того, что написано - когда вы хотите обрезать десятичные дроби, а не округлять.
Ник Ноулсон,

1
Не собираюсь работать с 17,56, потому что браузер дает 17,56 * 100 = 1755.9999999999998, а не 1756
shendz

Хороший момент, Шендз. Я обновил свой ответ решением, которое устраняет все ошибки с плавающей запятой для тех, кому это нужно.
Ник Ноулсон 01

1
Это не сработает для чисел меньше 1, если вы не хотите использовать десятичные дроби - truncateDecimals (.12345, 0) приведет к NaN, если вы не добавите проверку: if(isNAN(result) result = 0; Зависит от желаемого поведения.
Джереми Уитмер

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

5
Это работает хорошо, но даст результаты, которые, вероятно, будут нежелательными, если ему (или кому-то еще, кто посмотрит этот ответ позже) придется иметь дело с отрицательными числами. См stackoverflow.com/a/9232092/224354
Ник Ноулсон

3
Почему нежелательно? Изменение направления округления, когда вы опускаетесь ниже 0, вызывает всевозможные арифметические артефакты.
Thomas W

8
Есть разница между округлением и усечением. Усечение явно является поведением, к которому стремится этот вопрос. Если я перезвоню truncate(-3.14)и получу -4ответ, я бы определенно назвал это нежелательным.
Ник Ноулсон,

Я согласен с Томасом. Разница в перспективе может зависеть от того, усекаете ли вы обычно для отображения или для вычислений. С вычислительной точки зрения это позволяет избежать «арифметических артефактов».

3
var a = 65.1 var truncated = Math.floor(a * 100) / 100; // = 65.09 Следовательно, это неправильное решение
Саньям Джайн

22

Вы можете исправить округление, вычтя 0,5 для toFixed, например

(f - 0.005).toFixed(2)

1
Внимание: поскольку это не работает для очень маленьких чисел, чисел с более чем 3 десятичными знаками или отрицательных чисел. Попробуйте .0045, 5,4678 и -5,467
Ник Ноулсон,

Это будет работать до тех пор, пока вы сопоставите значение, которое вы вычитаете, с длиной, которую хотите иметь. все, что вы передаете toFixed (), должно быть числом 0 после десятичной дроби.
dmarra 02

18

Рассмотрим , воспользовавшись двойной тильдой:~~ .

Возьмите число. Умножьте на значащие цифры после запятой, чтобы можно было обрезать до нуля с помощью ~~. Разделите множитель обратно. Прибыль.

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

Я пытаюсь сопротивляться заключению ~~вызова в скобки; Я считаю, что порядок операций должен заставить это работать правильно.

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

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

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

Ссылка JSFiddle .

РЕДАКТИРОВАТЬ: Оглядываясь назад, я непреднамеренно также обрабатывал случаи, чтобы округлить левую часть десятичной дроби.

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

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


Это лучший ответ. Если вы расширите Mathпрототип этим и проверите NaN перед выполнением, это будет просто идеально.
Бартломей Залевски

truncator((10 * 2.9) / 100, 2)вернуть 0,28 вместо 0,29 ... jsfiddle.net/25tgrzq1
Alex

14

Хорошее однострочное решение:

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

Затем вызовите его с помощью:

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

2
Мне нравится это решение, но учтите, что оно полностью не поддерживается всеми браузерами. ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
Джин Парчеллано,

11

Я подумал, что добавлю ответ, |так как это просто и хорошо работает.

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

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

1
Хороший звонок. Использование побитового оператора переводит значение в int, а oring с 0 означает «просто сохраните то, что у меня уже есть». Делает то, что ~~делает мой ответ, но с помощью одной побитовой операции. Хотя он имеет то же ограничение, что и написано: мы не можем перейти больше 2 ^ 31 .
ruffin

1
неверно, если truncate((10 * 2.9) / 100);этот код возвращает 0,28 вместо 0,29 jsfiddle.net/9pf0732d
Alex

@Alex Как я догадываюсь, вы понимаете ... добро пожаловать в JavaScript! . Есть исправления. Возможно, вы хотите поделиться одним из них? : D
ruffin

@ruffin Я знаю об этой проблеме =) Я думал, что этот ответ был решением этой проблемы. К сожалению, точного решения пока не нашел, везде такая проблема.
Alex


7

Ответ @Dogbert можно улучшить с помощью Math.trunc, который усекает вместо округления.

Есть разница между округлением и усечением. Усечение явно является поведением, к которому стремится этот вопрос. Если я вызову truncate (-3.14) и получу -4 обратно, я определенно назвал бы это нежелательным. - @NickKnowlson

var a = 5.467;
var truncated = Math.trunc(a * 100) / 100; // = 5.46
var a = -5.467;
var truncated = Math.trunc(a * 100) / 100; // = -5.46

1
Это работает не во всех случаях, например console.log (Math.trunc (9.28 * 100) / 100); // 9.27
Майк Макуч

@MikeMakuch , что это не проблема с Math.trunc, а чем 9.28 * 100это , 927.9999а не 928. Возможно, вы захотите прочитать The Perils of Floating Point
zurfyx

5

Я написал ответ более коротким методом. Вот что я придумал

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

    return temp / step;
}

Метод можно использовать так

truncate(132456.25456789, 5)); // Output: 132456.25456
truncate(132456.25456789, 3)); // Output: 132456.254
truncate(132456.25456789, 1)); // Output: 132456.2   
truncate(132456.25456789));    // Output: 132456

Или, если вам нужен более короткий синтаксис, пожалуйста

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

это метод, который я ожидал использовать
taxilian

4
Number.prototype.trim = function(decimals) {
    var s = this.toString();
    var d = s.split(".");
    d[1] = d[1].substring(0, decimals);
    return parseFloat(d.join("."));
}

console.log((5.676).trim(2)); //logs 5.67

Мне нравится, что это работает со строками, что устраняет нюансы чисел с плавающей запятой. Благодарность!
iCode

4

Думаю, эта функция может быть простым решением:

function trunc(decimal,n=2){
  let x = decimal + ''; // string 
  return x.lastIndexOf('.')>=0?parseFloat(x.substr(0,x.lastIndexOf('.')+(n+1))):decimal; // You can use indexOf() instead of lastIndexOf()
}

console.log(trunc(-241.31234,2));
console.log(trunc(241.312,5));
console.log(trunc(-241.233));
console.log(trunc(241.2,0));  
console.log(trunc(241));


Через два года после того, как это было опубликовано, я наткнулся на это, когда пытался найти лучший способ, используя Math.trunc, regex и т.д. Мне очень нравится это решение. Очень просто, но отлично работает (по крайней мере, для моего случая использования).
giles123

Однако не забудьте учесть n = 0.
giles123

3

Я обнаружил проблему: учитывая следующую ситуацию: 2,1 или 1,2 или -6,4

Что делать, если вы хотите всегда 3 десятичных знака или два или что угодно, поэтому вам нужно заполнить ведущие нули справа

// 3 decimals numbers
0.5 => 0.500

// 6 decimals
0.1 => 0.10000

// 4 decimales
-2.1 => -2.1000

// truncate to 3 decimals
3.11568 => 3.115

Это фиксированная функция Ника Ноулсона.

function truncateDecimals (num, digits) 
{
    var numS = num.toString();
    var decPos = numS.indexOf('.');
    var substrLength = decPos == -1 ? numS.length : 1 + decPos + digits;
    var trimmedResult = numS.substr(0, substrLength);
    var finalResult = isNaN(trimmedResult) ? 0 : trimmedResult;

    // adds leading zeros to the right
    if (decPos != -1){
        var s = trimmedResult+"";
        decPos = s.indexOf('.');
        var decLength = s.length - decPos;

            while (decLength <= digits){
                s = s + "0";
                decPos = s.indexOf('.');
                decLength = s.length - decPos;
                substrLength = decPos == -1 ? s.length : 1 + decPos + digits;
            };
        finalResult = s;
    }
    return finalResult;
};

https://jsfiddle.net/huttn155/7/


x = 0.0000тест truncateDecimals (x, 2)не пройден. возвращается 0. не так, как ожидалось0.00
Ling Loeng

3
function toFixed(number, digits) {
    var reg_ex = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)")
    var array = number.toString().match(reg_ex);
    return array ? parseFloat(array[1]) : number.valueOf()
}

var test = 10.123456789
var __fixed = toFixed(test, 6)
console.log(__fixed)
// => 10.123456

3

Ответ @kirilloid кажется правильным, однако основной код необходимо обновить. Его решение не заботится об отрицательных числах (которые кто-то упомянул в разделе комментариев, но не был обновлен в основном коде).

Обновление до полного окончательного протестированного решения:

Number.prototype.toFixedDown = function(digits) {
    var re = new RegExp("([-]*\\d+\\.\\d{" + digits + "})(\\d)"),
    m = this.toString().match(re);
    return m ? parseFloat(m[1]) : this.valueOf();
};

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

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

Fiddle: округление номера JS вниз

PS: Недостаточно репо, чтобы прокомментировать это решение.


2

Вот простая, но работающая функция для усечения числа до двух знаков после запятой.

           function truncateNumber(num) {
                var num1 = "";
                var num2 = "";
                var num1 = num.split('.')[0];
                num2 = num.split('.')[1];
                var decimalNum = num2.substring(0, 2);
                var strNum = num1 +"."+ decimalNum;
                var finalNum = parseFloat(strNum);
                return finalNum;
            }

2

Получившийся тип остается числом ...

/* Return the truncation of n wrt base */
var trunc = function(n, base) {
    n = (n / base) | 0;
    return base * n;
};
var t = trunc(5.467, 0.01);

2

Lodash имеет несколько вспомогательных методов Math , которые могут круглый , пол , и CEIL числа до заданной десятичной точности. Это оставляет нули в конце.

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

(Примечание: funcесть Math.roundили ceilили floorв приведенном ниже коде)

// Shift with exponential notation to avoid floating-point issues.
var pair = (toString(number) + 'e').split('e'),
    value = func(pair[0] + 'e' + (+pair[1] + precision));

pair = (toString(value) + 'e').split('e');
return +(pair[0] + 'e' + (+pair[1] - precision));

Ссылка на исходный код


1

Вот мой взгляд на эту тему:

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

Это просто немного более проработанная версия

(f - 0.005).toFixed(2)

1

То, что помечено как решение, является лучшим решением, которое я находил до сегодняшнего дня, но имеет серьезную проблему с 0 (например, 0.toFixedDown (2) дает -0.01). Поэтому я предлагаю использовать это:

Number.prototype.toFixedDown = function(digits) {
  if(this == 0) {
    return 0;
  }
  var n = this - Math.pow(10, -digits)/2;
  n += n / Math.pow(2, 53); // added 1360765523: 17.56.toFixedDown(2) === "17.56"
  return n.toFixed(digits);
}

1

Вот что я использую:

var t = 1;
for (var i = 0; i < decimalPrecision; i++)
    t = t * 10;

var f = parseFloat(value);
return (Math.floor(f * t)) / t;

1
const TO_FIXED_MAX = 100;

function truncate(number, decimalsPrecison) {
  // make it a string with precision 1e-100
  number = number.toFixed(TO_FIXED_MAX);

  // chop off uneccessary digits
  const dotIndex = number.indexOf('.');
  number = number.substring(0, dotIndex + decimalsPrecison + 1);

  // back to a number data type (app specific)
  return Number.parseFloat(number);
}

// example
truncate(0.00000001999, 8);
0.00000001

работает с:

  • отрицательные числа
  • очень маленькие числа (точность Number.EPSILON)

0

просто чтобы указать на простое решение, которое сработало для меня

преобразовать его в строку, а затем регулярное выражение ...

var number = 123.45678;
var number_s = '' + number;
var number_truncated_s = number_s.match(/\d*\.\d{4}/)[0]
var number_truncated = parseFloat(number_truncated_s)

Его можно сократить до

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

0

Вот код ES6, который делает то, что вы хотите

const truncateTo = (unRouned, nrOfDecimals = 2) => {
      const parts = String(unRouned).split(".");

      if (parts.length !== 2) {
          // without any decimal part
        return unRouned;
      }

      const newDecimals = parts[1].slice(0, nrOfDecimals),
        newString = `${parts[0]}.${newDecimals}`;

      return Number(newString);
    };

// your examples 

 console.log(truncateTo(5.467)); // ---> 5.46

 console.log(truncateTo(985.943)); // ---> 985.94

// other examples 

 console.log(truncateTo(5)); // ---> 5

 console.log(truncateTo(-5)); // ---> -5

 console.log(truncateTo(-985.943)); // ---> -985.94


0
Number.prototype.truncate = function(places) {
  var shift = Math.pow(10, places);

  return Math.trunc(this * shift) / shift;
};

0

Вы можете работать со струнами. Проверяет, есть ли '.' существует, а затем удаляет часть строки.

усечь (7,88, 1) -> 7,8

усечь (7,889, 2) -> 7,89

усечь (-7,88, 1) -> -7,88

function  truncate(number, decimals) {
    const tmp = number + '';
    if (tmp.indexOf('.') > -1) {
        return +tmp.substr(0 , tmp.indexOf('.') + decimals+1 );
    } else {
        return +number
    }
 }

0

Я немного сбит с толку, почему существует так много разных ответов на такой фундаментально простой вопрос; есть только два подхода, которые я видел, и которые мне показались достойными внимания. Я провел быстрый тест, чтобы увидеть разницу в скорости, используя https://jsbench.me/ .

Это решение, которое в настоящее время (26.09.2020) помечено как ответ:

function truncate(n, digits) {
    var re = new RegExp("(\\d+\\.\\d{" + digits + "})(\\d)"),
        m = n.toString().match(re);
    return m ? parseFloat(m[1]) : n.valueOf();
};

[   truncate(5.467,2),
    truncate(985.943,2),
    truncate(17.56,2),
    truncate(0, 1),
    truncate(1.11, 1) + 22];

Однако здесь выполняются строковые и регулярные выражения, что обычно не очень эффективно, и есть функция Math.trunc, которая точно выполняет то, что хочет OP, только без десятичных знаков. Следовательно, вы можете легко использовать это плюс небольшую дополнительную арифметику, чтобы получить то же самое.

Вот еще одно решение, которое я нашел в этой теме, и я бы его использовал:

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

    return temp / step;
}

[   truncate(5.467,2),
    truncate(985.943,2),
    truncate(17.56,2),
    truncate(0, 1),
    truncate(1.11, 1) + 22];
    

Первый метод "на 99,92% медленнее", чем второй, поэтому второй определенно один я рекомендовал бы использовать.

Хорошо, вернемся к поиску других способов избежать работы ...

скриншот теста

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.