Каков наилучший способ преобразования плавающей запятой в целое число в JavaScript?

StackOverflow https://stackoverflow.com/questions/131406

  •  02-07-2019
  •  | 
  •  

Вопрос

Существует несколько различных методов преобразования чисел с плавающей запятой в целые числа в JavaScript.Мой вопрос: какой метод обеспечивает наилучшую производительность, наиболее совместим или считается лучшей практикой?

Вот несколько известных мне методов:

var a = 2.5;
window.parseInt(a); // 2
Math.floor(a);      // 2
a | 0;              // 2

Я уверен, что есть и другие.Предложения?

Это было полезно?

Решение

В соответствии с этот сайт:

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

Для округления чисел до целых чисел предпочтительнее использовать Math.round, Math.ceil и Math.floor...

Другие советы

Из «Яваскрипта:Хорошие стороны» от Дугласа Крокфорда:

Number.prototype.integer = function () {
    return Math[this < 0 ? 'ceil' : 'floor'](this);
}

При этом вы добавляете метод к каждому объекту Number.

Тогда вы можете использовать его так:

var x = 1.2, y = -1.2;

x.integer(); // 1
y.integer(); // -1

(-10 / 3).integer(); // -3

Очевидно, что double-bitwise-not — это самый быстрый способ получить число:

var x = 2.5;
console.log(~~x); // 2

Раньше была статья здесь, но сейчас получаю 404: http://james.padolsey.com/javascript/double-bitwise-not/

Google закешировал это: http://74.125.155.132/search?q=cache:wpZnhsbJGt0J:james.padolsey.com/javascript/double-bitwise-not/+double+bitwise+not&cd=1&hl=en&ct=clnk&gl=us

Но Wayback Machine спасает положение! http://web.archive.org/web/20100422040551/http://james.padolsey.com/javascript/double-bitwise-not/

Ответ уже был дан, но для ясности.

Используйте для этого библиотеку Math.круглые, потолочные или напольные функции.

parseInt предназначен для преобразования строки в int, что здесь не требуется.

toFixed предназначен для преобразования числа с плавающей запятой в строку, но это не то, что здесь нужно.

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

Вы можете использовать Number(a).toFixed(0);

Или даже просто a.toFixed(0);

Редактировать:

Это округление до 0 знаков, немного отличающееся от усечения, и, как предположил кто-то другой, toFixed возвращает строку, а не необработанное целое число.Полезно для демонстрации.

var num = 2.7;  // typeof num is "Number"
num.toFixed(0) == "3"

Лучший путь зависит от на:

  • режим округления:что тип округления (от числа с плавающей запятой до целого числа), которое вы ожидаете/требуете
    для положительных и/или отрицательных чисел, имеющих дробную часть.
    Распространенные примеры:
    float | trunc | floor |  ceil | near (half up)
    ------+-------+-------+-------+---------------
    +∞    |   +∞  |   +∞  |   +∞  |   +∞  
    +2.75 |   +2  |   +2  |   +3  |   +3
    +2.5  |   +2  |   +2  |   +3  |   +3
    +2.25 |   +2  |   +2  |   +3  |   +2
    +0    |   +0  |   +0  |   +0  |   +0
     NaN  |  NaN  |  NaN  |  NaN  |  NaN
    -0    |   -0  |   -0  |   -0  |   -0
    -2.25 |   -2  |   -3  |   -2  |   -2
    -2.5  |   -2  |   -3  |   -2  |   -2
    -2.75 |   -2  |   -3  |   -2  |   -3
    -∞    |   -∞  |   -∞  |   -∞  |   -∞  
    
    Для преобразования чисел с плавающей запятой в целые числа мы обычно ожидать "усечение"
    (он же «округлить к нулю» он же "округлить до бесконечности").
    По сути, это просто «отрезает» дробную часть числа с плавающей запятой.
    Большинство техник и (внутренне) встроенных методов ведут себя таким образом.
  • вход:как представлено ваше число (с плавающей запятой):
    • String
      Обычно основание/основание:10 (десятичный)
    • с плавающей запятой («внутренний») Number
  • выход:что вы хотите сделать с полученным значением:
    • (промежуточный) вывод String (система счисления по умолчанию 10) (на экране)
    • выполнить дальнейшие расчеты по полученному значению
  • диапазон:
    в каком числовом диапазоне вы ожидаете результаты ввода/расчета
    и для какого диапазона вы ожидаете соответствующий «правильный» результат.

