Skip to content

Instantly share code, notes, and snippets.

@voronoipotato
Created August 17, 2024 00:41
Show Gist options
  • Save voronoipotato/fa4834afa20f3b00d55b64e378080489 to your computer and use it in GitHub Desktop.
Save voronoipotato/fa4834afa20f3b00d55b64e378080489 to your computer and use it in GitHub Desktop.
open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Running
open BenchmarkDotNet.Jobs
open System
open Microsoft.FSharp.NativeInterop
type Chars () =
static member chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".AsSpan()
let chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let b36Unfold (number: int) =
let unfoldFunc n =
if n = 0 then None
else
let remainder = n % 36
let quotient = n / 36
Some (chars.[remainder], quotient)
Array.unfold unfoldFunc number
|> Array.rev
|> System.String
let b36Span (number:int) =
let digits = Math.Log(number, 36.0) |> ceil |> int
let mem = NativePtr.stackalloc<char>(digits) |> NativePtr.toVoidPtr
let nativeSpan = Span<char>(mem, digits)
let mutable (n:int) = number
for identifier = digits downto 1 do //identifier = 1 to digits do
nativeSpan[identifier-1] <- Chars.chars.[n%36]
n <- n/36
nativeSpan.ToString()
let toBase36 n =
let mutable num = n
let mutable r = 0
let mutable newNumber = ""
while num >= 36 do
r <- num % 36
newNumber <- string (chars.[r]) + newNumber
num <- num / 36
newNumber <- string (chars.[num]) + newNumber
newNumber
let toBase36Rec n =
let rec toBase36 n state =
if n < 36 then
string state
else
let r = n % 36
let newState = chars[r] :: state
toBase36 (n/36) newState
toBase36 n List.empty
[<SimpleJob(RuntimeMoniker.Net80)>]
type Benchmarks() =
[<Params(123, 245685, 3000000)>]
member val size = 0 with get, set
[<Benchmark>]
member __.B36Unfold () = b36Unfold __.size
[<Benchmark>]
member __.B36Span () = b36Span __.size
[<Benchmark>]
member __.B36Mutable () = toBase36 __.size
[<Benchmark>]
member __.B36Rec () = toBase36Rec __.size
BenchmarkRunner.Run<Benchmarks>() |> ignore
@1eyewonder
Copy link

1eyewonder commented Dec 31, 2024

As a heads up, I needed to add support around some of the outputs Math.Log outputs. This function also is only working for positive integers so depending on the developer's needs, this is something to keep in mind for edge cases.

let toBase36 (number: int) =
    let digits =
        match Math.Log(number, 36) with
        | Double.NaN
        | Double.PositiveInfinity
        | Double.NegativeInfinity
        | 0. -> 1
        | x when x = floor x -> int x + 1 // Handle power of 36
        | x -> ceil x |> int

    let mem = NativePtr.stackalloc<char> digits |> NativePtr.toVoidPtr
    let nativeSpan = Span<char>(mem, digits)
    let mutable n = number

    for identifier = digits - 1 downto 0 do
        nativeSpan[identifier] <- chars.AsSpan()[n % 36]
        n <- n / 36

    nativeSpan.ToString()

@voronoipotato
Copy link
Author

I would just make the function take an unsigned int personally. negative base36 is not valid for most consumers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment