Зрізати (не округляти) десяткові числа в javascript


93

Я намагаюся усікати десяткові числа до десяткових знаків. Щось на зразок цього:

5.467   -> 5.46  
985.943 -> 985.94

toFixed(2)робить майже правильну річ, але це заокруглює цінність. Мені не потрібно значення, округлене. Сподіваюся, це можливо в javascript.


6
jQuery - це просто фреймворк, і ваша проблема не пов'язана з jQuery. Це більше про те, щоб виконати деякі базові обчислення в JavaScript. Сподіваюся, вас також влаштовує рішення не-jQuery.
Фелікс Клінг,

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

Відповіді:


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 . Ви можете знайти інші бібліотеки Javascript BigDecimal у цьому запитанні SO: "Чи є хороша бібліотека Javascript BigDecimal?" і ось хороший допис у блозі про математичні бібліотеки для Javascript


Чому несподівано? Зміна напрямку округлення, коли ви опускаєтесь нижче 0, спричиняє всілякі арифметичні артефакти та безглузду математику. Наприклад, удвічі більше чисел округлиться до 0, ніж будь-яке інше ціле число. Що стосується графіки, бухгалтерського обліку та багатьох інших цілей, ви отримаєте безглузді результати. Правду кажучи, важче було б сказати, для чого корисна ваша пропозиція, як сказати, що це не так .
Thomas W

Це добре саме для того, що там написано - коли потрібно скоротити десяткові крапки, а не округлення.
Нік Ноулсон,

1
Не буде працювати з 17.56, оскільки браузер дає 17.56 * 100 = 1755.9999999999998, а не 1756
Шендз

Хороший момент шендз. Я оновив свою відповідь рішенням, яке усуває всі помилки з плаваючою комою для тих, хто цього потребує.
Нік Ноулсон

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

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 посилання .

EDIT: Озираючись назад, я ненавмисно також обробляв справи, щоб також округляти ліворуч від десяткової коми.

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

Логіка трохи шалена шукає таке використання і може отримати вигоду від швидкого рефактора. Але це все одно працює. Краще пощастить, ніж добре.


Це найкраща відповідь. Якщо ви продовжите Mathпрототип цим і перевірите наявність NaN перед запуском, це було б просто ідеально.
Бартломей Залевський

truncator((10 * 2.9) / 100, 2)повернути 0,28 замість 0,29 ... jsfiddle.net/25tgrzq1
Алекс

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/… )
Gene Parcellano

11

Я думав, що кину відповідь, використовуючи, |оскільки це просто і добре працює.

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

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

1
Гарний дзвінок. Використання побітового оператора примушує значення до int, а oring з 0 означає "просто зберегти те, що я вже отримав". Робить те, що робить моя ~~відповідь, але з однією побітовою операцією. Хоча воно має таке ж обмеження, як і написане: Ми не можемо перейти 2 ^ 31 .
jorffin

1
неправильно, коли truncate((10 * 2.9) / 100);цей код повертає 0,28 замість 0,29 jsfiddle.net/9pf0732d
Олексій

@Alex Як я здогадуюсь, ти усвідомлюєш ... ласкаво просимо до JavaScript! . Є виправлення. Можливо, ви хотіли б поділитися ним? : D
ruffin

@ruffin Я знаю про це питання =) Я думав, що ця відповідь є рішенням цієї проблеми. На жаль, я ще не знайшов точного рішення, скрізь є така проблема.
Алекс


7

Відповідь Догберта можна покращити за допомогою Math.trunc, який скорочує замість округлення.

Існує різниця між округленням та усіченням. Зрізання - це, очевидно, поведінка, яку шукає це питання. Якщо я покличу усікати (-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. Можливо, ви захочете прочитати "Небезпеки з плаваючою точкою"
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;
}

це метод, який я очікував застосувати
таксіст

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, регулярні вирази тощо. Мені дуже подобається це рішення. Мертвий простий, але чудово працює (у будь-якому випадку для мого випадку використання).
giles123

Не забудьте врахувати n = 0.
giles123

3

Я виявив проблему: враховуючи наступну ситуацію: 2.1 або 1.2 або -6.4

Що робити, якщо ви хочете завжди 3 десяткові чи два знаки або wharever, тож ви повинні заповнити провідні нулі праворуч

// 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
Лінг Лоенг

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

Скрипка: номер JS округлий вниз

PS: Недостатньо репо для коментарів щодо цього рішення.


2

Ось проста, але робоча функція для скорочення числа до 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

працює з:

  • від’ємні числа
  • дуже малі цифри (число. точність 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.