Только после Получив ответы на эти соображения, мы можем подумать о подходящих методах и скорости!


Согласно спецификации ECMAScript 262: все числа (тип Number) в javascript представлены/хранятся в:
"IEEE 754 с плавающей запятой двойной точности (binary64)"формат.
Итак, целые числа также представлены в такой же формат с плавающей запятой (как числа без дроби).
Примечание:большинство реализаций делать используйте более эффективные (по скорости и размеру памяти) целочисленные типы внутренне когда возможно!

Поскольку в этом формате хранится 1 знаковый бит, 11 битов экспоненты и первые 53 значащих бита («мантисса»), мы можем сказать, что: только Number-ценности между -252 и +252 может иметь дробь.
Другими словами: все репрезентативные положительные и отрицательные Number-ценности между 252 чтобы (почти) 2(211/2=1024) (в этот момент формат вызывает его день Infinity) уже являются целыми числами (с внутренним округлением, поскольку не осталось битов для представления оставшихся дробных и/или наименее значащих целых цифр).

И есть первая «подвох»:
Вы не можете контролировать внутренний режим округления Number-результаты встроенных преобразований литерала/строки в число с плавающей запятой (режим округления:IEEE 754-2008 «округление до ближайшего, привязка к четному») и встроенные арифметические операции (режим округления:IEEE 754-2008 «округление до ближайшего»).
Например:
252+0.25 = 4503599627370496.25 округляется и сохраняется как: 4503599627370496
252+0.50 = 4503599627370496.50 округляется и сохраняется как: 4503599627370496
252+0.75 = 4503599627370496.75 округляется и сохраняется как: 4503599627370497
252+1.25 = 4503599627370497.25 округляется и сохраняется как: 4503599627370497
252+1.50 = 4503599627370497.50 округляется и сохраняется как: 4503599627370498
252+1.75 = 4503599627370497.75 округляется и сохраняется как: 4503599627370498
252+2.50 = 4503599627370498.50 округляется и сохраняется как: 4503599627370498
252+3.50 = 4503599627370499.50 округляется и сохраняется как: 4503599627370500

Чтобы контролировать округление Number требуется дробная часть (и хотя бы один бит для ее представления), в противном случае ceil/floor/trunc/near возвращает целое число, которое вы в него ввели.

Чтобы правильно ограничить/ограничить/ограничить число до x значащих дробных десятичных цифр, нас волнует только то, будет ли соответствующее наименьшее и наибольшее десятичное дробное значение по-прежнему давать нам двоичное дробное значение после округления (поэтому не должно быть верхнего или нижнего предела). следующее целое число).
Так, например, если вы ожидаете «правильного» округления (для потолка/пола/охвата) до 1 значащей дробной десятичной цифры (x.1 to x.9), нам нужно по меньшей мере 3 бита (а не 4), чтобы дать нам а двоичное дробное значение:
0.1 ближе к 1/(23=8)=0.125 чем это 0 и 0.9 ближе к 1-1/(23=8)=0.875 чем это 1.

только вплоть до ±2(53-3=50) все представимые значения будут иметь ненулевую двоичную дробь не более чем первый значащая десятичная дробная цифра (значения x.1 к x.9).
Для 2 десятичных знаков ±2(53-6=47), для 3 десятичных знаков ±2(53-9=44), для 4 десятичных знаков ±2(53-13=40), для 5 десятичных знаков ±2(53-16=37), для 6 десятичных знаков ±2(53-19=34), для 7 десятичных знаков ±2(53-23=30), для 8 десятичных знаков ±2(53-26=27), для 9 десятичных знаков ±2(53-29=24), для 10 десятичных знаков ±2(53-33=20), для 11 десятичных знаков ±2(53-36=17), и т. д..

