#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