Проблема, с которой вы столкнулись, заключается в том, что все ваши промежуточные вычисления неявно являются модом 2 64 , и, как правило, неверно, что
a · b mod m = (a· B mod 2 64 ) mod m
, это то, что вы рассчитываете.
Я не могу придумать простой способ сделать правильныйвычисление с использованием только 64-битных чисел, но вам не нужно переходить к bigints;если a
и b
имеют не более 64 бит, то их полный продукт имеет максимум 128 бит, поэтому вы можете отслеживать продукт в двух 64-битных целых числах (здесь связанных как пользовательская структура):
// bit width of a uint64, needed for mod calculation
let width =
let rec loop w = function
| 0uL -> w
| n -> loop (w+1) (n >>> 1)
loop 0
[<Struct; CustomComparison; CustomEquality>]
type UInt128 =
val hi : uint64
val lo : uint64
new (hi,lo) = { lo = lo; hi = hi }
new (lo) = { lo = lo; hi = 0uL }
static member (+)(x:UInt128, y:UInt128) =
if x.lo > 0xffffffffuL - y.lo then
UInt128(x.hi + y.hi + 1uL, x.lo + y.lo)
else
UInt128(x.hi + y.hi, x.lo + y.lo)
static member (-)(x:UInt128, y:UInt128) =
if y.lo > x.lo then
UInt128(x.hi - y.hi - 1uL, x.lo - y.lo)
else
UInt128(x.hi - y.hi, x.lo - y.lo)
static member ( * )(x:UInt128, y:UInt128) =
let a1 = ((x.lo &&& 0xffffffffuL) * (y.lo &&& 0xffffffffuL)) >>> 32
let a2 = (x.lo &&& 0xffffffffuL) * (y.lo >>> 32)
let a3 = (x.lo >>> 32) * (y.lo &&& 0xffffffffuL)
let sum = ((a1 + a2 + a3) >>> 32) + (x.lo >>> 32) * (y.lo >>> 32)
let sum =
if a2 > 0xffffffffffffffffuL - a1 || a1 + a2 > 0xffffffffffffffffuL - a3 then
0x100000000uL + sum
else
sum
UInt128(x.hi * y.lo + x.lo * y.hi + sum, x.lo * y.lo)
static member (>>>)(x:UInt128, n) =
UInt128(x.hi >>> n, x.lo >>> n)
static member (<<<)(x:UInt128, n) =
UInt128((x.hi <<< n) + (x.lo >>> (64 - n)), x.lo <<< n)
interface System.IComparable with
member x.CompareTo(y) =
match y with
| :? UInt128 as y ->
match x.hi.CompareTo(y.hi) with
| 0 -> x.lo.CompareTo(y.lo)
| n -> n
override x.Equals(y) =
match y with
| :? UInt128 as y -> x.hi = y.hi && x.lo = y.lo
| _ -> false
override x.GetHashCode() = x.hi.GetHashCode() + x.lo.GetHashCode() * 7
(* calculate mod via long-division *)
static member (%)(x:UInt128, d) =
let rec reduce (r:UInt128) d' =
if r.hi = 0uL then r.lo % d
else
let r' = if r < d' then r else r - d'
reduce r' (d' >>> 1)
let shift = width x.hi + (64 - width d)
reduce x (UInt128(0uL,d) <<< shift)
let mulMod m a b =
UInt128(a) * UInt128(b) % m
(* squareMod, powMod basically as before: *)
let squareMod m a = mulMod m a a
let powMod m = pow (mulMod m) (squareMod m) 1uL
let a = (pown 2uL 40) - 1uL
let b = (pown 2uL 32) - 1uL
let p = powMod a b 2
Сказав, что, поскольку bigint
s даст вам правильный ответ, почему бы просто не использовать bigint
s, чтобы выполнить промежуточный расчет и конвертировать в long в конце (который гарантированно будет без потерьпреобразование с учетом диапазона м)?Я подозреваю, что снижение производительности при использовании bigints должно быть приемлемым для большинства приложений (по сравнению с головной болью поддержки ваших собственных математических процедур).