Использование побитового ИЛИ 0 для создания числа


193

Мой коллега наткнулся на метод для получения чисел с плавающей запятой с использованием побитового или:

var a = 13.6 | 0; //a == 13

Мы говорили об этом и задавались вопросом несколько вещей.

  • Как это работает? Наша теория заключалась в том, что использование такого оператора приводит число к целому числу, удаляя дробную часть
  • Есть ли у него какие-либо преимущества по сравнению с этим Math.floor? Может быть, это немного быстрее? (каламбур не предназначен)
  • Есть ли у него недостатки? Может быть, это не работает в некоторых случаях? Ясность очевидна, так как мы должны были это выяснить, и я пишу этот вопрос.

Спасибо.


6
Недостаток: он работает только до 2 ^ 31-1, что составляет около 2 миллиардов (10 ^ 9). Максимальное числовое значение составляет около 10 ^ 308 между прочим.
Шиме Видас

12
Пример: 3000000000.1 | 0оценивается в -1294967296. Таким образом, этот метод не может быть применен для расчета денег (особенно в случаях, когда вы умножаете на 100, чтобы избежать десятичных чисел).
Шиме Видас

13
@ ŠimeVidas Floats также не должны использоваться в расчетах денег
Джордж Райт

20
Это не настил, это усечение (округление до 0).
Бартломей Залевский

3
@sequence попробуйте набрать 0.1 + 0.2 == 0.3в консоли JavaScript. Если ваш язык поддерживает это, вы должны использовать десятичный тип. Если нет, храните центы вместо этого.
Алекс Турпин

Ответы:


161

Как это работает? Наша теория заключалась в том, что использование такого оператора приводит число к целому числу, удаляя дробную часть

Все побитовые операции, кроме беззнакового сдвига вправо >>>, работают с 32-разрядными целыми числами со знаком. Таким образом, использование побитовых операций преобразует число с плавающей точкой в ​​целое число.

Есть ли у него какие-либо преимущества перед Math.floor? Может быть, это немного быстрее? (каламбур не предназначен)

http://jsperf.com/or-vs-floor/2 кажется немного быстрее

Есть ли у него недостатки? Может быть, это не работает в некоторых случаях? Ясность очевидна, так как мы должны были это выяснить, и я пишу этот вопрос.

  • Не пройдет jsLint.
  • Только 32-разрядные целые числа со знаком
  • Странное Сравнительное поведение:, Math.floor(NaN) === NaNпока(NaN | 0) === 0

9
@harold действительно, потому что это на самом деле не округляется, а просто усекается.
Алекс Турпин

5
Еще одним возможным недостатком является то Math.floor(NaN) === NaN, что пока (NaN | 0) === 0. Эта разница может быть важной в некоторых приложениях.
Тед Хопп

4
Ваш jsperf выдает информацию о производительности для пустых циклов в Chrome из-за движения инвариантного кода цикла. Немного лучший тест на тестирование будет: jsperf.com/floor-performance/2
Сэм Джайлс,

4
Это стандартная часть asm.js(где я впервые узнал об этом). Это быстрее, если ни по какой другой причине, потому что это не вызывает функцию на Mathобъекте, функцию, которая могла бы быть в любое время заменена как в Math.floor = function(...).
Ган

3
(value | 0) === valueможет использоваться для проверки того, что значение на самом деле является целым числом и только целым числом (как в исходном коде Elm @ dwayne-crooks связанный). И foo = foo | 0может использоваться для приведения любого значения к целому числу (где 32-битные числа усекаются и все не числа становятся 0).
Дэвид Майкл Грегг

36

Это усечение в отличие от настила. Ответ Ховарда вроде правильный; Но я бы добавил, что Math.floorделает именно то, что положено в отношении отрицательных чисел. Математически это и есть пол.

В случае, который вы описали выше, программист больше интересовался усечением или отключением десятичной дроби. Хотя синтаксис, который они использовали, скрывает тот факт, что они конвертируют float в int.


7
Это правильный ответ, принятый - нет. Добавьте к этому, что Math.floor(8589934591.1)дает ожидаемый результат, не 8589934591.1 | 0 делает .
Салман А

21

В 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

6
За исключением того, что Math.trunc()работают с числом выше или равным 2 ^ 31 и | 0нет
Nolyurn

10

Ваш первый пункт верен. Число приводится к целому числу, и поэтому любые десятичные цифры удаляются. Обратите внимание, что Math.floorокругляется до следующего целого числа в сторону минус бесконечность и, таким образом, дает другой результат применительно к отрицательным числам.


5

Javascript представляет в Numberкачестве двойной точности 64-разрядных чисел с плавающей .

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);
}

5
  • В спецификациях говорится, что оно конвертируется в целое число:

    Пусть lnum будет ToInt32 (lval).

  • Производительность: это было проверено на jsperf раньше.

примечание: мертвая ссылка на спецификацию удалена

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.