Использование побитового ИЛИ 0 для создания номера
Мой коллега наткнулся на метод для получения чисел с плавающей запятой с использованием побитового или:
var a = 13.6 | 0; //a == 13
Мы говорили об этом и задавались вопросом несколько вещей.
- Как это работает? Наша теория заключалась в том, что использование такого оператора приводит число к целому числу, удаляя дробную часть
- Есть ли у нее какие-либо преимущества перед
Math.floor
? Может быть, это немного быстрее? (каламбур не предназначен) - Есть ли у него недостатки? Может быть, это не работает в некоторых случаях? Ясность очевидна, так как мы должны были это выяснить, и я пишу этот вопрос.
Благодарю.
7 ответов
Как это работает? Наша теория заключалась в том, что использование такого оператора приводит число к целому числу, удаляя дробную часть
Все побитовые операции, кроме беззнакового сдвига вправо, >>>
, работать с 32-разрядными целыми числами со знаком. Таким образом, использование побитовых операций преобразует число с плавающей точкой в целое число.
Есть ли у него какие-либо преимущества перед Math.floor? Может быть, это немного быстрее? (каламбур не предназначен)
http://jsperf.com/or-vs-floor/2 кажется немного быстрее
Есть ли у него недостатки? Может быть, это не работает в некоторых случаях? Ясность очевидна, так как мы должны были это выяснить, и я пишу этот вопрос.
- Не пройдет jsLint.
- Только 32-разрядные целые числа со знаком
- Странное Сравнительное поведение:
Math.floor(NaN) === NaN
, в то время как(NaN | 0) === 0
Это усечение в отличие от настила. Ответ Ховарда вроде правильный; Но я бы добавил, что Math.floor
делает именно то, что предполагается относительно отрицательных чисел. Математически это и есть пол.
В случае, который вы описали выше, программист больше интересовался усечением или отключением десятичной дроби. Хотя синтаксис, который они использовали, скрывает тот факт, что они конвертируют число с плавающей точкой в int.
В ECMAScript 6, эквивалент |0
это Math.trunc, вроде бы я должен сказать:
Возвращает неотъемлемую часть числа путем удаления любых дробных цифр. Он просто усекает точку и цифры за ней, независимо от того, является ли аргумент положительным числом или отрицательным числом.
Math.trunc(13.37) // 13
Math.trunc(42.84) // 42
Math.trunc(0.123) // 0
Math.trunc(-0.123) // -0
Math.trunc("-1.123")// -1
Math.trunc(NaN) // NaN
Math.trunc("foo") // NaN
Math.trunc() // NaN
Javascript представляет Number
как 64-разрядные числа с плавающей запятой Double Precision.
Math.floor
работает с этим в виду.
Побитовые операции работают в 32-битных целых числах со знаком. 32-разрядные целые числа со знаком используют первый бит в качестве отрицательного значения, а остальные 31 бит - это число. Из-за этого допустимое минимальное и максимальное число 32-разрядных чисел со знаком составляют -2 147 483 648 и 2147483647 (0x7FFFFFFFF) соответственно.
Итак, когда вы делаете | 0
по сути делаешь & 0xFFFFFFFF
, Это означает, что любое число, представленное как 0x80000000 (2147483648) или выше, будет возвращено как отрицательное число.
Например:
// Safe
(2147483647.5918 & 0xFFFFFFFF) === 2147483647
(2147483647 & 0xFFFFFFFF) === 2147483647
(200.59082098 & 0xFFFFFFFF) === 200
(0X7FFFFFFF & 0xFFFFFFFF) === 0X7FFFFFFF
// Unsafe
(2147483648 & 0xFFFFFFFF) === -2147483648
(-2147483649 & 0xFFFFFFFF) === 2147483647
(0x80000000 & 0xFFFFFFFF) === -2147483648
(3000000000.5 & 0xFFFFFFFF) === -1294967296
Также. Побитовые операции не "пол". Они усекаются, что равносильно тому, чтобы сказать, они кругом ближе всего к 0
, Как только вы обойдете отрицательные числа, Math.floor
округляет , а поразрядно начинает округлять.
Как я сказал ранее, Math.floor
безопаснее, потому что он работает с 64-битными плавающими числами. Побитовый - быстрее, да, но ограничен 32-битной областью со знаком.
Подвести итоги:
- Побитовая работает так же, если вы работаете с
0 to 2147483647
, - Поразрядно 1 номер, если вы работаете с
-2147483647 to 0
, - Побитовое абсолютно отличается для чисел меньше
-2147483648
и больше чем2147483647
,
Если вы действительно хотите настроить производительность и использовать оба:
function floor(n) {
if (n >= 0 && n < 0x80000000) {
return n & 0xFFFFFFFF;
}
if (n > -0x80000000 && n < 0) {
return (n - 1) & 0xFFFFFFFF;
}
return Math.floor(n);
}
Просто добавить Math.trunc
работает как побитовые операции. Так что вы можете сделать это:
function trunc(n) {
if (n > -0x80000000 && n < 0x80000000) {
return n & 0xFFFFFFFF;
}
return Math.trunc(n);
}
Ваш первый пункт верен. Число приводится к целому числу, и поэтому любые десятичные цифры удаляются. Обратите внимание, что Math.floor
округляет до следующего целого числа в сторону минус бесконечность и, таким образом, дает другой результат применительно к отрицательным числам.
В спецификациях говорится, что оно конвертируется в целое число:
Пусть lnum будет ToInt32(lval).
Производительность: это было проверено в jsperf раньше.
var myNegInt = -1 * Math.pow(2, 32);
var myFloat = 0.010203040506070809;
var my64BitFloat = myNegInt - myFloat;
var trunc1 = my64BitFloat | 0;
var trunc2 = ~~my64BitFloat;
var trunc3 = my64BitFloat ^ 0;
var trunc4 = my64BitFloat - my64BitFloat % 1;
var trunc5 = parseInt(my64BitFloat);
var trunc6 = Math.floor(my64BitFloat);
console.info(my64BitFloat);
console.info(trunc1);
console.info(trunc2);
console.info(trunc3);
console.info(trunc4);
console.info(trunc5);
console.info(trunc6);
IMO: На вопрос «Как это работает?», «Есть ли у него какие-то преимущества перед Math.floor?», «Есть ли у него недостатки?» бледный по сравнению с "Логично ли вообще использовать его для этой цели?"
Я думаю, что перед тем, как вы начнете разбираться в своем коде, вы можете запустить их. Мой совет; просто двигайтесь, здесь не на что смотреть. Побитовое использование для сохранения нескольких операций, и если это вообще важно для вас, обычно означает, что ваша архитектура кода требует доработки. Что касается того, почему это может иногда работать , остановленные часы показывают точность дважды в день, что не делает их полезными. У этих операторов есть свои применения, но не в этом контексте.