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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
|
package factors
import (
"math"
"math/big"
)
/*
Score returns a "factor score" equal to the sum of the reciprocoals
of the number n's factors.
Rationale:
A number's factors are one of the most important things that determines
how well it functions as a number radix. An easy way to determine how
good these factors are is to simply count them, but that comes with
the problem that all factors are considered equal when they really aren't
- divisibility by 2 (which ensures 1/2 is a terminating fraction and
you can tell whether a number is even or odd by looking at the last digit)
is more important than divisibility by 23. The most obvious way of
accounting for this is by giving each factor a score and adding those
scores to get the overall score. I chose score(f) = 1/f because it is
the simplest function that captures the intuition that smaller factors
are more valuable than larger ones. It also gives the score a meaning:
a factor's score is the probability a random number is divisible by it.
Special cases:
Score(0) = NaN
Score(1) = 1.0
*/
func Score(n uint) float64 {
if n == 0 {
return math.NaN()
} else if n > maxSmall {
return bigScore(n)
}
factorSum := uint(0)
for _, f := range Factors(n) {
factorSum += f
}
return float64(factorSum) / float64(n)
}
const maxSmall = 1<<28 - 1
func bigScore(n uint) float64 {
factorSum := new(big.Int)
// initialize bigFactor like this to avoid reallocating memory
bigFactor := new(big.Int).SetUint64(uint64(n))
for _, f := range Factors(n) {
bigFactor.SetUint64(uint64(f))
factorSum.Add(factorSum, bigFactor)
}
bigN := new(big.Int).SetUint64(uint64(n))
score := new(big.Rat).SetFrac(factorSum, bigN)
score64, _ := score.Float64()
return score64
}
/*
BasicRank returns a rank describing how well a radix handles the simplest
fractions (1/2, 1/3, 1/4, 1/5). It consists of two parts: a letter (A-F)
and a sign (+, -, ~). It is also known as the 2345 Rank.
The letter determines how well the first three fractions are handled
(specifically, what their decimal expansions look like):
- A means all three terminate with one digit.
- B means 1/2 and 1/3 terminate with one digit,
and 1/4 terminates with two digits.
- C means 1/2 and 1/4 terminate with one digit,
but 1/3 is infinitely repeating (pattern length 1-2 digits).
- D means 1/2 terminates with one digit,
but 1/3 is infinitely repeating (pattern length 1-2 digits),
and 1/4 terminates with two digits.
- E means 1/3 terminates with one digit,
but 1/2 and 1/4 are infinitely repeating
(pattern lengths 1 and 1-2 respectively).
- F means all three are infinitely repeating
(pattern lengths 1, 1-2 and 1-2 respectively).
The sign determines how well 1/5 is handled:
- A plus means it terminates with one digit.
- A tilde means it repeats with a short (1-2 digit) pattern.
- A minus means it repeats with a long (4-digit) pattern.
Because the rank only depends on radix % 60, 0 and 1 are given the same
ranks as 60 and 61 (A+, F~).
*/
func BasicRank(radix uint) string {
var firstRank, secondRank string
switch uint(0) {
case radix % 12:
firstRank = "A"
case radix % 6:
firstRank = "B"
case radix % 4:
firstRank = "C"
case radix % 2:
firstRank = "D"
case radix % 3:
firstRank = "E"
default:
firstRank = "F"
}
switch radix % 5 {
case 0:
secondRank = "+"
case 1, 4:
secondRank = "~"
case 2, 3:
secondRank = "-"
}
return firstRank + secondRank
}
|