如何在 F# 中通过 SRTP 添加与 int32 兼容且通用的基于闭包的数字运算支持
How to add an int32-compatible and versatile, closure-based numeric operation support through SRTP in 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 &&
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
但我真的想不通,有什么想法吗?
此外,想知道我的目标是否真的可行?
由于您的除数现在是非泛型的,您不能再使用被除数类型的除法运算符。相反,我认为最好的办法是将所有内容都转换为“安全”类型,使用安全类型的除法运算符,然后再转换回股息类型。这在转换过程中引入了 rounding/truncation 错误的可能性,但幸运的是,看起来我们可以使用 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
上下文:前几天(几周前,我正在玩一种算法,根据不同的类型提取给定数字的数字)。
注意:除了玩 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 &&
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
但我真的想不通,有什么想法吗?
此外,想知道我的目标是否真的可行?
由于您的除数现在是非泛型的,您不能再使用被除数类型的除法运算符。相反,我认为最好的办法是将所有内容都转换为“安全”类型,使用安全类型的除法运算符,然后再转换回股息类型。这在转换过程中引入了 rounding/truncation 错误的可能性,但幸运的是,看起来我们可以使用 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