А «Безопасное целое число» в javascript это целое число:

  • это может быть точно представлено как число двойной точности IEEE-754, и
  • чье представление IEEE-754 не могу быть результатом округления любого другого целого числа, чтобы оно соответствовало представлению IEEE-754.
    (Несмотря на то ±253 (как точную степень 2) можно точно представить, это нет безопасное целое число, потому что оно также могло быть ±(253+1) до того, как оно было округлено, чтобы уместиться максимум в 53 старших бита).

Это эффективно определяет подмножество диапазона (безопасно представимых) целых чисел. между -253 и +253:

  • от: -(253 - 1) = -9007199254740991 (включительно)
    (константа, предоставляемая как статическое свойство Number.MIN_SAFE_INTEGER начиная с ES6)
  • к: +(253 - 1) = +9007199254740991 (включительно)
    (константа, предоставляемая как статическое свойство Number.MAX_SAFE_INTEGER начиная с ES6)
    Тривиальный полифил для этих двух новых констант ES6:

    Number.MIN_SAFE_INTEGER || (Number.MIN_SAFE_INTEGER=
      -(Number.MAX_SAFE_INTEGER=9007199254740991) //Math.pow(2,53)-1
    );
    


Начиная с ES6, существует дополнительный статический метод. Number.isSafeInteger() который проверяет, имеет ли переданное значение тип Number и является целым числом в безопасном целочисленном диапазоне (возвращает логическое значение). true или false).
Примечание:тоже вернется false для: NaN, Infinity и очевидно String (даже если оно представляет собой число).
Полифилл пример:

Number.isSafeInteger || (Number.isSafeInteger = function(value){
  return typeof value === 'number' && 
                value === Math.floor(value) &&
                value  <   9007199254740992 &&
                value  >  -9007199254740992;
});

ECMAScript 2015/ES6 предоставляет новый статический метод. Math.trunc()
чтобы усечь число с плавающей запятой до целого числа:

Возвращает целую часть числа x, удаляя все дробные цифры.Если x уже является целым числом, результатом будет x.

Или проще говоря(МДН):

В отличие от других трех математических методов: Math.floor(), Math.ceil() и Math.round(), путь Math.trunc() Работает очень просто и понятно:
просто усеките точку и цифры за ней, независимо от того, является ли аргумент положительным или отрицательным числом.

Мы можем дополнительно объяснить (и полифилл) Math.trunc() как таковой:

Math.trunc || (Math.trunc = function(n){
    return n < 0 ? Math.ceil(n) : Math.floor(n); 
});

Обратите внимание: полезная нагрузка приведенного выше полифила может потенциально быть лучше предварительно оптимизирован движком по сравнению с:
Math[n < 0 ? 'ceil' : 'floor'](n);

Применение: Math.trunc(/* Number or String */)
Вход:(Целое число или с плавающей запятой) Number (но с радостью попытаюсь преобразовать строку в число)
Выход:(Целое число) Number (но с радостью попытаюсь преобразовать число в строку в строковом контексте)
Диапазон: -2^52 к +2^52 (кроме этого мы должны ожидать «ошибок округления» (и в какой-то момент научного/экспоненциального обозначения) просто и ясно, потому что наши Number ввод в IEEE 754 уже потерял дробную точность:поскольку числа между ±2^52 к ±2^53 уже внутренне закругленный целые числа (например 4503599627370509.5 внутренне уже представлен как 4503599627370510) и далее ±2^53 целые числа также теряют точность (степень 2)).


Преобразование числа с плавающей запятой в целое число путем вычитания Остаток (%) деления на 1:

Пример: result = n-n%1 (или n-=n%1)
Это также должно обрезать плавает.Поскольку оператор Remainder имеет более высокий приоритет чем вычитание, мы фактически получаем: (n)-(n%1).
Для положительных чисел легко увидеть, что это минимальное значение: (2.5) - (0.5) = 2,
для отрицательных чисел это значение: (-2.5) - (-0.5) = -2 (потому что --=+ так (-2.5) + (0.5) = -2).

