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

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

Вот несколько методов, о которых я знаю:

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

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

Ответы

Ответ 1

Согласно этот веб-сайт:

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

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

Ответ 2

По-видимому, двойной побитовый - это не самый быстрый способ заполнить номер:

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 экономит день! http://web.archive.org/web/20100422040551/http://james.padolsey.com/javascript/double-bitwise-not/

Ответ 3

От "Javascript: хорошие детали" от Дугласа Крокфорда:

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

Ответ 4

Ответ уже дан, но чтобы быть ясным.

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

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

toFixed предназначен для преобразования float в строку также не в том, что здесь необходимо

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

Ответ 5

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

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

Edit:

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

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

Ответ 6

var i = parseInt(n, 10);

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

Ответ 7

"Лучший" способ зависит от:

  • режим округления: какой тип округления (от float до integer), который вы ожидаете/требуете
    для положительных и/или отрицательных чисел, которые имеют дробную часть.
    Общие примеры:
    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
    -∞    |   -∞  |   -∞  |   -∞  |   -∞  
    
    Для конверсий с плавающей точкой в ​​целые числа мы обычно ожидаем "усечения"
    (aka "round to zero" aka "округлить от бесконечности" ).
    Эффективно это просто "отбивает" дробную часть числа с плавающей запятой.
    Большинство методов и (внутренние) встроенные методы ведут себя таким образом.
  • input: как отображается ваш (с плавающей запятой):
    • String
      Обычно radix/base: 10 (десятичный)
    • плавающая точка ('internal') Number
  • вывод: что вы хотите сделать с полученным значением:
    • (промежуточный) выход String (по умолчанию radix 10) (на экране)
    • выполнить дальнейшие вычисления по результирующему значению
  • диапазон:
    в каком числовом диапазоне вы ожидаете ввода/вычисления-результаты
    и для какого диапазона вы ожидаете соответствующий "правильный" выход.

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


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

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

И есть первая "добыча":
Вы не можете управлять внутренним режимом округления Number -результатов для встроенных потоков Literal/String для конвертирования по платам (округление: 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 значащих дробных десятичных цифр, нам остается только, если соответствующее минимальное и наименьшее дробное значение по-прежнему будет давать двоичное дробное значение после округления (так что не быть потолком или перекрывается до следующего целого числа).
Так, например, если вы ожидаете "правильного" округления (для ceil/floor/trunc) до 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), для десяти десятичных знаков ±2(53-33=20), для 11 десятичных знаков ±2(53-36=17) и т.д.

A "Безопасное целое число" в javascript - целое число:

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

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

  • от: -(253 - 1) = -9007199254740991 (включительно)
    (константа, предоставленная как статическое свойство Number.MIN_SAFE_INTEGER с ES6)
  • to: +(253 - 1) = +9007199254740991 (включительно)
    (константа, предоставленная как статическое свойство Number.MAX_SAFE_INTEGER с ES6)
    Тривиальный polyfill для этих 2 новых констант 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 (даже если он представляет число).
Polyfill пример:

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

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

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

Или добавьте проще (MDN):

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

Далее мы можем объяснить (и polyfill) Math.trunc() следующим образом:

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

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

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


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

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

Поскольку ввод и вывод Number, мы должны получить тот же полезный диапазон и вывод по сравнению с ES6 Math.trunc() (или it polyfill). < ш > Примечание: жестко я боюсь (не уверен), могут быть различия: потому что мы делаем арифметику (которая внутренне использует режим округления "nearTiesEven" (например, округление Banker)) на исходном номере (float) и втором производном номере (фракция), это, кажется, приглашает компаундирование digital_representation и арифметические ошибки округления, таким образом, потенциально возвращая поплавок в конце концов..


Преобразование float to integer с помощью (ab-) с помощью побитовых операций:

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

Опять, ввод и вывод Number (и снова тихая конвертация из String-input в Number и Number-output в String).

Более важные жесткие (и обычно забытые и не объясненные):
в зависимости от побитовой операции и знака числа, полезный диапазон будет ограничен между:
-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: побитовое представляет свои собственные служебные данные об изменении (серьезность vs Math зависит от фактической реализации, поэтому побитовое может быть быстрее (часто в старых исторических браузерах)).


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

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


EXTRA:
Из приведенной выше информации вы должны иметь все, что вам нужно знать.

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

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

Ответ 8

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

Предположим, что ваша функция принимает аргумент 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 для не-чисел

Ответ 9

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

benchmark result

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

Ответ 10

Вопрос заключается в том, что речь идет конкретно о преобразовании из float в int. Я понимаю, что способ сделать это - использовать toFixed. Так что...

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

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

Ответ 11

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

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

Ответ 12

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