diff --git a/README.md b/README.md index cfde595..66f77aa 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,17 @@ # modular -A modular arithmetic library with an emphasis on speed -[![Status](https://github.com/stewi1014/modular/actions/workflows/go.yml/badge.svg)](https://github.com/stewi1014/modular/actions/workflows/go.yml) - -Float64 -[![GoDoc](https://godoc.org/github.com/stewi1014/modular/modular64?status.svg)](https://godoc.org/github.com/stewi1014/modular/modular64) +A math library with an emphasis on speed. -Float32 -[![GoDoc](https://godoc.org/github.com/stewi1014/modular/modular32?status.svg)](https://godoc.org/github.com/stewi1014/modular/modular32) +[![Status](https://github.com/stewi1014/modular/actions/workflows/go.yml/badge.svg)](https://github.com/stewi1014/modular/actions/workflows/go.yml) +[![Go Reference](https://pkg.go.dev/badge/github.com/stewi1014/modular.svg)](https://pkg.go.dev/github.com/stewi1014/modular) -Modular tries to leverage pre-computation as much as possible to allow direct computation in Congruent() and Index(), using [fastdiv] and pre-computed lookup tables. I can't test it on all hardware, but in principle should perform better than traditional modulo functions on all but the strangest of hardware. +Modular tries to leverage pre-computation as much as possible to allow direct computation in Mod() and Index(), using [fastdiv] and pre-computed lookup tables. I can't test it on all hardware, but in principle should perform better than traditional modulo functions on all but the strangest of hardware. For example, on my machine with a modulo of 1e-25; **float64** -| Number | Math.Mod | Modulus.Congruent | Indexer.Index | +| Number | Math.Mod | Modulus.Mod | Indexer.Index | | ------ | ------ | ------ | ------ | | 0 | 14.1 ns/op | 6.07 ns/op | 12.4 ns/op | | 2.5e-25 | 29.2 ns/op | 14.4 ns/op | 15.7 ns/op | @@ -25,7 +21,7 @@ For example, on my machine with a modulo of 1e-25; **float32** -| Number | Math.Mod | Modulus.Congruent | Indexer.Index | +| Number | Math.Mod | Modulus.Mod | Indexer.Index | | ------ | ------ | ------ | ------ | | 0 | 15.6 ns/op | 5.49 ns/op | 11.3 ns/op | | 2.5e-25 | 33.5 ns/op | 12.1 ns/op | 11.3 ns/op | @@ -34,7 +30,7 @@ For example, on my machine with a modulo of 1e-25; *** -I've use the name 'Congruent' as it's a more explicit definition of the function, and helps avoid confusion with other functions. It is the same as a euclidian 'modulo' function; that is, it finds the number satisfying '0 <= n < modulus' that is representative of the given number's congruency class, hence the name Congruent. +Modular implements the euclidian modulo function; that is, it finds the number satisfying '0 <= n < modulus' that is representative of the given number's congruency class. [fastdiv]: diff --git a/go.mod b/go.mod index 137374b..2c05098 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,7 @@ module github.com/stewi1014/modular -go 1.12 +go 1.21 require ( github.com/bmkessler/fastdiv v0.0.0-20190227075523-41d5178f2044 - github.com/chewxy/math32 v1.0.0 - github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a ) diff --git a/go.sum b/go.sum index ffb51f9..84c131e 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,2 @@ github.com/bmkessler/fastdiv v0.0.0-20190227075523-41d5178f2044 h1:8Rz0TcIbkvU+x53bDQgezQ3tbjrQSpZRr6h9JnR9lZU= github.com/bmkessler/fastdiv v0.0.0-20190227075523-41d5178f2044/go.mod h1:OI0uaNyGvxANSxteY6/mFRZs9EcQGqK30Bd1wqQj9zQ= -github.com/chewxy/math32 v1.0.0 h1:RTt2SACA7BTzvbsAKVQJLZpV6zY2MZw4bW9L2HEKkHg= -github.com/chewxy/math32 v1.0.0/go.mod h1:Miac6hA1ohdDUTagnvJy/q+aNnEk16qWUdb8ZVhvCN0= -github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a h1:yoAEv7yeWqfL/l9A/J5QOndXIJCldv+uuQB1DSNQbS0= -github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= -golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc= -golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/integer/sqrt.go b/integer/sqrt.go new file mode 100644 index 0000000..a529965 --- /dev/null +++ b/integer/sqrt.go @@ -0,0 +1,34 @@ +package integer + +// Sqrt returns the square root of n, rounding down and returning the remainder. +func Sqrt(n int) (sqrt int, remainder int) { + // TODO; optimise + + if n < 0 { + panic("square root of negative number") + } + + if n < 2 { + return n, 0 + } + + shift := 2 + for (n >> shift) != 0 { + shift += 2 + } + + result := 0 + for shift >= 0 { + result = result << 1 + + large_cand := result + 1 + + if large_cand*large_cand <= n>>shift { + result = large_cand + } + + shift -= 2 + } + + return result, n - (result * result) +} diff --git a/integer/sqrt_test.go b/integer/sqrt_test.go new file mode 100644 index 0000000..fb0acc3 --- /dev/null +++ b/integer/sqrt_test.go @@ -0,0 +1,43 @@ +package integer + +import "testing" + +// TODO; add more tests +func TestSqrt(t *testing.T) { + tests := []struct { + name string + arg int + wantSqrt int + wantRemainder int + }{ + { + name: "sqrt(4) = 2,0", + arg: 4, + wantSqrt: 2, + wantRemainder: 0, + }, + { + name: "sqrt(9) = 3,0", + arg: 9, + wantSqrt: 3, + wantRemainder: 0, + }, + { + name: "sqrt(10) = 3,1", + arg: 10, + wantSqrt: 3, + wantRemainder: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotSqrt, gotRemainder := Sqrt(tt.arg) + if gotSqrt != tt.wantSqrt { + t.Errorf("Sqrt() gotSqrt = %v, want %v", gotSqrt, tt.wantSqrt) + } + if gotRemainder != tt.wantRemainder { + t.Errorf("Sqrt() gotRemainder = %v, want %v", gotRemainder, tt.wantRemainder) + } + }) + } +} diff --git a/integeru64/sqrt.go b/integeru64/sqrt.go new file mode 100644 index 0000000..eb438f8 --- /dev/null +++ b/integeru64/sqrt.go @@ -0,0 +1,30 @@ +package integeru64 + +// Sqrt returns the square root of n, rounding down and returning the remainder. +func Sqrt(n uint64) (sqrt uint64, remainder uint64) { + // TODO; optimise + + if n < 2 { + return n, 0 + } + + shift := 2 + for (n >> shift) != 0 { + shift += 2 + } + + result := uint64(0) + for shift >= 0 { + result = result << 1 + + large_cand := result + 1 + + if large_cand*large_cand <= n>>shift { + result = large_cand + } + + shift -= 2 + } + + return result, n - (result * result) +} diff --git a/integeru64/sqrt_test.go b/integeru64/sqrt_test.go new file mode 100644 index 0000000..2c8deaa --- /dev/null +++ b/integeru64/sqrt_test.go @@ -0,0 +1,43 @@ +package integeru64 + +import "testing" + +// TODO; add more tests +func TestSqrt(t *testing.T) { + tests := []struct { + name string + arg uint64 + wantSqrt uint64 + wantRemainder uint64 + }{ + { + name: "sqrt(4) = 2,0", + arg: 4, + wantSqrt: 2, + wantRemainder: 0, + }, + { + name: "sqrt(9) = 3,0", + arg: 9, + wantSqrt: 3, + wantRemainder: 0, + }, + { + name: "sqrt(10) = 3,1", + arg: 10, + wantSqrt: 3, + wantRemainder: 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotSqrt, gotRemainder := Sqrt(tt.arg) + if gotSqrt != tt.wantSqrt { + t.Errorf("Sqrt() gotSqrt = %v, want %v", gotSqrt, tt.wantSqrt) + } + if gotRemainder != tt.wantRemainder { + t.Errorf("Sqrt() gotRemainder = %v, want %v", gotRemainder, tt.wantRemainder) + } + }) + } +} diff --git a/modular32/float32.go b/modular32/float32.go index e859719..547b5f7 100644 --- a/modular32/float32.go +++ b/modular32/float32.go @@ -2,11 +2,16 @@ package modular32 import ( "math/bits" - - math "github.com/chewxy/math32" + "unsafe" ) const ( + nan = 0x7FE00000 + posinf = 0x7F800000 + neginf = 0xFF800000 + + MaxFloat = 0x1p127 * (1 + (1 - 0x1p-23)) + fExponentBits = 8 fFractionBits = 23 fTotalBits = 32 @@ -27,10 +32,10 @@ func shiftSub(up, down uint, n uint32) uint32 { return n >> (down - up) } -// frexp splits a float into it's exponent and fraction component. Sign bit is discarded. +// Frexp splits a float into it's exponent and fraction component. Sign bit is discarded. // The 24th implied bit is placed in the fraction if appropriate -func frexp(f float32) (uint32, uint) { - fbits := math.Float32bits(f) +func Frexp(f float32) (uint32, uint) { + fbits := ToBits(f) exp := uint((fbits & fExponentMask) >> fFractionBits) if exp == 0 { return fbits & fFractionMask, 0 @@ -38,11 +43,11 @@ func frexp(f float32) (uint32, uint) { return (fbits & fFractionMask) | (1 << fFractionBits), exp } -// ldexp assembles a float from an exponent and fraction component. Sign is ignored. +// Ldexp assembles a float from an exponent and fraction component. Sign is ignored. // Expects the 24th implied bit to be set if appropriate. -func ldexp(fr uint32, exp uint) float32 { +func Ldexp(fr uint32, exp uint) float32 { if exp == 0 || fr == 0 { - return math.Float32frombits(fr & fFractionMask) + return FromBits(fr & fFractionMask) } shift := uint(bits.LeadingZeros32(fr) - fExponentBits) if shift >= exp { @@ -52,5 +57,35 @@ func ldexp(fr uint32, exp uint) float32 { exp -= uint(shift) } fr = fr << shift - return math.Float32frombits((uint32(exp) << fFractionBits) | (fr & fFractionMask)) + return FromBits((uint32(exp) << fFractionBits) | (fr & fFractionMask)) +} + +func ToBits(n float32) uint32 { + return *(*uint32)(unsafe.Pointer(&n)) +} + +func FromBits(bits uint32) float32 { + return *(*float32)(unsafe.Pointer(&bits)) +} + +func Abs(n float32) float32 { + if n <= 0 { + return -n + } + return n +} + +func NaN() float32 { + return FromBits(nan) +} + +func IsInf(n float32) bool { + return n > MaxFloat || n < -MaxFloat +} + +func Inf(sign int) float32 { + if sign < 0 { + return FromBits(neginf) + } + return FromBits(posinf) } diff --git a/modular32/indexer.go b/modular32/indexer.go index 7b9d94e..041147a 100644 --- a/modular32/indexer.go +++ b/modular32/indexer.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/bmkessler/fastdiv" - math "github.com/chewxy/math32" ) // Error types @@ -18,12 +17,13 @@ var ( // index must not be larger than 2**16, and modulus must be a normalised float // // Special cases: -// NewIndexer(m, 0) = panic(integer divide by zero) -// NewIndexer(m, i > 2**16) = ErrBadIndex -// NewIndexer(0, i) = ErrBadModulo -// NewIndexer(±Inf, i) = ErrBadModulo -// NewIndexer(NaN, i) = ErrBadModulo -// NewIndexer(m, i) = ErrBadModulo for |m| < 2**-126 +// +// NewIndexer(m, 0) = panic(integer divide by zero) +// NewIndexer(m, i > 2**16) = ErrBadIndex +// NewIndexer(0, i) = ErrBadModulo +// NewIndexer(±Inf, i) = ErrBadModulo +// NewIndexer(NaN, i) = ErrBadModulo +// NewIndexer(m, i) = ErrBadModulo for |m| < 2**-126 func NewIndexer(modulus float32, index int) (Indexer, error) { mod := NewModulus(modulus) return mod.NewIndexer(index) @@ -31,14 +31,14 @@ func NewIndexer(modulus float32, index int) (Indexer, error) { // NewIndexer creates a new indexer from the Modulus. func (m Modulus) NewIndexer(index int) (Indexer, error) { - if math.IsInf(m.mod, 0) || math.IsNaN(m.mod) || m.exp == 0 { + if IsInf(m.mod) || m.mod != m.mod || m.exp == 0 { return Indexer{}, ErrBadModulo } if index > (1<<16) || index < 1 { return Indexer{}, ErrBadIndex } - modfr, _ := frexp(m.mod) + modfr, _ := Frexp(m.mod) r := modfr << fExponentBits //r - range; is shifted fExponentBits to get a little more rDivisor := r / uint32(index) return Indexer{ @@ -63,14 +63,15 @@ type Indexer struct { // Otherwise, it always satisfies 0 <= num < index // // Special cases: -// Index(NaN) = index -// Index(±Inf) = index +// +// Index(NaN) = index +// Index(±Inf) = index func (i Indexer) Index(n float32) int { - if math.IsNaN(n) || math.IsInf(n, 0) || i.i == 0 { + if n != n || IsInf(n) || i.i == 0 { return i.i } - nfr, nexp := frexp(n) + nfr, nexp := Frexp(n) var nr uint32 switch { case n > i.mod: diff --git a/modular32/indexer_test.go b/modular32/indexer_test.go index e27e9a2..d32f475 100644 --- a/modular32/indexer_test.go +++ b/modular32/indexer_test.go @@ -4,7 +4,6 @@ import ( "fmt" "testing" - math "github.com/chewxy/math32" "github.com/stewi1014/modular/modular32" ) @@ -140,7 +139,7 @@ func TestIndexer_Index(t *testing.T) { { name: "Infinite Modulus", args: args{ - modulus: math.Inf(1), + modulus: modular32.Inf(1), index: 100, n: -2, }, @@ -152,7 +151,7 @@ func TestIndexer_Index(t *testing.T) { { name: "NaN Modulus", args: args{ - modulus: math.NaN(), + modulus: modular32.NaN(), index: 10054, n: -2, }, @@ -166,7 +165,7 @@ func TestIndexer_Index(t *testing.T) { args: args{ modulus: 23, index: 10054, - n: math.NaN(), + n: modular32.NaN(), }, want: want{ n: 10054, diff --git a/modular32/modulus.go b/modular32/modulus.go index 78a4a83..56be08c 100644 --- a/modular32/modulus.go +++ b/modular32/modulus.go @@ -2,16 +2,15 @@ package modular32 import ( "github.com/bmkessler/fastdiv" - math "github.com/chewxy/math32" ) // NewModulus creates a new Modulus. -// An Infinite modulus has no effect other than to waste CPU time // // Special cases: -// NewModulus(0) = panic(integer divide by zero) +// +// NewModulus(0) = panic(integer divide by zero) func NewModulus(modulus float32) Modulus { - modfr, modexp := frexp(modulus) + modfr, modexp := Frexp(modulus) powers := make([]uint64, fMaxExp-modexp) r := uint32(1) @@ -28,7 +27,7 @@ func NewModulus(modulus float32) Modulus { mod := Modulus{ fd: fastdiv.NewUint64(uint64(modfr)), powers: powers, - mod: math.Abs(modulus), + mod: Abs(modulus), exp: modexp, } @@ -38,7 +37,7 @@ func NewModulus(modulus float32) Modulus { // Modulus defines a modulus. // It offers greater performance than traditional floating point modulo calculations by pre-computing the inverse of the modulus's fractional component, // and pre-computing a lookup table for different exponents in the given modulus, allowing direct computation of n mod m - no iteration or recursion is used. -// This obviously adds overhead to the creation of a new Modulus, but quickly breaks even after a few calls to Congruent. +// This obviously adds overhead to the creation of a new Modulus, but quickly breaks even after a few calls to Mod. type Modulus struct { fd fastdiv.Uint64 powers []uint64 @@ -47,13 +46,13 @@ type Modulus struct { } // Mod returns the modulus -func (m Modulus) Mod() float32 { +func (m Modulus) Modulus() float32 { return m.mod } -// Dist returns the distance and direction of n1 to n2. +// Dist returns the shortest distance from n1 to n2. func (m Modulus) Dist(n1, n2 float32) float32 { - d := m.Congruent(n2 - n1) + d := m.Mod(n2 - n1) if d > m.mod/2 { return d - m.mod } @@ -65,20 +64,21 @@ func (m Modulus) GetCongruent(n1, n2 float32) float32 { return n1 - m.Dist(n2, n1) } -// Congruent returns n mod m. +// Mod returns n mod m. // // Special cases: -// Modulus{NaN}.Congruent(n) = NaN -// Modulus{±Inf}.Congruent(n>=0) = n -// Modulus{±Inf}.Congruent(n<0) = +Inf -// Modulus{m}.Congruent(±Inf) = NaN -// Modulus{m}.Congruent(NaN) = NaN -func (m Modulus) Congruent(n float32) float32 { +// +// Modulus{NaN}.Mod(n) = NaN +// Modulus{±Inf}.Mod(n>=0) = n +// Modulus{±Inf}.Mod(n<0) = +Inf +// Modulus{m}.Mod(±Inf) = NaN +// Modulus{m}.Mod(NaN) = NaN +func (m Modulus) Mod(n float32) float32 { if m.mod == 0 || m.mod != m.mod { // 0 or NaN modulus - return math.NaN() + return NaN() } - nfr, nexp := frexp(n) + nfr, nexp := Frexp(n) if n < m.mod && n > -m.mod { if n < 0 { @@ -88,7 +88,7 @@ func (m Modulus) Congruent(n float32) float32 { } if nexp == fMaxExp { - return math.NaN() + return NaN() } expdiff := nexp - m.exp @@ -98,7 +98,7 @@ func (m Modulus) Congruent(n float32) float32 { nfr = m.modExp(nfr, expdiff) - r := ldexp(nfr, m.exp) + r := Ldexp(nfr, m.exp) if n < 0 && r != 0 { r = m.mod - r // correctly handle negatives diff --git a/modular32/modulus_test.go b/modular32/modulus_test.go index 51ea59f..5d06c0f 100644 --- a/modular32/modulus_test.go +++ b/modular32/modulus_test.go @@ -4,12 +4,9 @@ import ( "fmt" "testing" - math "github.com/chewxy/math32" "github.com/stewi1014/modular/modular32" ) -const randomTestNum = 20000 - var ( float32Sink float32 ) @@ -41,15 +38,9 @@ func TestModulus_Congruent(t *testing.T) { }, { name: "Very small test", - modulus: math.Float32frombits(4144), - arg: math.Float32frombits(123445), - want: math.Float32frombits(3269), - }, - { - name: "very big test with small modulus", - modulus: 10, - arg: 456897613245865, - want: math.Mod(456897613245865, 10), + modulus: modular32.FromBits(4144), + arg: modular32.FromBits(123445), + want: modular32.FromBits(3269), }, { name: "Negative number", @@ -63,60 +54,60 @@ func TestModulus_Congruent(t *testing.T) { arg: -3, want: 2, }, - // Modulus{NaN}.Congruent(n) = NaN + // Modulus{NaN}.Mod(n) = NaN { name: "NaN modulus", - modulus: math.NaN(), + modulus: modular32.NaN(), arg: 0, - want: math.NaN(), + want: modular32.NaN(), }, - // Modulus{±Inf}.Congruent(n>=0) = n + // Modulus{±Inf}.Mod(n>=0) = n { name: "Inf modulus, positive number", - modulus: math.Inf(1), + modulus: modular32.Inf(1), arg: 0, want: 0, }, - // Modulus{±Inf}.Congruent(n<0) = +Inf + // Modulus{±Inf}.Mod(n<0) = +Inf { name: "Inf modulus, negative number", - modulus: math.Inf(1), + modulus: modular32.Inf(1), arg: -1, - want: math.Inf(1), + want: modular32.Inf(1), }, - // Modulus{m}.Congruent(±Inf) = NaN + // Modulus{m}.Mod(±Inf) = NaN { name: "Inf number", modulus: 1, - arg: math.Inf(1), - want: math.NaN(), + arg: modular32.Inf(1), + want: modular32.NaN(), }, - // Modulus{m}.Congruent(NaN) = NaN + // Modulus{m}.Mod(NaN) = NaN { name: "NaN number", modulus: 1, - arg: math.NaN(), - want: math.NaN(), + arg: modular32.NaN(), + want: modular32.NaN(), }, { name: "Denormalised edge case", - modulus: math.Ldexp(1, -126), - arg: math.Ldexp(1.003, -126), - want: math.Mod(math.Ldexp(1.003, -126), math.Ldexp(1, -126)), + modulus: 1.1754944e-38, + arg: 1.1790209e-38, + want: 3.5265e-41, }, { name: "Denormalised edge case2", - modulus: math.Ldexp(1, -127), - arg: math.Ldexp(1.003, -126), - want: math.Mod(math.Ldexp(1.003, -126), math.Ldexp(1, -127)), + modulus: 5.877472e-39, + arg: 5.877472e-39, + want: 3.5265e-41, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := modular32.NewModulus(tt.modulus) - got := m.Congruent(tt.arg) - if got != tt.want && !(math.IsNaN(got) && math.IsNaN(tt.want)) { - t.Errorf("Modulus{%v}.Congruent(%v) = %v, want %v", tt.modulus, tt.arg, got, tt.want) + got := m.Mod(tt.arg) + if got != tt.want && !(got != got && tt.want != tt.want) { + t.Errorf("Modulus{%v}.Mod(%v) = %v, want %v", tt.modulus, tt.arg, got, tt.want) } }) } @@ -173,32 +164,32 @@ func TestModulus_Dist(t *testing.T) { name: "NaN args", modulus: 100, args: args{ - n1: math.NaN(), + n1: modular32.NaN(), n2: 30, }, - want: math.NaN(), + want: modular32.NaN(), }, { name: "NaN args", modulus: 100, args: args{ n1: 20, - n2: math.NaN(), + n2: modular32.NaN(), }, - want: math.NaN(), + want: modular32.NaN(), }, { name: "NaN modulus", - modulus: math.NaN(), + modulus: modular32.NaN(), args: args{ n1: 20, n2: 30, }, - want: math.NaN(), + want: modular32.NaN(), }, { name: "Inf modulus", - modulus: math.Inf(1), + modulus: modular32.Inf(1), args: args{ n1: 20, n2: 30, @@ -209,17 +200,17 @@ func TestModulus_Dist(t *testing.T) { name: "Inf arg", modulus: 100, args: args{ - n1: math.Inf(1), + n1: modular32.Inf(1), n2: 30, }, - want: math.NaN(), + want: modular32.NaN(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := modular32.NewModulus(tt.modulus) got := m.Dist(tt.args.n1, tt.args.n2) - if got != tt.want && !(math.IsNaN(got) && math.IsNaN(tt.want)) { + if got != tt.want && !(got != got && tt.want != tt.want) { t.Errorf("Modulus.Dist(%v, %v) = %v, want %v (mod %v)", tt.args.n1, tt.args.n2, got, tt.want, tt.modulus) } }) @@ -287,7 +278,7 @@ func TestModulus_GetCongruent(t *testing.T) { func TestModulus_Misc(t *testing.T) { t.Run("Mod() test", func(t *testing.T) { m := modular32.NewModulus(15) - got := m.Mod() + got := m.Modulus() if got != 15 { t.Errorf("Modulus.Mod() = %v, want %v", got, 15) } @@ -306,18 +297,104 @@ func BenchmarkMath_Mod(b *testing.B) { for _, n := range benchmarks { b.Run(fmt.Sprintf("Math.Mod(%v)", n), func(b *testing.B) { for i := 0; i < b.N; i++ { - float32Sink = math.Mod(n, benchmarkModulo) + float32Sink = math32mod(n, benchmarkModulo) } }) } } +func math32mod(x, y float32) float32 { + if y == 0 || modular32.IsInf(x) || x != x || y != y { + return modular32.NaN() + } + y = modular32.Abs(y) + + yfr, yexp := math32frexp(y) + r := x + if x < 0 { + r = -x + } + + for r >= y { + rfr, rexp := math32frexp(r) + if rfr < yfr { + rexp = rexp - 1 + } + r = r - math32ldexp(y, rexp-yexp) + } + if x < 0 { + r = -r + } + return r +} + +func math32ldexp(frac float32, exp int) float32 { + // special cases + switch { + case frac == 0: + return frac // correctly return -0 + case modular32.IsInf(frac) || frac != frac: + return frac + } + frac, e := math32normalize(frac) + exp += e + x := modular32.ToBits(frac) + exp += int(x>>23)&0xFF - 127 + if exp < -149 { + return math32copysign(0, frac) // underflow + } + if exp > 127 { // overflow + if frac < 0 { + return modular32.Inf(-1) + } + return modular32.Inf(1) + } + var m float32 = 1 + if exp < -(127 - 1) { // denormal + exp += 23 + m = 1.0 / (1 << 23) // 1/(2**-23) + } + x &^= 0xFF << 23 + x |= uint32(exp+127) << 23 + return m * modular32.FromBits(x) +} + +func math32copysign(x, y float32) float32 { + const sign = 1 << 31 + return modular32.FromBits(modular32.ToBits(x)&^sign | modular32.ToBits(y)&sign) +} + +func math32frexp(f float32) (frac float32, exp int) { + // special cases + switch { + case f == 0: + return f, 0 // correctly return -0 + case modular32.IsInf(f) || f != f: + return f, 0 + } + f, exp = math32normalize(f) + x := modular32.ToBits(f) + exp += int((x>>23)&0xFF) - 127 + 1 + x &^= 0xFF << 23 + x |= (-1 + 127) << 23 + frac = modular32.FromBits(x) + return +} + +func math32normalize(x float32) (y float32, exp int) { + const SmallestNormal = 1.1754943508222875079687365e-38 // 2**-(127 - 1) + if modular32.Abs(x) < SmallestNormal { + return x * (1 << 23), -23 + } + return x, 0 +} + func BenchmarkModulus(b *testing.B) { for _, n := range benchmarks { - b.Run(fmt.Sprintf("Congruent(%v)", n), func(b *testing.B) { + b.Run(fmt.Sprintf("Mod(%v)", n), func(b *testing.B) { m := modular32.NewModulus(benchmarkModulo) for i := 0; i < b.N; i++ { - float32Sink = m.Congruent(n) + float32Sink = m.Mod(n) } }) } diff --git a/modular32/vec.go b/modular32/vec.go deleted file mode 100644 index 6f2890a..0000000 --- a/modular32/vec.go +++ /dev/null @@ -1,137 +0,0 @@ -package modular32 - -import ( - mgl "github.com/go-gl/mathgl/mgl32" -) - -// NewVec2Modulus creates a new 2d Vector Modulus -func NewVec2Modulus(vec mgl.Vec2) Vec2Modulus { - return Vec2Modulus{ - x: NewModulus(vec[0]), - y: NewModulus(vec[1]), - } -} - -// Vec2Modulus defines a modulus for 2d vectors -type Vec2Modulus struct { - x Modulus - y Modulus -} - -// Congruent performs Congruent() on all axis -func (m Vec2Modulus) Congruent(vec mgl.Vec2) mgl.Vec2 { - return mgl.Vec2{ - m.x.Congruent(vec[0]), - m.y.Congruent(vec[1]), - } -} - -// Dist returns the distance and direction of v1 to v2 -// It picks the shortest distance. -func (m Vec2Modulus) Dist(v1, v2 mgl.Vec2) mgl.Vec2 { - return mgl.Vec2{ - m.x.Dist(v1[0], v2[0]), - m.y.Dist(v1[1], v2[1]), - } -} - -// GetCongruent returns the vector closest to v1 that is congruent to v2 -func (m Vec2Modulus) GetCongruent(v1, v2 mgl.Vec2) mgl.Vec2 { - return mgl.Vec2{ - m.x.GetCongruent(v1[0], v2[0]), - m.y.GetCongruent(v1[1], v2[1]), - } -} - -// NewVec3Modulus creates a new 3d Vector Modulus -func NewVec3Modulus(vec mgl.Vec3) Vec3Modulus { - return Vec3Modulus{ - x: NewModulus(vec[0]), - y: NewModulus(vec[1]), - z: NewModulus(vec[2]), - } -} - -// Vec3Modulus defines a modulus for 3d vectors -type Vec3Modulus struct { - x Modulus - y Modulus - z Modulus -} - -// Congruent performs Congruent() on all axis -func (m Vec3Modulus) Congruent(vec mgl.Vec3) mgl.Vec3 { - return mgl.Vec3{ - m.x.Congruent(vec[0]), - m.y.Congruent(vec[1]), - m.z.Congruent(vec[2]), - } -} - -// Dist returns the distance and direction of v1 to v2 -// It picks the shortest distance. -func (m Vec3Modulus) Dist(v1, v2 mgl.Vec3) mgl.Vec3 { - return mgl.Vec3{ - m.x.Dist(v1[0], v2[0]), - m.y.Dist(v1[1], v2[1]), - m.z.Dist(v1[2], v2[2]), - } -} - -// GetCongruent returns the vector closest to v1 that is congruent to v2 -func (m Vec3Modulus) GetCongruent(v1, v2 mgl.Vec3) mgl.Vec3 { - return mgl.Vec3{ - m.x.GetCongruent(v1[0], v2[0]), - m.y.GetCongruent(v1[1], v2[1]), - m.z.GetCongruent(v1[2], v2[2]), - } -} - -// NewVec4Modulus creates a new 4d Vector Modulus -func NewVec4Modulus(vec mgl.Vec4) Vec4Modulus { - return Vec4Modulus{ - x: NewModulus(vec[0]), - y: NewModulus(vec[1]), - z: NewModulus(vec[2]), - w: NewModulus(vec[3]), - } -} - -// Vec4Modulus defines a modulus for 4d vectors -type Vec4Modulus struct { - x Modulus - y Modulus - z Modulus - w Modulus -} - -// Congruent performs Congruent() on all axis -func (m Vec4Modulus) Congruent(vec mgl.Vec4) mgl.Vec4 { - return mgl.Vec4{ - m.x.Congruent(vec[0]), - m.y.Congruent(vec[1]), - m.z.Congruent(vec[2]), - m.w.Congruent(vec[3]), - } -} - -// Dist returns the distance and direction of v1 to v2 -// It picks the shortest distance. -func (m Vec4Modulus) Dist(v1, v2 mgl.Vec4) mgl.Vec4 { - return mgl.Vec4{ - m.x.Dist(v1[0], v2[0]), - m.y.Dist(v1[1], v2[1]), - m.z.Dist(v1[2], v2[2]), - m.z.Dist(v1[3], v2[3]), - } -} - -// GetCongruent returns the vector closest to v1 that is congruent to v2 -func (m Vec4Modulus) GetCongruent(v1, v2 mgl.Vec4) mgl.Vec4 { - return mgl.Vec4{ - m.x.GetCongruent(v1[0], v2[0]), - m.y.GetCongruent(v1[1], v2[1]), - m.z.GetCongruent(v1[2], v2[2]), - m.w.GetCongruent(v1[3], v2[3]), - } -} diff --git a/modular64/float64.go b/modular64/float64.go index 3574c03..dbbbb75 100644 --- a/modular64/float64.go +++ b/modular64/float64.go @@ -1,11 +1,17 @@ package modular64 import ( - "math" "math/bits" + "unsafe" ) const ( + nan = 0x7FF8000000000001 + posinf = 0x7FF0000000000000 + neginf = 0xFFF0000000000000 + + MaxFloat = 0x1p1023 * (1 + (1 - 0x1p-52)) + fExponentBits = 11 fFractionBits = 52 fTotalBits = 64 @@ -29,7 +35,7 @@ func shiftSub(up, down uint, n uint64) uint64 { // frexp splits a float into it's exponent and fraction component. Sign bit is discarded. // The 53rd implied bit is placed in the fraction if appropriate func frexp(f float64) (uint64, uint) { - fbits := math.Float64bits(f) + fbits := ToBits(f) exp := uint((fbits & fExponentMask) >> fFractionBits) if exp == 0 { return fbits & fFractionMask, 0 @@ -41,7 +47,7 @@ func frexp(f float64) (uint64, uint) { // Expects the 53rd implied bit to be set if appropriate. func ldexp(fr uint64, exp uint) float64 { if exp == 0 || fr == 0 { - return math.Float64frombits(fr & fFractionMask) + return FromBits(fr & fFractionMask) } shift := uint(bits.LeadingZeros64(fr) - fExponentBits) if shift >= exp { @@ -51,5 +57,35 @@ func ldexp(fr uint64, exp uint) float64 { exp -= uint(shift) } fr = fr << shift - return math.Float64frombits((uint64(exp) << fFractionBits) | (fr & fFractionMask)) + return FromBits((uint64(exp) << fFractionBits) | (fr & fFractionMask)) +} + +func ToBits(n float64) uint64 { + return *(*uint64)(unsafe.Pointer(&n)) +} + +func FromBits(bits uint64) float64 { + return *(*float64)(unsafe.Pointer(&bits)) +} + +func Abs(n float64) float64 { + if n <= 0 { + return -n + } + return n +} + +func NaN() float64 { + return FromBits(nan) +} + +func IsInf(n float64) bool { + return n > MaxFloat || n < -MaxFloat +} + +func Inf(sign int) float64 { + if sign < 0 { + return FromBits(neginf) + } + return FromBits(posinf) } diff --git a/modular64/indexer.go b/modular64/indexer.go index d8d49dc..e48181f 100644 --- a/modular64/indexer.go +++ b/modular64/indexer.go @@ -2,7 +2,6 @@ package modular64 import ( "errors" - "math" "github.com/bmkessler/fastdiv" ) @@ -18,12 +17,13 @@ var ( // index must not be larger than 2**32, and modulus must be a normalised float. // // Special cases: -// NewIndexer(m, 0) = panic(integer divide by zero) -// NewIndexer(m, i > 2**32) = ErrBadIndex -// NewIndexer(0, i) = ErrBadModulo -// NewIndexer(±Inf, i) = ErrBadModulo -// NewIndexer(NaN, i) = ErrBadModulo -// NewIndexer(m, i) = ErrBadModulo for |m| < 2**-1022 +// +// NewIndexer(m, 0) = panic(integer divide by zero) +// NewIndexer(m, i > 2**32) = ErrBadIndex +// NewIndexer(0, i) = ErrBadModulo +// NewIndexer(±Inf, i) = ErrBadModulo +// NewIndexer(NaN, i) = ErrBadModulo +// NewIndexer(m, i) = ErrBadModulo for |m| < 2**-1022 func NewIndexer(modulus float64, index int) (Indexer, error) { mod := NewModulus(modulus) return mod.NewIndexer(index) @@ -31,7 +31,7 @@ func NewIndexer(modulus float64, index int) (Indexer, error) { // NewIndexer creates a new indexer from the Modulus. func (m Modulus) NewIndexer(index int) (Indexer, error) { - if math.IsInf(m.mod, 0) || math.IsNaN(m.mod) || m.exp == 0 { + if IsInf(m.mod) || m.mod != m.mod || m.exp == 0 { return Indexer{}, ErrBadModulo } if index > (1<<32) || index < 1 { @@ -63,10 +63,11 @@ type Indexer struct { // Otherwise, it always satisfies 0 <= num < index // // Special cases: -// Index(NaN) = index -// Index(±Inf) = index +// +// Index(NaN) = index +// Index(±Inf) = index func (i Indexer) Index(n float64) int { - if math.IsNaN(n) || math.IsInf(n, 0) || i.i == 0 { + if n != n || IsInf(n) || i.i == 0 { return i.i } diff --git a/modular64/indexer_test.go b/modular64/indexer_test.go index 4980411..040c16b 100644 --- a/modular64/indexer_test.go +++ b/modular64/indexer_test.go @@ -2,7 +2,6 @@ package modular64_test import ( "fmt" - "math" "testing" "github.com/stewi1014/modular/modular64" @@ -128,7 +127,7 @@ func TestIndexer_Index(t *testing.T) { { name: "Infinite Modulus", args: args{ - modulus: math.Inf(1), + modulus: modular64.Inf(1), index: 100, n: -2, }, @@ -140,7 +139,7 @@ func TestIndexer_Index(t *testing.T) { { name: "NaN Modulus", args: args{ - modulus: math.NaN(), + modulus: modular64.NaN(), index: 10054, n: -2, }, @@ -154,7 +153,7 @@ func TestIndexer_Index(t *testing.T) { args: args{ modulus: 23, index: 10054, - n: math.NaN(), + n: modular64.NaN(), }, want: want{ n: 10054, diff --git a/modular64/modulus.go b/modular64/modulus.go index 68efc7c..0446f64 100644 --- a/modular64/modulus.go +++ b/modular64/modulus.go @@ -1,7 +1,6 @@ package modular64 import ( - "math" "math/bits" "github.com/bmkessler/fastdiv" @@ -9,10 +8,9 @@ import ( // NewModulus creates a new Modulus. // -// An Infinite modulus has no effect other than to waste CPU time. -// // Special cases: -// NewModulus(0) = panic(integer divide by zero) +// +// NewModulus(0) = panic(integer divide by zero) func NewModulus(modulus float64) Modulus { modfr, modexp := frexp(modulus) fd := fastdiv.NewUint64(modfr) @@ -37,7 +35,7 @@ func NewModulus(modulus float64) Modulus { mod := Modulus{ fd: fastdiv.NewUint64(modfr), powers: powers, - mod: math.Abs(modulus), + mod: Abs(modulus), fr: modfr, exp: modexp, } @@ -56,14 +54,14 @@ type Modulus struct { exp uint } -// Mod returns the modulus. -func (m Modulus) Mod() float64 { +// Modulus returns the modulus. +func (m Modulus) Modulus() float64 { return m.mod } // Dist returns the distance and direction of n1 to n2. func (m Modulus) Dist(n1, n2 float64) float64 { - d := m.Congruent(n2 - n1) + d := m.Mod(n2 - n1) if d > m.mod/2 { return d - m.mod } @@ -75,17 +73,18 @@ func (m Modulus) GetCongruent(n1, n2 float64) float64 { return n1 - m.Dist(n2, n1) } -// Congruent returns n mod m. +// Mod returns n mod m. // // Special cases: -// Modulus{NaN}.Congruent(n) = NaN -// Modulus{±Inf}.Congruent(n>=0) = n -// Modulus{±Inf}.Congruent(n<0) = +Inf -// Modulus{m}.Congruent(±Inf) = NaN -// Modulus{m}.Congruent(NaN) = NaN -func (m Modulus) Congruent(n float64) float64 { +// +// Modulus{NaN}.Mod(n) = NaN +// Modulus{±Inf}.Mod(n>=0) = n +// Modulus{±Inf}.Mod(n<0) = +Inf +// Modulus{m}.Mod(±Inf) = NaN +// Modulus{m}.Mod(NaN) = NaN +func (m Modulus) Mod(n float64) float64 { if m.mod == 0 || m.mod != m.mod { // 0 or NaN modulus - return math.NaN() + return NaN() } nfr, nexp := frexp(n) @@ -98,7 +97,7 @@ func (m Modulus) Congruent(n float64) float64 { } if nexp == fMaxExp { - return math.NaN() + return NaN() } expdiff := nexp - m.exp diff --git a/modular64/modulus_test.go b/modular64/modulus_test.go index e0b7e08..1451e74 100644 --- a/modular64/modulus_test.go +++ b/modular64/modulus_test.go @@ -8,8 +8,6 @@ import ( "github.com/stewi1014/modular/modular64" ) -const randomTestNum = 20000 - var ( float64Sink float64 ) @@ -41,15 +39,15 @@ func TestModulus_Congruent(t *testing.T) { }, { name: "Very small test", - modulus: math.Float64frombits(4144), - arg: math.Float64frombits(123445), - want: math.Float64frombits(3269), + modulus: modular64.FromBits(4144), + arg: modular64.FromBits(123445), + want: modular64.FromBits(3269), }, { name: "very big test with small modulus", modulus: 10, arg: 456897613245865, - want: math.Mod(456897613245865, 10), + want: 5, }, { name: "Negative number", @@ -66,69 +64,69 @@ func TestModulus_Congruent(t *testing.T) { // Modulus{NaN}.Congruent(n) = NaN { name: "NaN modulus", - modulus: math.NaN(), + modulus: modular64.NaN(), arg: 0, - want: math.NaN(), + want: modular64.NaN(), }, // Modulus{±Inf}.Congruent(n>=0) = n { name: "Inf modulus, positive number", - modulus: math.Inf(1), + modulus: modular64.Inf(1), arg: 0, want: 0, }, // Modulus{±Inf}.Congruent(n<0) = +Inf { name: "Inf modulus, negative number", - modulus: math.Inf(1), + modulus: modular64.Inf(1), arg: -1, - want: math.Inf(1), + want: modular64.Inf(1), }, // Modulus{m}.Congruent(±Inf) = NaN { name: "Inf number", modulus: 1, - arg: math.Inf(1), - want: math.NaN(), + arg: modular64.Inf(1), + want: modular64.NaN(), }, // Modulus{m}.Congruent(NaN) = NaN { name: "NaN number", modulus: 1, - arg: math.NaN(), - want: math.NaN(), + arg: modular64.NaN(), + want: modular64.NaN(), }, { name: "Denormalised edge case", - modulus: math.Ldexp(1, -1022), - arg: math.Ldexp(1.003, -1022), - want: math.Mod(math.Ldexp(1.003, -1022), math.Ldexp(1, -1022)), + modulus: 2.2250738585072014e-308, + arg: 2.2317490800827227e-308, + want: 6.6752215755214e-311, }, { name: "Generated case 1", modulus: -1.16358406231669e-309, arg: -2.3400420480579135e+145, - want: math.Mod(-2.3400420480579135e+145, 1.16358406231669e-309) + 1.16358406231669e-309, + want: 2.28657639287183e-310, }, { name: "Generated case 2", modulus: 1.0863201545657832e-307, arg: 1.3303463489150531e+13, - want: math.Mod(1.3303463489150531e+13, 1.0863201545657832e-307), + want: 1.170654222001558e-308, }, { name: "Generated case 3", modulus: 2.039381663448266e-229, arg: -1.370217367318819e-267, - want: math.Mod(-1.370217367318819e-267, 2.039381663448266e-229) + 2.039381663448266e-229, + want: 2.039381663448266e-229, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := modular64.NewModulus(tt.modulus) - got := m.Congruent(tt.arg) - if got != tt.want && !(math.IsNaN(got) && math.IsNaN(tt.want)) { - t.Errorf("Modulus{%v}.Congruent(%v) = %v, want %v", tt.modulus, tt.arg, got, tt.want) + got := m.Mod(tt.arg) + if got != tt.want && !(got != got && tt.want != tt.want) { + t.Errorf("Modulus{%v}.Mod(%v) = %v, want %v", tt.modulus, tt.arg, got, tt.want) } }) } @@ -185,32 +183,32 @@ func TestModulus_Dist(t *testing.T) { name: "NaN args", modulus: 100, args: args{ - n1: math.NaN(), + n1: modular64.NaN(), n2: 30, }, - want: math.NaN(), + want: modular64.NaN(), }, { name: "NaN args", modulus: 100, args: args{ n1: 20, - n2: math.NaN(), + n2: modular64.NaN(), }, - want: math.NaN(), + want: modular64.NaN(), }, { name: "NaN modulus", - modulus: math.NaN(), + modulus: modular64.NaN(), args: args{ n1: 20, n2: 30, }, - want: math.NaN(), + want: modular64.NaN(), }, { name: "Inf modulus", - modulus: math.Inf(1), + modulus: modular64.Inf(1), args: args{ n1: 20, n2: 30, @@ -221,17 +219,17 @@ func TestModulus_Dist(t *testing.T) { name: "Inf arg", modulus: 100, args: args{ - n1: math.Inf(1), + n1: modular64.Inf(1), n2: 30, }, - want: math.NaN(), + want: modular64.NaN(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { m := modular64.NewModulus(tt.modulus) got := m.Dist(tt.args.n1, tt.args.n2) - if got != tt.want && !(math.IsNaN(got) && math.IsNaN(tt.want)) { + if got != tt.want && !(got != got && tt.want != tt.want) { t.Errorf("Modulus.Dist(%v, %v) = %v, want %v (mod %v)", tt.args.n1, tt.args.n2, got, tt.want, tt.modulus) } }) @@ -299,9 +297,9 @@ func TestModulus_GetCongruent(t *testing.T) { func TestModulus_Misc(t *testing.T) { t.Run("Mod() test", func(t *testing.T) { m := modular64.NewModulus(15) - got := m.Mod() + got := m.Modulus() if got != 15 { - t.Errorf("Modulus.Mod() = %v, want %v", got, 15) + t.Errorf("Modulus.Modulus() = %v, want %v", got, 15) } }) } @@ -326,10 +324,10 @@ func BenchmarkMath_Mod(b *testing.B) { func BenchmarkModulus(b *testing.B) { for _, n := range benchmarks { - b.Run(fmt.Sprintf("Congruent(%v)", n), func(b *testing.B) { + b.Run(fmt.Sprintf("Mod(%v)", n), func(b *testing.B) { m := modular64.NewModulus(benchmarkModulo) for i := 0; i < b.N; i++ { - float64Sink = m.Congruent(n) + float64Sink = m.Mod(n) } }) } diff --git a/modular64/vec.go b/modular64/vec.go deleted file mode 100644 index 9f15a79..0000000 --- a/modular64/vec.go +++ /dev/null @@ -1,137 +0,0 @@ -package modular64 - -import ( - mgl "github.com/go-gl/mathgl/mgl64" -) - -// NewVec2Modulus creates a new 2d Vector Modulus -func NewVec2Modulus(vec mgl.Vec2) Vec2Modulus { - return Vec2Modulus{ - x: NewModulus(vec[0]), - y: NewModulus(vec[1]), - } -} - -// Vec2Modulus defines a modulus for 2d vectors -type Vec2Modulus struct { - x Modulus - y Modulus -} - -// Congruent performs Congruent() on all axis -func (m Vec2Modulus) Congruent(vec mgl.Vec2) mgl.Vec2 { - return mgl.Vec2{ - m.x.Congruent(vec[0]), - m.y.Congruent(vec[1]), - } -} - -// Dist returns the distance and direction of v1 to v2 -// It picks the shortest distance. -func (m Vec2Modulus) Dist(v1, v2 mgl.Vec2) mgl.Vec2 { - return mgl.Vec2{ - m.x.Dist(v1[0], v2[0]), - m.y.Dist(v1[1], v2[1]), - } -} - -// GetCongruent returns the vector closest to v1 that is congruent to v2 -func (m Vec2Modulus) GetCongruent(v1, v2 mgl.Vec2) mgl.Vec2 { - return mgl.Vec2{ - m.x.GetCongruent(v1[0], v2[0]), - m.y.GetCongruent(v1[1], v2[1]), - } -} - -// NewVec3Modulus creates a new 3d Vector Modulus -func NewVec3Modulus(vec mgl.Vec3) Vec3Modulus { - return Vec3Modulus{ - x: NewModulus(vec[0]), - y: NewModulus(vec[1]), - z: NewModulus(vec[2]), - } -} - -// Vec3Modulus defines a modulus for 3d vectors -type Vec3Modulus struct { - x Modulus - y Modulus - z Modulus -} - -// Congruent performs Congruent() on all axis -func (m Vec3Modulus) Congruent(vec mgl.Vec3) mgl.Vec3 { - return mgl.Vec3{ - m.x.Congruent(vec[0]), - m.y.Congruent(vec[1]), - m.z.Congruent(vec[2]), - } -} - -// Dist returns the distance and direction of v1 to v2 -// It picks the shortest distance. -func (m Vec3Modulus) Dist(v1, v2 mgl.Vec3) mgl.Vec3 { - return mgl.Vec3{ - m.x.Dist(v1[0], v2[0]), - m.y.Dist(v1[1], v2[1]), - m.z.Dist(v1[2], v2[2]), - } -} - -// GetCongruent returns the vector closest to v1 that is congruent to v2 -func (m Vec3Modulus) GetCongruent(v1, v2 mgl.Vec3) mgl.Vec3 { - return mgl.Vec3{ - m.x.GetCongruent(v1[0], v2[0]), - m.y.GetCongruent(v1[1], v2[1]), - m.z.GetCongruent(v1[2], v2[2]), - } -} - -// NewVec4Modulus creates a new 4d Vector Modulus -func NewVec4Modulus(vec mgl.Vec4) Vec4Modulus { - return Vec4Modulus{ - x: NewModulus(vec[0]), - y: NewModulus(vec[1]), - z: NewModulus(vec[2]), - w: NewModulus(vec[3]), - } -} - -// Vec4Modulus defines a modulus for 4d vectors -type Vec4Modulus struct { - x Modulus - y Modulus - z Modulus - w Modulus -} - -// Congruent performs Congruent() on all axis -func (m Vec4Modulus) Congruent(vec mgl.Vec4) mgl.Vec4 { - return mgl.Vec4{ - m.x.Congruent(vec[0]), - m.y.Congruent(vec[1]), - m.z.Congruent(vec[2]), - m.w.Congruent(vec[3]), - } -} - -// Dist returns the distance and direction of v1 to v2 -// It picks the shortest distance. -func (m Vec4Modulus) Dist(v1, v2 mgl.Vec4) mgl.Vec4 { - return mgl.Vec4{ - m.x.Dist(v1[0], v2[0]), - m.y.Dist(v1[1], v2[1]), - m.z.Dist(v1[2], v2[2]), - m.z.Dist(v1[3], v2[3]), - } -} - -// GetCongruent returns the vector closest to v1 that is congruent to v2 -func (m Vec4Modulus) GetCongruent(v1, v2 mgl.Vec4) mgl.Vec4 { - return mgl.Vec4{ - m.x.GetCongruent(v1[0], v2[0]), - m.y.GetCongruent(v1[1], v2[1]), - m.z.GetCongruent(v1[2], v2[2]), - m.w.GetCongruent(v1[3], v2[3]), - } -}