7 responses to “PowerShell needs Shift operators …”

  1. Jeffrey Snover

    Boy I’m glad we added Add-Type to V2!

  2. Ed Withers

    Ok, I take the point, and I’ll grant that Add-Type is cool, but wouldn’t it have been easier to just:

    function shift-left {
      Param ($x =1, $y)
      (1..$y)|%{$x = $x * 2}
      $x
    }

    (and divide for shift-right)?

  3. Ed Withers

    Hi Joel,

    Ok, the more I think about it, the more I agree with your original premise, we need shift operators.

    Actually, you really caught my attention when you pointed out that PowerShell doesn’t do “integer math”, so I had to do some playing around, and you know how that can lead into strange directions, (like how Int16 behaves differently than Int32 when you try to assign values with the high bit set.)

    To make a long story short, I decided to play with the bitwise operators and see if I could do the bit truncation needed to make my original multiply/divide trick work. It’s a lot more work than it’s worth. However, I finally hacked something together that I think is pretty stable and works quickly for any value in a [byte], [Int16], [Int32], or [Int64] regardless of the starting value or number of bits to shift.

    Here’s my code. I didn’t try to make it pipeline friendly, maybe I’ll save that for another day. I did combine both left and right shift into a single function with -left and -right switches…

    function Shift-Bit {
    ######################################################################
    ##
    ##   Shift-Bit
    ##
    ##   Accepts an input value in any of the integer types (byte, Int16,
    ##       Int32, and Int64) and the number of bits to shift it to the left
    ##
    ##   Input:
    ##       $x -    Value in any of the integer types
    ##               (byte, Int16, Int32, and Int64)
    ##               [Defaults to 1]
    ##
    ##       $y -    Number of bits to shift between 1 and bitsize of $x -1
    ##
    ##       $left - Switch to do left shift
    ##       $right- Switch to do right shift
    ##               [Default is right, if both are set, right takes priority]
    ##
    ##   v1  B. Edward Withers 27-Feb-2009
    ##       Initial code
    ##
    #######################################################################
        param ($x = 1, $y, [switch] $left, [switch] $right)

        # PowerShell does some strange casting of Int16, so we have to handle
        # it specially.
        [bool] $flagInt16 = $false

        # Only process if the number of bits to shift is valid
        if ($y -gt 0) {

            # Check the input variable type
            Switch ($x.gettype().fullname) {
                System.Byte {
                    $maxbits = 8
                    [byte] $highbit = 0x80
                    [byte] $allbits = 0xFF
                    [byte] $maskclear = 1
                    [byte] $maskhigh = 1
                    [byte] $maskrest = 1
                }
                System.Int16 {
                    $maxbits = 16
                    [int16] $highbit = -1 * 0x8000  # Int16 behaves differently
                    [int16] $allbits = -1
                    [int16] $maskclear = 1
                    [int16] $maskhigh = 1
                    [int16] $maskrest = 1
                    [bool] $flagInt16 = $true
                }
                System.Int32 {
                    $maxbits = 32
                    [int32] $highbit = 0x80000000
                    [int32] $allbits = -1
                    [int32] $maskclear = 1
                    [int32] $maskhigh = 1
                    [int32] $maskrest = 1
                }
                System.Int64 {
                    $maxbits = 64
                    [int64] $highbit = 0x8000000000000000
                    [int64] $allbits = -1
                    [int64] $maskclear = 1
                    [int64] $maskhigh = 1
                    [int64] $maskrest = 1
                }
                # For any other variable type, alert the user and exit
                default {
                    "Cannot process" + ($x.gettype().fullname) + " variables in a bitwise fashion."
                    return
                }
            } # End of Switch

            # Only process if the shift distance is valid (less than variable length)
            if ($y -lt $maxbits) {

                # Check the shift direction, default to right, and if both are set,
                # give precedence to right
                if ($left.IsPresent -and (-not $right.IsPresent)) {

                    # create a bitmask for just the bits we want to keep
                    for ($count=1; $count -le $maxbits - $y -1; $count += 1) {
                        $maskhigh = $maskhigh * 2 # a mask with only the high bit set
                        $maskrest = $maskrest * 2 + 1 # a mask with all kept bits set
                    }

                    # Now do some mask magic to get the high bits and the rest of the bits
                    # separated into variables
                    $maskrest = $maskrest -bxor $maskhigh # clear the high bit from $maskrest
                    $maskrest = $maskrest -band $x # then get just those bits from input
                    $maskhigh = $maskhigh -band $x # and get the high kept bit from input

                    # Now shift left by multiplying by 2
                    for ($count=1; $count -le $y; $count += 1) {
                        $maskrest = $maskrest * 2
                    }

                    # Set the low-order bits of the return value
                    $x = $maskrest

                    # if the high input bit was set, then set it on output
                    if ($maskhigh -ne 0) {
                        $x = $highbit -bor $x
                    }
                 }
                 else { # Assume we want a right shift if Left wasn't selected
                    # create a bitmask for the bits we will be losing
                    # (this prevents underflow and conversion into a floating point
                    # format)
                    for ($count=1; $count -le $y -1; $count += 1) {
                        $maskclear = $maskclear * 2 + 1 # a mask of bits to clear
                    }
                    # Invert the mask so the bits to clear are zero
                    $maskclear = $maskclear -bxor $allbits
                    # Reduce the input to just the bits we want
                    $maskclear = $maskclear -band $x
                    # Now shift to the right by dividing by 2
                    for ($count=1; $count -le $y; $count += 1) {
                        $maskclear = $maskclear / 2
                    }
                   
                    # Set the return value
                    $x = $maskclear                
                 }
            }
            else {
                "Cannot shift a " + ($x.gettype().fullname) + " by $y bits."
            }
        } # End of check for shift length

        # Handle the special case by forcing the output to be cast back
        if ($flagInt16) {[Int16]$x = $x}

        # Output the result
        $x

    } # End of Function

  4. Klaus Schulte

    Excellent!
    I did already add a snapin ( My first SnapIn !!! ) to powershell with exactly that functionality but only using the type long int. That’s a nice excercise … but the “Add-Type” cmdlet makes these things SO MUCH easier … that’s brilliant!!!

    I should go now and buy me the T-Shirt with the usual red heart, the “PS>_” and the “I love the PS team” on it!

    best regards, Klaus