summaryrefslogtreecommitdiff
path: root/factors/table_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'factors/table_test.go')
-rw-r--r--factors/table_test.go404
1 files changed, 404 insertions, 0 deletions
diff --git a/factors/table_test.go b/factors/table_test.go
new file mode 100644
index 0000000..ab06aba
--- /dev/null
+++ b/factors/table_test.go
@@ -0,0 +1,404 @@
+package factors
+
+import (
+ "fmt"
+ "maps"
+ "slices"
+ "testing"
+)
+
+// Run a test with a table of cases
+func tableTest[IN comparable, OUT any](t *testing.T, toTest func(IN) OUT,
+ cases map[IN]OUT, equal func(OUT, OUT) bool, name string) {
+ for input, expected := range cases {
+ t.Run(fmt.Sprintf("%v", input), func(t *testing.T) {
+ actual := toTest(input)
+ if !equal(expected, actual) {
+ t.Errorf("%s(%v) = %v, expect %v", name, input, actual, expected)
+ }
+ })
+ }
+}
+
+type uintPair struct{ a, b uint }
+
+var primeFactorCases = map[uint]PrimeFactorization{
+ 0: PrimeFactorization{map[uint]uint{0: 1}},
+ 1: PrimeFactorization{map[uint]uint{}},
+ 2: PrimeFactorization{map[uint]uint{2: 1}},
+ 3: PrimeFactorization{map[uint]uint{3: 1}},
+ 4: PrimeFactorization{map[uint]uint{2: 2}},
+ 6: PrimeFactorization{map[uint]uint{2: 1, 3: 1}},
+ 9: PrimeFactorization{map[uint]uint{3: 2}},
+ 10: PrimeFactorization{map[uint]uint{2: 1, 5: 1}},
+ 12: PrimeFactorization{map[uint]uint{2: 2, 3: 1}},
+ 33: PrimeFactorization{map[uint]uint{3: 1, 11: 1}},
+ 60: PrimeFactorization{map[uint]uint{2: 2, 3: 1, 5: 1}},
+ 71: PrimeFactorization{map[uint]uint{71: 1}},
+ 1024: PrimeFactorization{map[uint]uint{2: 10}},
+ 86400: PrimeFactorization{map[uint]uint{2: 7, 3: 3, 5: 2}},
+}
+
+func TestPrimeFactorize(t *testing.T) {
+ equal := func(a, b PrimeFactorization) bool {
+ return maps.Equal(a.exponents, b.exponents)
+ }
+ tableTest(t, PrimeFactorize, primeFactorCases, equal, "PrimeFactorize")
+}
+
+var factorCases = map[uint][]uint{
+ 1: []uint{1},
+ 2: []uint{1, 2},
+ 4: []uint{1, 2, 4},
+ 6: []uint{1, 2, 3, 6},
+ 9: []uint{1, 3, 9},
+ 10: []uint{1, 2, 5, 10},
+ 12: []uint{1, 2, 3, 4, 6, 12},
+ 13: []uint{1, 13},
+ 15: []uint{1, 3, 5, 15},
+ 16: []uint{1, 2, 4, 8, 16},
+ 18: []uint{1, 2, 3, 6, 9, 18},
+ 30: []uint{1, 2, 3, 5, 6, 10, 15, 30},
+ 32: []uint{1, 2, 4, 8, 16, 32},
+ 33: []uint{1, 3, 11, 33},
+ 60: []uint{1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60},
+ 71: []uint{1, 71},
+}
+
+func TestFactors(t *testing.T) {
+ tableTest(t, Factors, factorCases, setEquals, "Factors")
+}
+
+var totativeRatioCases = map[uint]float64{
+ 1: 1.0, 2: 0.5, 3: 2.0 / 3.0, 4: 0.5, 6: 1.0 / 3.0, 7: 6.0 / 7.0, 8: 0.5,
+ 10: 0.4, 12: 1.0 / 3.0, 14: 3.0 / 7.0, 15: 8.0 / 15.0, 16: 0.5, 20: 0.4,
+ 24: 1.0 / 3.0, 30: 4.0 / 15.0, 60: 4.0 / 15.0, 71: 70.0 / 71.0,
+ 120: 4.0 / 15.0,
+}
+
+func totativeRatio(n uint) float64 {
+ return float64(Totient(n)) / float64(n)
+}
+
+func TestTotativeRatio(t *testing.T) {
+ tableTest(t, totativeRatio, totativeRatioCases, stdEquals, "TotativeRatio")
+}
+
+var totientCases = map[uint]uint{
+ 0: 0, 1: 1, 2: 1, 3: 2, 4: 2, 6: 2, 7: 6, 8: 4, 10: 4, 12: 4,
+ 14: 6, 15: 8, 16: 8, 20: 8, 24: 8, 30: 8, 60: 16, 71: 70, 120: 32,
+}
+
+func TestTotativeCount(t *testing.T) {
+ tableTest(t, Totient, totientCases, stdEquals, "Totient")
+}
+
+var totativeDigitCases = map[uint32][]uint32{
+ 0: []uint32{},
+ 1: []uint32{1},
+ 2: []uint32{1},
+ 3: []uint32{1, 2},
+ 4: []uint32{1, 3},
+ 6: []uint32{1, 5},
+ 7: []uint32{1, 2, 3, 4, 5, 6},
+ 8: []uint32{1, 3, 5, 7},
+ 10: []uint32{1, 3, 7, 9},
+ 12: []uint32{1, 5, 7, 11},
+ 14: []uint32{1, 3, 5, 9, 11, 13},
+ 15: []uint32{1, 2, 4, 7, 8, 11, 13, 14},
+ 16: []uint32{1, 3, 5, 7, 9, 11, 13, 15},
+ 20: []uint32{1, 3, 7, 9, 11, 13, 17, 19},
+ 24: []uint32{1, 5, 7, 11, 13, 17, 19, 23},
+ 30: []uint32{1, 7, 11, 13, 17, 19, 23, 29},
+ 60: []uint32{1, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59},
+ 71: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
+ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
+ 61, 62, 63, 64, 65, 66, 67, 68, 69, 70},
+ 120: []uint32{1, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 49, 53, 59,
+ 61, 67, 71, 73, 77, 79, 83, 89, 91, 97, 101, 103, 107, 109, 113, 119},
+}
+
+func TestTotativeDigits(t *testing.T) {
+ tableTest(t, TotativeDigits, totativeDigitCases, slices.Equal,
+ "TotativeDigits")
+}
+
+var factorScoreCases = map[uint]float64{
+ 1: 1.0,
+ 2: 1.5,
+ 3: 4.0 / 3.0,
+ 4: 1.75,
+ 6: 2.0,
+ 8: 1.875,
+ 10: 1.8,
+ 12: 7.0 / 3.0,
+ 71: 72.0 / 71.0,
+ 120: 3.0,
+ // number that will use bigScore
+ 367567200: 62496.0 / 12155.0,
+}
+
+func TestFactorScore(t *testing.T) {
+ // factors.Score is accurate enough that we can test for exact floats!
+ tableTest(t, Score, factorScoreCases, stdEquals, "Score")
+}
+
+var basicRankCases = map[uint]string{
+ // one test per rank, plus three extra (22, 25, 44) to test that
+ // n%3 = 1 vs n%3 = 2 doesn't matter
+ 2: "D-", 3: "E-", 4: "C~", 5: "F+", 6: "B~", 7: "F-", 8: "C-",
+ 9: "E~", 10: "D+", 11: "F~", 12: "A-", 14: "D~", 15: "E+", 18: "B-",
+ 20: "C+", 22: "D-", 24: "A~", 25: "F+", 30: "B+", 44: "C~", 60: "A+",
+ // test that it only depends on n%60
+ 62: "D-", 63: "E-", 64: "C~", 65: "F+", 66: "B~", 67: "F-", 68: "C-",
+ 69: "E~", 70: "D+", 71: "F~", 72: "A-", 74: "D~", 75: "E+", 78: "B-",
+ 80: "C+", 82: "D-", 84: "A~", 85: "F+", 90: "B+", 104: "C~", 120: "A+",
+ // test that it can extend to 0 and 1 as well
+ 0: "A+", 1: "F~",
+}
+
+func TestBasicRank(t *testing.T) {
+ tableTest(t, BasicRank, basicRankCases, stdEquals, "BasicRank")
+}
+
+var gcdCases = map[struct{ a, b uint32 }]uint32{
+ {0, 0}: 0, {1, 1}: 1,
+ {0, 1}: 1, {1, 0}: 1,
+ {0, 12}: 12, {12, 0}: 12,
+ {1, 12}: 1, {12, 1}: 1,
+ {6, 10}: 2, {10, 6}: 2,
+ {12, 20}: 4, {20, 12}: 4,
+ {20, 55}: 5, {55, 20}: 5,
+ {60, 120}: 60, {120, 60}: 60,
+ {120, 180}: 60, {180, 120}: 60,
+}
+
+// a version of gcd() for testing purposes
+func gcdTest(c struct{ a, b uint32 }) uint32 {
+ return gcd(c.a, c.b)
+}
+
+func TestGCD(t *testing.T) {
+ tableTest(t, gcdTest, gcdCases, stdEquals, "gcd")
+}
+
+var mtcCases = map[uint32]uint64{
+ 2: 0, 3: 0, 4: 2, 5: 10, 6: 8,
+ 7: 28, 8: 26, 9: 42, 10: 42, 11: 88, 12: 52,
+ 13: 130, 14: 100, 15: 116, 16: 138, 18: 146,
+ 20: 190, 24: 252, 28: 416, 30: 380, 32: 618, 36: 598,
+ 48: 1100, 60: 1496, 64: 2602, 90: 3662, 120: 6080,
+ 210: 18542, 360: 54362, 840: 270122, 2520: 2363528, 5040: 9409112,
+}
+
+func TestMTC(t *testing.T) {
+ tableTest(t, MTC, mtcCases, stdEquals, "MTC")
+}
+
+var sanCases = map[uint]bool{
+ 1: true, 2: true, 4: true, 6: true, 12: true, 24: true, 36: true,
+ 48: true, 60: true, 120: true, 180: true, 240: true, 360: true,
+ 720: true, 840: true, 1260: true, 1680: true, 2520: true, 5040: true,
+ 10080: true, 15120: true, 25200: true, 27720: true, 55440: true,
+ 3: false, 8: false, 13: false, 18: false, 20: false, 30: false,
+ 31: false, 72: false, 107: false, 135: false, 144: false, 300: false,
+ 1728: false, 4000: false, 6912: false, 7560: false,
+}
+
+func isSAN(n uint) bool {
+ return Type(n) >= Superabundant
+}
+
+func TestSAN(t *testing.T) {
+ tableTest(t, isSAN, sanCases, stdEquals, "isSAN")
+}
+
+var expOrdCases = map[uint]bool{
+ 0: true, 1: true, 2: true, 4: true, 5: false, 6: true, 8: true, 12: true,
+ 16: true, 18: false, 20: false, 27: false, 30: true, 36: true, 44: false,
+ 60: true, 70: false, 90: false, 96: true, 101: false, 120: true, 144: true,
+ 180: true, 770: false, 900: true, 6912: true, 10010: false,
+}
+
+func TestExponentsOrdered(t *testing.T) {
+ tableTest(t, exponentsOrdered, expOrdCases, stdEquals, "exponentsOrdered")
+}
+
+var practicalCases = map[uint]bool{
+ 1: true, 2: true, 4: true, 6: true, 8: true, 12: true, 16: true,
+ 18: true, 20: true, 24: true, 28: true, 30: true, 32: true, 36: true,
+ 48: true, 60: true, 120: true, 180: true, 240: true, 360: true,
+ 720: true, 840: true, 1260: true, 1680: true, 2520: true, 5040: true,
+ 10080: true, 15120: true, 25200: true, 27720: true, 55440: true,
+ 3: false, 10: false, 27: false, 44: false, 45: false, 68: false,
+ 70: false, 114: false, 121: false, 770: false, 1729: false, 10010: false,
+}
+
+func TestPractical(t *testing.T) {
+ tableTest(t, practical, practicalCases, stdEquals, "practical")
+}
+
+var typeCases = map[uint]NumberType{
+ 2: ColossallyAbundant, 3: NotPractical, 4: Superabundant,
+ 6: ColossallyAbundant, 8: OrderedExponent, 10: NotPractical,
+ 12: ColossallyAbundant, 18: Practical, 20: Practical, 24: Superabundant,
+ 28: Practical, 30: OrderedExponent, 31: NotPractical, 34: NotPractical,
+ 60: ColossallyAbundant, 70: NotPractical, 90: Practical, 100: Practical,
+ 120: ColossallyAbundant, 144: OrderedExponent, 180: Superabundant,
+ 240: Superabundant, 360: ColossallyAbundant, 720: Superabundant,
+ 770: NotPractical, 900: OrderedExponent, 1680: Superabundant,
+ 1729: NotPractical, 6912: OrderedExponent, 10010: NotPractical,
+ 10080: Superabundant, 15120: Superabundant, 25200: Superabundant,
+ 27720: Superabundant, 55440: ColossallyAbundant,
+}
+
+func TestType(t *testing.T) {
+ tableTest(t, Type, typeCases, stdEquals, "Type")
+}
+
+// ====== DIGIT MAP TESTS ======
+var (
+ zt = zeroType // 00 - zero
+ ot = oneType // 0R - one
+ ft = DigitType{1, Regular} // 1R - factors
+ rt = DigitType{2, Regular} // 2R - regulars (index 2)
+ wt = DigitType{0, Omega} // 0ω - omega neighbours
+ at = DigitType{0, Alpha} // 0α - alpha neighbours
+ nt = DigitType{0, Pseudoneighbour} // 0N - neighbour-like
+ pt = DigitType{0, Opaque} // 0P - opaque totatives
+)
+var digitMapCases = map[uint][]DigitType{
+ 0: []DigitType{},
+ 1: []DigitType{zt},
+ 2: []DigitType{zt, ot},
+ 3: []DigitType{zt, ot, wt},
+ 4: []DigitType{zt, ot, ft, wt},
+ 5: []DigitType{zt, ot, wt, at, wt},
+ 6: []DigitType{zt, ot, ft, ft, rt, wt},
+ 8: []DigitType{zt, ot, ft, at, ft, pt, DigitType{1, Alpha}, wt},
+ 10: []DigitType{zt, ot, ft, wt, rt, ft, DigitType{1, Omega}, pt,
+ DigitType{3, Regular}, wt},
+ 11: []DigitType{zt, ot, wt, at, at, wt, at, pt, nt, pt, wt},
+ 12: []DigitType{zt, ot, ft, ft, ft, pt, ft, pt, rt, rt,
+ DigitType{1, Opaque}, wt},
+ 16: []DigitType{zt, ot, ft, wt, ft, wt, DigitType{1, Omega}, pt,
+ ft, pt, DigitType{1, Omega}, pt, DigitType{1, Omega},
+ pt, DigitType{1, Opaque}, wt},
+ 17: []DigitType{zt, ot, wt, at, wt, pt, at, pt, wt, at,
+ pt, pt, nt, pt, pt, pt, wt},
+ 19: []DigitType{zt, ot, wt, wt, at, at, wt, pt, nt, wt,
+ at, pt, nt, pt, pt, nt, pt, pt, wt},
+ 22: []DigitType{zt, ot, ft, wt, rt, pt, DigitType{1, Omega}, wt,
+ DigitType{3, Regular}, pt, DigitType{1, Opaque}, ft,
+ DigitType{2, Omega}, pt, DigitType{1, Omega}, pt,
+ DigitType{4, Regular}, pt, DigitType{1, Opaque}, pt,
+ DigitType{2, Opaque}, wt},
+ 24: []DigitType{zt, ot, ft, ft, ft, at, ft, pt, ft, rt,
+ DigitType{1, Alpha}, pt, ft, pt, DigitType{1, Opaque},
+ DigitType{1, Alpha}, rt, pt, rt, pt, DigitType{1, Alpha},
+ DigitType{1, Opaque}, DigitType{1, Opaque}, wt},
+}
+
+func TestDigitMap(t *testing.T) {
+ tableTest(t, DigitMap, digitMapCases, slices.Equal, "DigitMap")
+}
+
+// ensures GetDigitType(d, r) == DigitMap(r)[d];
+// does not ensure it has the correct value (that's TestDigitMap's job)
+func TestGetDigitType(t *testing.T) {
+ for radix := range digitMapCases {
+ digitMap := DigitMap(radix)
+ for digit := range digitMap {
+ actual := GetDigitType(uint(digit), radix)
+ if actual != digitMap[digit] {
+ t.Logf("GetDigitType(%d, %d) = %d", digit, radix, actual)
+ t.Errorf("GetDigitType(%d, %d) != DigitMap(%d)[%d]",
+ digit, radix, radix, digit)
+ }
+ }
+ }
+}
+
+// A few test cases related to radix zero
+func TestGetDigitTypeMisc(t *testing.T) {
+ t.Run("GetDigitType(0, 0)", func(t *testing.T) {
+ if GetDigitType(0, 0) != ft {
+ t.Errorf("GetDigitType(0, 0) = %s, expected 1R", GetDigitType(0, 0))
+ }
+ })
+ t.Run("GetDigitType(1, 0)", func(t *testing.T) {
+ if GetDigitType(1, 0) != ot {
+ t.Errorf("GetDigitType(1, 0) = %s, expected 0R", GetDigitType(1, 0))
+ }
+ })
+ t.Run("GetDigitType(2, 0)", func(t *testing.T) {
+ if GetDigitType(2, 0) != pt {
+ t.Errorf("GetDigitType(2, 0) = %s, expected 0P", GetDigitType(2, 0))
+ }
+ })
+}
+
+var splitCases = map[uintPair]uintPair{
+ // digit, radix, regular part, totative part
+ uintPair{0, 0}: uintPair{0, 1},
+ uintPair{0, 1}: uintPair{1, 0},
+ uintPair{0, 2}: uintPair{1, 0},
+ uintPair{0, 12}: uintPair{1, 0},
+ uintPair{1, 0}: uintPair{1, 1},
+ uintPair{20, 0}: uintPair{1, 20},
+ uintPair{360, 0}: uintPair{1, 360},
+ uintPair{1, 1}: uintPair{1, 1},
+ uintPair{12, 1}: uintPair{1, 12},
+ uintPair{87, 1}: uintPair{1, 87},
+ uintPair{120, 1}: uintPair{1, 120},
+ uintPair{1, 2}: uintPair{1, 1},
+ uintPair{1, 3}: uintPair{1, 1},
+ uintPair{1, 7}: uintPair{1, 1},
+ uintPair{1, 12}: uintPair{1, 1},
+ uintPair{1, 39}: uintPair{1, 1},
+ uintPair{1, 2948}: uintPair{1, 1},
+ uintPair{5, 6}: uintPair{1, 5},
+ uintPair{10, 6}: uintPair{2, 5},
+ uintPair{12, 6}: uintPair{12, 1},
+ uintPair{15, 6}: uintPair{3, 5},
+ uintPair{35, 6}: uintPair{1, 35},
+ uintPair{56, 6}: uintPair{8, 7},
+ uintPair{5, 12}: uintPair{1, 5},
+ uintPair{10, 12}: uintPair{2, 5},
+ uintPair{12, 12}: uintPair{12, 1},
+ uintPair{15, 12}: uintPair{3, 5},
+ uintPair{35, 12}: uintPair{1, 35},
+ uintPair{56, 12}: uintPair{8, 7},
+ uintPair{12, 7}: uintPair{1, 12},
+ uintPair{56, 7}: uintPair{7, 8},
+ uintPair{70, 10}: uintPair{10, 7},
+}
+
+// testing version of Split
+func splitTest(a uintPair) uintPair {
+ regular, totative := Split(a.a, a.b)
+ return uintPair{regular, totative}
+}
+
+func TestSplit(t *testing.T) {
+ tableTest(t, splitTest, splitCases, stdEquals, "Split")
+}
+
+// to be used as the equal paramater for tableTest
+func stdEquals[T comparable](a, b T) bool { return a == b }
+
+func setEquals[E comparable](a, b []E) bool {
+ // use maps to simulate sets
+ // aSet[a] == true means set contains a, false means not
+ aSet := make(map[E]bool)
+ bSet := make(map[E]bool)
+ for _, i := range a {
+ aSet[i] = true
+ }
+ for _, j := range b {
+ bSet[j] = true
+ }
+ return maps.Equal(aSet, bSet)
+}