4

I have been looking at an OCaml implementation of Hash Array Mapped Tries and noticed that three distinct bit-level operators are used:

  • lsl: left shift
  • lsr: right shift
  • asr: unsigned right shift

I would like to port this to F# but am unsure of the exact behavior of the F# bitwise operators.

The F# language reference gives two bitwise operators:

  • <<<: left shift
  • >>>: right shift

How would I use these operators to replicate the OCaml originals?

Regards,

Michael

Michael Thomas
  • 1,354
  • 7
  • 18

2 Answers2

6

Inspecting the implementation of operator <<< and >>> hints at a solution (https://github.com/Microsoft/visualfsharp/blob/fsharp4/src/fsharp/FSharp.Core/prim-types.fs)

let inline mask (n:int) (m:int) = (# "and" n m : int #)

[<NoDynamicInvocation>]
let inline (<<<) (x: ^T) (n:int) : ^T = 
     (^T: (static member (<<<) : ^T * int -> ^T) (x,n))
     when ^T : int32      = (# "shl" x (mask n 31) : int #)
     when ^T : uint32     = (# "shl" x (mask n 31) : uint32 #)
     when ^T : int64      = (# "shl" x (mask n 63) : int64 #)
     when ^T : uint64     = (# "shl" x (mask n 63) : uint64 #)
     when ^T : nativeint  = (# "shl" x n : nativeint #)
     when ^T : unativeint = (# "shl" x n : unativeint #)
     when ^T : int16      = (# "conv.i2" (# "shl" x (mask n 15) : int32  #) : int16 #)
     when ^T : uint16     = (# "conv.u2" (# "shl" x (mask n 15) : uint32 #) : uint16 #)
     when ^T : sbyte      = (# "conv.i1" (# "shl" x (mask n 7 ) : int32  #) : sbyte #)
     when ^T : byte       = (# "conv.u1" (# "shl" x (mask n 7 ) : uint32 #) : byte #)

[<NoDynamicInvocation>]
let inline (>>>) (x: ^T) (n:int) : ^T = 
     (^T: (static member (>>>) : ^T * int -> ^T) (x,n))
     when ^T : int32      = (# "shr"    x (mask n 31) : int32 #)
     when ^T : uint32     = (# "shr.un" x (mask n 31) : uint32 #)
     when ^T : int64      = (# "shr"    x (mask n 63) : int64 #)
     when ^T : uint64     = (# "shr.un" x (mask n 63) : uint64 #)
     when ^T : nativeint  = (# "shr"    x n : nativeint #)
     when ^T : unativeint = (# "shr.un" x n : unativeint #)
     when ^T : int16      = (# "conv.i2" (# "shr"    x (mask n 15) : int32  #) : int16 #)
     when ^T : uint16     = (# "conv.u2" (# "shr.un" x (mask n 15) : uint32 #) : uint16 #)
     when ^T : sbyte      = (# "conv.i1" (# "shr"    x (mask n 7 ) : int32  #) : sbyte #)
     when ^T : byte       = (# "conv.u1" (# "shr.un" x (mask n 7 ) : uint32 #) : byte #)

This code uses inline IL as well type-overloading only available to F# devs but the interesting rows seems to be:

when ^T : int32      = (# "shr"    x (mask n 31) : int32 #)
when ^T : uint32     = (# "shr.un" x (mask n 31) : uint32 #)

To shift right the int32 version then uses shr (signed shift right) which corresponds to asr, the uint32 uses shr.un (unsigned shift right) which corresponds to lsr.

The shift left versions all uses shl.

So for int32 a potential solution could be:

module ShiftOps =
    let inline ( lsl ) (x: int32) (n:int) : int32 = x <<< n
    let inline ( lsr ) (x: int32) (n:int) : int32 = int32 (uint32 x >>> n)
    let inline ( asr ) (x: int32) (n:int) : int32 = x >>> n

open ShiftOps

[<EntryPoint>]
let main argv = 
    printfn "0x%x" <| -1 lsr 4
    printfn "0x%x" <| -1 asr 4
    printfn "0x%x" <| -1 lsl 4

    0
Ramon Snir
  • 7,520
  • 3
  • 43
  • 61
3

In F# the kind of right shift is determined by the first operand. If it is unsigned, then the shift will be zero-padding, i.e., asr, otherwise it will be logical.

ivg
  • 34,431
  • 2
  • 35
  • 63
  • Thanks; so given I'm working with int32, and `of_uint32` and `to_int32` are defined as expected, I would have `let asr (x:int32) n = (to_int32 x) >>> n |> of_uint32`? – Michael Thomas Mar 22 '15 at 16:10
  • yes, looks correct to me. But I would rewrite it with: `let ast x n = uint32 (int32 x >>> n)`. (I always not sure about the precedence of logical operatos. – ivg Mar 22 '15 at 16:38