Поскольку вход и выход являются Number мы должен получить тот же полезный диапазон и мощность по сравнению с ES6 Math.trunc() (или это полифилл).
Примечание:крутой я страх (не уверен) могут быть различия:поскольку мы выполняем арифметические действия (которые внутри используют режим округления «nearTiesEven» (также известное как «округление банкира»)) над исходным числом (число с плавающей запятой) и вторым производным числом (дробь), это, по-видимому, приводит к объединению ошибок цифрового_представления и арифметического округления, поэтому потенциально в конце концов возвращает поплавок..


Преобразование числа с плавающей запятой в целое число с помощью (ab-) использования побитовые операции:

Это работает внутренне принуждение (с плавающей запятой) Number преобразование (усечение и переполнение) в 32-битное целое число со знаком (дополнение до двух) с помощью побитовой операции на Number (и результат преобразуется обратно в (с плавающей запятой) Number который содержит только целочисленное значение).

Снова, вход и выход является Number (и снова тихое преобразование строкового ввода в число и числового вывода в строку).

Более важное сложное (и обычно забытое и не объясненное):
в зависимости от побитовой операции и знака числа, полезный диапазон будет ограниченное между:
-2^31 к +2^31 (нравиться ~~num или num|0 или num>>0) ИЛИ 0 к +2^32 (num>>>0).

Это следует дополнительно уточнить с помощью следующей таблицы поиска (содержащей все «критические» примеры):

              n             | n>>0 OR n<<0 OR   |    n>>>0    | n < 0 ? -(-n>>>0) : n>>>0
                            | n|0 OR n^0 OR ~~n |             |
                            | OR n&0xffffffff   |             |
----------------------------+-------------------+-------------+---------------------------
+4294967298.5 = (+2^32)+2.5 |             +2    |          +2 |          +2
+4294967297.5 = (+2^32)+1.5 |             +1    |          +1 |          +1
+4294967296.5 = (+2^32)+0.5 |              0    |           0 |           0
+4294967296   = (+2^32)     |              0    |           0 |           0
+4294967295.5 = (+2^32)-0.5 |             -1    | +4294967295 | +4294967295
+4294967294.5 = (+2^32)-1.5 |             -2    | +4294967294 | +4294967294
       etc...               |         etc...    |      etc... |      etc...
+2147483649.5 = (+2^31)+1.5 |    -2147483647    | +2147483649 | +2147483649
+2147483648.5 = (+2^31)+0.5 |    -2147483648    | +2147483648 | +2147483648
+2147483648   = (+2^31)     |    -2147483648    | +2147483648 | +2147483648
+2147483647.5 = (+2^31)-0.5 |    +2147483647    | +2147483647 | +2147483647
+2147483646.5 = (+2^31)-1.5 |    +2147483646    | +2147483646 | +2147483646
       etc...               |         etc...    |      etc... |      etc...
         +1.5               |             +1    |          +1 |          +1
         +0.5               |              0    |           0 |           0
          0                 |              0    |           0 |           0
         -0.5               |              0    |           0 |           0
         -1.5               |             -1    | +4294967295 |          -1
       etc...               |         etc...    |      etc... |      etc...
-2147483646.5 = (-2^31)+1.5 |    -2147483646    | +2147483650 | -2147483646
-2147483647.5 = (-2^31)+0.5 |    -2147483647    | +2147483649 | -2147483647
-2147483648   = (-2^31)     |    -2147483648    | +2147483648 | -2147483648
-2147483648.5 = (-2^31)-0.5 |    -2147483648    | +2147483648 | -2147483648
-2147483649.5 = (-2^31)-1.5 |    +2147483647    | +2147483647 | -2147483649
-2147483650.5 = (-2^31)-2.5 |    +2147483646    | +2147483646 | -2147483650
       etc...               |         etc...    |      etc... |      etc...
