Каков наилучший метод для преобразования с плавающей запятой в целое число в JavaScript? - PullRequest
29 голосов
/ 25 сентября 2008

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

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

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

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

Ответы [ 12 ]

38 голосов
/ 25 сентября 2008

По данным этого сайта :

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

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

8 голосов
/ 15 июня 2011

Из «Javascript: The Good Parts» от Дугласа Крокфорда:

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
7 голосов
/ 15 марта 2010

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

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/

4 голосов
/ 25 сентября 2008

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

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

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

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

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

3 голосов
/ 21 марта 2016

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

  • режим округления: какой тип округления (от числа с плавающей точкой до целого числа) вы ожидаете / требуете
    для положительных и / или отрицательных чисел, имеющих дробную часть.
    Общие примеры:
    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
    -∞    |   -∞  |   -∞  |   -∞  |   -∞  
    
    Для преобразования с плавающей точкой в ​​целое число мы обычно ожидаем "усечение"
    (он же "круглая к нулю" он же "круглая от бесконечности" ).
    Фактически это просто «отрубает» дробную часть числа с плавающей запятой.
    Большинство методов и (внутренних) встроенных методов ведут себя таким образом.
  • input: как представляется ваше число (с плавающей запятой):
    • String
      Обычно основание / основание: 10 (десятичное число)
    • с плавающей запятой («внутренний») Number
  • output: что вы хотите сделать с полученным значением:
    • (промежуточный) вывод String (по умолчанию основание 10) (на экране)
    • выполнить дальнейшие расчеты по полученному значению
  • Диапазон:
    в каком числовом диапазоне вы ожидаете ввод / расчет-результаты
    и для какого диапазона вы ожидаете соответствующего «правильного» выхода.

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


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

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

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

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

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

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

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

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

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

  • от: -(2<sup>53</sup> - 1) = -9007199254740991 (включительно)
    (константа предоставляется как статическое свойство Number.MIN_SAFE_INTEGER начиная с ES6)
  • до: +(2<sup>53</sup> - 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()
усечь число с плавающей точкой до целого числа:

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

Или, проще говоря ( MDN ):

В отличие от других трех математических методов: 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)
Это также должно усечь плавает. Так как оператор «Остаток» имеет более высокий приоритет , чем вычитание, мы фактически получаем: (n)-(n%1).
Для положительных чисел легко увидеть, что это минимальное значение: (2.5) - (0.5) = 2,
для отрицательных чисел это означает значение: (-2.5) - (-0.5) = -2 (потому что --=+ так (-2.5) + (0.5) = -2).

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


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

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

Опять же, ввод и вывод равен Number (и снова тихое преобразование из String-input в Number и Number-output to 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: поразрядно вводит свои собственные издержки преобразования ( s ) (серьезность против Math зависит от фактической реализации, поэтому побитовая может быть быстрее (часто в старых исторических браузерах)).


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

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


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

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

Math.intToInf || (Math.intToInf = function(n){
    return n < 0 ? Math.floor(n) : Math.ceil(n); 
});
3 голосов
/ 25 сентября 2008

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

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

Edit:

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

var num = 2.7;  // typeof num is "Number"
num.toFixed(0) == "3"
2 голосов
/ 25 сентября 2008
var i = parseInt(n, 10);

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

1 голос
/ 23 февраля 2015

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

benchmark result

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

1 голос
/ 07 сентября 2011

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

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

0 голосов
/ 26 сентября 2008

Вы также можете сделать это так:

var string = '1';
var integer = a * 1;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...