Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the equivalent F# bitwise operators to OCaml's 'lsr' and 'asr'?

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

like image 683
Michael Thomas Avatar asked Jan 08 '23 16:01

Michael Thomas


2 Answers

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
like image 70
Just another metaprogrammer Avatar answered Jan 11 '23 20:01

Just another metaprogrammer


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.

like image 22
ivg Avatar answered Jan 11 '23 21:01

ivg