-4294967294.5 = (-2^32)+1.5 |             +2    |          +2 | -4294967294
-4294967295.5 = (-2^32)+0.5 |             +1    |          +1 | -4294967295
-4294967296   = (-2^32)     |              0    |           0 |           0
-4294967296.5 = (-2^32)-0.5 |              0    |           0 |           0
-4294967297.5 = (-2^32)-1.5 |             -1    | +4294967295 |          -1
-4294967298.5 = (-2^32)-2.5 |             -2    | +4294967294 |          -2

Примечание 1:последний столбец имеет расширенный диапазон 0 к -4294967295 с использованием (n < 0 ? -(-n>>>0) : n>>>0).
Заметка 2:побитовое вводит свои собственные издержки преобразования(с) (тяжесть против Math зависит от фактической реализации, поэтому побитно мог быть быстрее (часто в старых исторических браузерах)).


Очевидно, что если бы ваше число с плавающей запятой было String начать с,
parseInt(/*String*/, /*Radix*/) было бы подходящим выбором для анализа его в целое число Number.
parseInt() воля обрезать а также (для положительных и отрицательных чисел).
А диапазон снова ограничено плавающей запятой двойной точности IEEE 754, как объяснено выше для Math метод(ы).

Наконец, если у вас есть String и ожидать String в качестве вывода вы также можете отрезать точку системы счисления и дробь (что также дает вам более точный диапазон усечения по сравнению с плавающей запятой двойной точности IEEE 754 (±2^52))!


ДОПОЛНИТЕЛЬНЫЙ:
Из приведенной выше информации теперь у вас должно быть все, что вам нужно знать.

Если, например, вы хотите округлить от нуля (он же округлить до бесконечности) вы можете изменить Math.trunc() полифилл, для пример:

Math.intToInf || (Math.intToInf = function(n){
    return n < 0 ? Math.floor(n) : Math.ceil(n); 
});
var i = parseInt(n, 10);

Если вы не укажете значения системы счисления, например '010' будет считаться восьмеричным (и поэтому результат будет 8 нет 10).

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

Предположим, ваша функция принимает аргумент value, и функция работает таким образом, что value всегда должно быть целым числом (принимается 0).Тогда любое из следующих действий назначит value как целое число:

value = ~~(value)
value = value | 0;
value = value & 0xFF;   // one byte; use this if you want to limit the integer to
                        // a predefined number of bits/bytes

Самое приятное то, что это работает со строками (которые вы можете получить при вводе текста и т. д.), которые являются числами. ~~("123.45") === 123.Любые нечисловые значения приводят к 0, то есть,

~~(undefined) === 0
~~(NaN) === 0
~~("ABC") === 0

Он работает с шестнадцатеричными числами как строками (с 0x префикс)

~~("0xAF") === 175

Я полагаю, что здесь задействовано какое-то принуждение.Я проведу несколько тестов производительности, чтобы сравнить их с parseInt() и Math.floor(), но мне нравится иметь дополнительное удобство в виде отсутствия Errors быть брошенным и получить 0 для нецифр

Поэтому я сделал тест на Chrome когда ввод уже является числом, самым быстрым будет ~~num и num|0, половина скорости: Math.floor, и самым медленным будет parseInt видеть здесь

benchmark result

РЕДАКТИРОВАТЬ:кажется, уже есть еще один человек, который сделал округление бенчмарк (больше результата) и дополнительные способы: num>>0 (так быстро как |0) и num - num%1 (иногда быстро)

Кажется, вопрос касается именно преобразования числа с плавающей запятой в int.Насколько я понимаю, способ сделать это - использовать toFixed.Так...

var myFloat = 2.5;
var myInt = myFloat.toFixed(0);

Кто-нибудь знает, если Math.floor() более или менее производительен, чем Number.toFixed()?

вы также можете сделать это следующим образом:

var string = '1';
var integer = a * 1;

parseInt(), вероятно, лучший. a | 0 не делает то, что вы действительно хотите (он просто присваивает 0, если a является неопределенным или нулевым значением, что означает, что пустой объект или массив проходит тест), а Math.floor работает с помощью некоторой хитрости типа (он в основном вызывает parseInt() на заднем фоне).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top