Каков наилучший способ преобразования плавающей запятой в целое число в JavaScript?
-
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
видеть здесь
РЕДАКТИРОВАТЬ:кажется, уже есть еще один человек, который сделал округление бенчмарк (больше результата) и дополнительные способы: 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() на заднем фоне).