Каков наилучший метод для преобразования с плавающей запятой в целое число в JavaScript?
Существует несколько различных методов преобразования чисел с плавающей запятой в целые числа в JavaScript. У меня вопрос: какой метод дает наилучшую производительность, является ли он наиболее совместимым или считается наилучшим?
Вот несколько методов, которые я знаю:
var a = 2.5;
window.parseInt(a); // 2
Math.floor(a); // 2
a | 0; // 2
Я уверен, что есть и другие. Предложения?
12 ответов
По данным этого сайта:
parseInt иногда используется как средство преобразования числа с плавающей запятой в целое число. Он очень плохо подходит для этой задачи, потому что если его аргумент имеет числовой тип, он сначала будет преобразован в строку, а затем проанализирован как число...
Для округления чисел до целых чисел предпочтительны Math.round, Math.ceil и Math.floor...
По-видимому, двойное поразрядно - не самый быстрый способ получить число:
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/
Из "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
"Лучший" способ зависит от:
- режим округления: какой тип округления (от числа до целого числа) вы ожидаете / требуете
для положительных и / или отрицательных чисел, имеющих дробную часть.
Общие примеры:плавать | ствол | пол | потолок | близко (половина вверх) ------+-------+-------+-------+--------------- +∞ | +∞ | +∞ | +∞ | +∞ +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
- результаты для встроенных преобразований Literal/String в float (режим округления: IEEE 754-2008 "округление до ближайшего, связь с четным") и встроенные арифметические операции (режим округления: IEEE 754-2008 "round- к ближайшему ").
Например: 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 возвращает целое число, которое вы ввели в него.
Для правильного ceil/floor/trunc числа с числом до 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)
на 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)
Тривиальное 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()
Работает очень просто и понятно:
просто обрежьте точку и цифры за ней, независимо от того, является ли аргумент положительным числом или отрицательным числом.
Мы можем дополнительно объяснить (и 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 в строковом контексте)
Диапазон: -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 имеет более высокий приоритет, чем Subtraction, мы эффективно получаем: (n)-(n%1)
,
Для положительных чисел легко увидеть, что это определяет значение: (2.5) - (0.5) = 2
,
для отрицательных чисел это означает значение: (-2.5) - (-0.5) = -2
(так как --=+
так (-2.5) + (0.5) = -2
).
Поскольку вход и выход Number
мы должны получить тот же полезный диапазон и выход по сравнению с ES6 Math.trunc()
(или это полифилл).
Примечание: хотя я боюсь (не уверен), что могут быть различия: потому что мы делаем арифметику (которая внутренне использует режим округления "nearTiesEven" (aka Banker's Rounding)) для исходного числа (число с плавающей запятой) и второго производного числа (дробь) это, кажется, вызывает смешивание digital_representation и арифметических ошибок округления, что потенциально может вернуть значение с плавающей точкой.
Преобразование с плавающей точкой в целое число (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>>0 ИЛИ n<<0 ИЛИ | n>>>0 | n < 0? -(-n>>>0): n>>>0 | n|0 ИЛИ n^0 ИЛИ ~~n | | | ИЛИ 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 и т.д... | и т.д... | и т.д... | так далее... +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 и т.д... | и т.д... | и т.д... | так далее... +1,5 | +1 | +1 | +1 +0,5 | 0 | 0 | 0 0 | 0 | 0 | 0 -0,5 | 0 | 0 | 0 -1,5 | -1 | +4294967295 | -1 и т.д... | и т.д... | и т.д... | так далее... -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 и т.д... | и т.д... | и т.д... | так далее... -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
))!
EXTRA:
Из приведенной выше информации у вас теперь должно быть все, что вам нужно знать.
Например, если вы хотите округлить от нуля (то есть до бесконечности), вы можете изменить Math.trunc()
Polyfill, например:
Math.intToInf || (Math.intToInf = function(n){
return n < 0 ? Math.floor(n) : Math.ceil(n);
});
Ответ уже дан, но для ясности.
Используйте библиотеку Math для этого. круглые, потолочные или напольные функции.
parseInt для преобразования строки в int, что здесь не то, что нужно
toFixed для преобразования float в строку, а не то, что здесь необходимо
Поскольку математические функции не будут выполнять никаких преобразований в строку или из нее, она будет быстрее, чем любой другой выбор, который в любом случае неверен.
Вы можете использовать Number(a).toFixed(0);
Или даже просто a.toFixed(0);
Редактировать:
Это округление до 0 мест, немного отличающееся от усечения, и, как кто-то еще предложил, toFixed возвращает строку, а не необработанное целое число. Полезно для демонстрации.
var num = 2.7; // typeof num is "Number"
num.toFixed(0) == "3"
var i = parseInt(n, 10);
Если вы не укажете радикальные значения, такие как '010'
будет рассматриваться как восьмеричное (и поэтому результат будет 8
не 10
).
Итак, я сделал тест, на Chrome
когда ввод уже число, самый быстрый будет ~~num
а также num|0
, половина скорости: Math.floor
и самый медленный будет parseInt
смотрите здесь
РЕДАКТИРОВАТЬ: кажется, уже есть другой человек, который сделал тест округления (больше результат) и дополнительные способы: num>>0
(так быстро как |0
) а также num - num%1
(иногда быстро)
Использование побитовых операторов. Возможно, это не самый ясный способ преобразования в целое число, но он работает с любым типом данных.
Предположим, ваша функция принимает аргумент 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
для не чисел
Похоже, что вопрос задается конкретно о преобразовании из числа с плавающей точкой в 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() на заднем фоне).