Why does a shift by 0 truncate the decimal?

后端 未结 5 959
迷失自我
迷失自我 2020-11-27 05:54

I recently found this piece of JavaScript code:

Math.random() * 0x1000000 << 0

I understood that the first part was just generating a

相关标签:
5条回答
  • 2020-11-27 06:02

    Math.random() returns a number between 0 (inclusive) and 1 (exclusive). Multiplying this number with a whole number results in a number that has decimal portion. The << operator is a shortcut for eliminating the decimal portion:

    The operands of all bitwise operators are converted to signed 32-bit integers in big-endian order and in two's complement format.

    The above statements means that the JavaScript engine will implicitly convert both operands of << operator to 32-bit integers; for numbers it does so by chopping off the fractional portion (numbers that do not fit 32-bit integer range loose more than just the decimal portion).

    And is it just a nuance of JavaScript, or does it occur in other languages as well?

    You'll notice similar behavior in loosely typed languages. PHP for example:

    var_dump(1234.56789 << 0);
    // int(1234)
    

    For strongly types languages, the programs will usually refuse to compile. C# complains like this:

    Console.Write(1234.56789 << 0);
    // error CS0019: Operator '<<' cannot be applied to operands of type 'double' and 'int'
    

    For these languages, you already have type-casting operators:

    Console.Write((int)1234.56789);
    // 1234
    
    0 讨论(0)
  • From the Mozilla documentation of bitwise operators (which includes the shift operators)

    The operands of all bitwise operators are converted to signed 32-bit integers in big-endian order and in two's complement format.

    So basically the code is using that somewhat-incidental aspect of the shift operator as the only significant thing it does due to shifting by 0 bits. Ick.

    And is it just a nuance of JavaScript, or does it occur in other languages as well?

    I can't speak for all languages of course, but neither Java nor C# permit double values to be the left operand a shift operator.

    0 讨论(0)
  • 2020-11-27 06:13

    You're correct; it is used to truncate the value.

    The reason >> works is because it operates only on 32-bit integers, so the value is truncated. (It's also commonly used in cases like these instead of Math.floor because bitwise operators have a low operator precedence, so you can avoid a mess of parentheses.)

    And since it operates only on 32-bit integers, it's also equivalent to a mask with 0xffffffff after rounding. So:

    0x110000000      // 4563402752
    0x110000000 >> 0 // 268435456
    0x010000000      // 268435456
    

    But that's not part of the intended behaviour since Math.random() will return a value between 0 and 1.

    Also, it does the same thing as | 0, which is more common.

    0 讨论(0)
  • 2020-11-27 06:18

    According to ECMAScript Language Specification:
    http://ecma-international.org/ecma-262/5.1/#sec-11.7.1

    The production ShiftExpression : ShiftExpression >> AdditiveExpression is evaluated as follows:

    1. Let lref be the result of evaluating ShiftExpression.
    2. Let lval be GetValue(lref).
    3. Let rref be the result of evaluating AdditiveExpression.
    4. Let rval be GetValue(rref).
    5. Let lnum be ToInt32(lval).
    6. Let rnum be ToUint32(rval).
    7. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.
    8. Return the result of performing a sign-extending right shift of lnum by shiftCount bits. The most significant bit is propagated. The result is a signed 32-bit integer.
    0 讨论(0)
  • 2020-11-27 06:24

    The behavior you're observing is defined in the ECMA-262 standard

    Here's an excerpt from the specification of the << left shift operator:

    The production ShiftExpression : ShiftExpression << AdditiveExpression is evaluated as follows:

    1. Let lref be the result of evaluating ShiftExpression.
    2. Let lval be GetValue(lref).
    3. Let rref be the result of evaluating AdditiveExpression.
    4. Let rval be GetValue(rref).
    5. Let lnum be ToInt32(lval).
    6. Let rnum be ToUint32(rval).
    7. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.
    8. Return the result of left shifting lnum by shiftCount bits. The result is a signed 32-bit integer.

    As you can see, both operands are cast to 32 bit integers. Hence the disappearance of decimal parts.

    The same applies for the other bit shift operators. You can find their respective descriptions in section 11.7 Bitwise Shift Operators of the document I linked to.

    In this case, the only effect of performing the shift is type conversion. Math.random() returns a floating point value.

    0 讨论(0)
提交回复
热议问题