Как добавить совместимую с int32 и универсальную поддержку числовых операций на основе закрытия с помощью SRTP в F#

#f#

Вопрос:

Контекст: на днях (несколько недель назад я играл с алгоритмом извлечения цифр для заданного числа, зависящего от разных типов).

Примечание: для этого нет никакой практической цели, кроме как играть с F# SRTP.

 type Numbers =
    static member Ten (_ : uint8)   = 10uy
    static member Ten (_ : uint16)  = 10us
    static member Ten (_ : uint32)  = 10ul
    static member Ten (_ : uint64)  = 10UL
    static member Ten (_ : int8)    = 10y
    static member Ten (_ : int16)   = 10s
    static member Ten (_ : int32)   = 10l
    static member Ten (_ : int64)   = 10L
    static member Ten (_ : single)  = 10f
    static member Ten (_ : double)  = 10.
    static member Ten (_ : decimal) = 10m
    // static member Ten (_ : bigint)  = 10I //bug?

let inline (/%) dividend divisor = let quotient = dividend / divisor in quotient, dividend - quotient * divisor

let inline divRemBy10 x =
    let inline call (_: ^T, x: ^I) = ((^T or ^I) : (static member Ten: _ -> _) x)
    x /% (call (Unchecked.defaultof<Numbers>, x))

[<RequireQualifiedAccess>]
module Digits =
    let inline (|Positive|Zero|Negative|) number =
        if number > LanguagePrimitives.GenericZero then Positive
        elif number = LanguagePrimitives.GenericZero then Zero
        else Negative

    let inline toDigits number =
        let generator oldQuotient =
            let newQuotient, newRemainder = divRemBy10 oldQuotient
            if newQuotient = LanguagePrimitives.GenericZero amp;amp;
               newRemainder = LanguagePrimitives.GenericZero
               then None
            else Some(newRemainder, newQuotient)
        let alternateNegative i digit =
            if i > LanguagePrimitives.GenericZero then digit * -LanguagePrimitives.GenericOne
            else digit
        match number with
        | Positive -> number |> Seq.unfold generator |> Seq.rev
        | Zero     -> Seq.singleton LanguagePrimitives.GenericZero
        | Negative -> number |> Seq.unfold generator |> Seq.rev |> Seq.mapi alternateNegative


[<EntryPoint>]
let main _ =
    [ -465l; 10l; 0l; -10l; 1l; 42l; Int32.MaxValue; Int32.MinValue ]
    |> List.map (fun number -> number, Digits.toDigits number |> String.join "")
    |> List.iter(fun (number, digitsString) -> printfn $"%d{number}=%s{digitsString}")

    [ 10y; 0y; -10y; 1y; 42y; SByte.MaxValue; SByte.MinValue ]
    |> List.map (fun number -> number, Digits.toDigits number |> String.join "")
    |> List.iter(fun (number, digitsString) -> printfn $"%d{number}=%s{digitsString}")

    [ 10s; 0s; -10s; 1s; 42s; Int16.MaxValue; Int16.MinValue ]
    |> List.map (fun number -> number, Digits.toDigits number |> String.join "")
    |> List.iter(fun (number, digitsString) -> printfn $"%d{number}=%s{digitsString}")

    [ 10L; 0L; -10L; 1L; 42L; Int64.MaxValue; Int64.MinValue ]
    |> List.map (fun number -> number, Digits.toDigits number |> String.join "")
    |> List.iter(fun (number, digitsString) -> printfn $"%d{number}=%s{digitsString}")
    0
 

До сих пор так хорошо, что приведенный выше код работает так, как ожидалось.

Тем не менее, мне интересно, как я могу его реорганизовать, чтобы извлечь выгоду из чего-то, что превратило int32 бы подобное, например 10 , в эквивалент реализации Ten функции для каждого базового числового примитива.

 let inline divRemBy x (i: int32) = ...
 

и на месте вызова это может стать тогда:

 divRemBy oldQuotient 10
 

Но я действительно не могу понять, как, есть идеи?

Кроме того, интересно, действительно ли то, к чему я стремлюсь, является чем-то выполнимым или нет?

Комментарии:

1. Есть а Math.DivRem и а BigInteger.DivRem , но вы, вероятно, уже знаете это.

Ответ №1:

Поскольку ваш делитель теперь не является универсальным, вы больше не можете использовать оператор деления типа дивидендов. Вместо этого, я думаю, что вам лучше всего преобразовать все в «безопасный» тип, использовать оператор разделения безопасного типа, а затем преобразовать обратно в тип дивидендов. Это создает возможность ошибок округления/усечения во время преобразований, но, к счастью, похоже, что мы можем использовать все ваши тестовые случаи int64 в качестве безопасного типа (включая bigint поддержку).

Во-первых, вот функция безопасного разделения:

 let div (convert, dividend: int64, divisor: int64) =
    let quotient = dividend / divisor
    convert quotient, convert (dividend - quotient * divisor)
 

Затем мы изменим Numbers так, чтобы он вызывал div правильную функцию преобразования для каждого типа дивидендов:

 type Numbers =
    static member Div (x: uint8, y: int64) = div (uint8, int64 x, y)
    static member Div (x: uint16, y: int64) = div (uint16, int64 x, y)
    static member Div (x: uint32, y: int64) = div (uint32, int64 x, y)
    static member Div (x: int8, y: int64) = div (int8, int64 x, y)
    static member Div (x: int16, y: int64) = div (int16, int64 x, y)
    static member Div (x: int32, y: int64) = div (int32, int64 x, y)
    static member Div (x: int64, y: int64) = div (int64, int64 x, y)
    static member Div (x: single, y: int64) = div (single, int64 x, y)
    static member Div (x: double, y: int64) = div (double, int64 x, y)
    static member Div (x: decimal, y: int64) = div (decimal, int64 x, y)
    static member Div (x: bigint, y: int64) = div (bigint, int64 x, y)
 

Теперь мы можем реализовать divRemBy так, как вы хотите:

 let inline divRemBy x (y: int) =
    let inline call (_: ^T, x: ^I) =
        ((^T or ^I) : (static member Div: _ * _ -> _) (x, int64 y))
    call (Unchecked.defaultof<Numbers>, x)
 

И сайт звонков также выглядит так, как вы хотите:

 let newQuotient, newRemainder = divRemBy oldQuotient 10
 

Для забавы я изменил базу на двоичную ( divRemBy oldQuotient 2 ), и я думаю, что результат все еще верен:

 -465=-111010001
10=1010
0=0
-10=-1010
1=1
42=101010
2147483647=1111111111111111111111111111111
-2147483648=-10000000000000000000000000000000
10=1010
0=0
-10=-1010
1=1
42=101010
127=1111111
-128=-10000000
10=1010
0=0
-10=-1010
1=1
42=101010
32767=111111111111111
-32768=-1000000000000000
10=1010
0=0
-10=-1010
1=1
42=101010
9223372036854775807=111111111111111111111111111111111111111111111111111111111111111
-9223372036854775808=-1000000000000000000000000000000000000000000000000000000000000000