Compare commits

...

9 Commits

3 changed files with 41 additions and 33 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
benfords-law
benfords-law.exe

View File

@ -6,14 +6,26 @@ This was a test to determine if random numbers follow [Benford's Law](https://en
With One-Hundred-Million samples of random numbers between 0 and 9,999,999,999,999,999, the number of leading digits was the following: With One-Hundred-Million samples of random numbers between 0 and 9,999,999,999,999,999, the number of leading digits was the following:
1. 11105630 ```shell
2. 11110535 $ ./benfords-law.exe
3. 11112084 2020/11/14 10:24:18 generating numbers...
4. 11113667 2020/11/14 10:24:19 18% completed generating and analyzing samples
5. 11120216 2020/11/14 10:24:20 37% completed generating and analyzing samples
6. 11106549 2020/11/14 10:24:21 56% completed generating and analyzing samples
7. 11108623 2020/11/14 10:24:22 75% completed generating and analyzing samples
8. 11114813 2020/11/14 10:24:23 93% completed generating and analyzing samples
9. 11107883 2020/11/14 10:24:24 done.
1: 1108503 (11.085030%)
2: 1111584 (11.115840%)
3: 1111726 (11.117260%)
4: 1111122 (11.111220%)
5: 1110443 (11.104430%)
6: 1111248 (11.112480%)
7: 1111496 (11.114960%)
8: 1111777 (11.117770%)
9: 1112101 (11.121010%)
Press 'Enter' to continue...
```
This shows that Benford's law only works when the data is not random, such as natural data gathered in real life. This is because for data which Benford's law does work, it's natural data generated following a [power law](https://en.wikipedia.org/wiki/Power_law), which is quite common in nature.
This shows that Benford's law only works when the data is not random, such as natural data gathered in real life. This is because natural data is generated following a power law, which is common in nature.

40
main.go
View File

@ -7,64 +7,58 @@ import (
"math" "math"
"math/rand" "math/rand"
"os" "os"
"runtime"
"time" "time"
) )
const ( const (
randomMin = 0 randomMin = 1 // We specify a range which random numbers will be generated, we must start at the first possible left-most digit
randomMax = 9999999999999999 randomMax = 999999999999999999 // int64 max value is 9223372036854775807. We use one digit less than that with all 9's in order to not give bias to any digits.
numSamples = 10000000 numSamples = 100000000 // A nice rounded human number
) )
func main() { func main() {
results := [9]int{} // There are 9 possible leading digits and 0 does not count, offset by 1 for index to actual value. Examples: To access 1 use [0]. To access 5 use [4]. To access 9 use [8]. // In results, we store a count of each left most leading digits as numbers are randomly genereated
currentSample := 0 results := [9]int{} // There are 9 possible leading digits and 0 does not count, offset by 1 for index to actual value. Examples: To access count for 1 use [0]. To access 5 use [4]. To access 9 use [8].
currentSample := 0 // A counter that increments each time a random number sample has been generated. Used for status messages
// Start a little goroutine to output status and attach a ticker to execute it each second
statusTicker := time.NewTicker(time.Second) statusTicker := time.NewTicker(time.Second)
go func() { go func() {
for { for {
<-statusTicker.C <-statusTicker.C // Wait for heartbeat from ticker channel
percentCompleted := (currentSample * 100) / numSamples percentCompleted := (currentSample * 100) / numSamples
log.Printf("%d %% completed generating and analyzing samples", percentCompleted) log.Printf("%d%% completed generating and analyzing samples", percentCompleted)
} }
}() }()
log.Printf("generating numbers...") log.Printf("generating numbers...")
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
generatedNumbers := make(chan int, 1024)
for i := 0; i < runtime.NumCPU(); i++ {
go generatorWorker(generatedNumbers)
}
for currentSample = 0; currentSample < numSamples; currentSample++ { for currentSample = 0; currentSample < numSamples; currentSample++ {
results[firstDigit(<-generatedNumbers)-1]++ results[firstDigit(rand.Intn(randomMax-randomMin+1)+randomMin)-1]++ // Generate a random number between randomMin and randomMax, get the first digit then increment the counter in results array, remember, it's offset by 1
} }
// Done generating and counting digits, stop the status ticker
statusTicker.Stop() statusTicker.Stop()
log.Printf("done.") log.Printf("done.")
// output results // Output results
for digitMinusOne, count := range results { for digitMinusOne, digitCount := range results {
fmt.Printf("%d: %d\n", digitMinusOne+1, count) fmt.Printf("%d: %d (%f%%)\n", digitMinusOne+1, digitCount, float64(digitCount*100)/float64(numSamples))
} }
// Wait indefinitely until Enter Key is pressed, avoid terminating terminal before viewing results if ran from a shell
fmt.Print("Press 'Enter' to continue...") fmt.Print("Press 'Enter' to continue...")
bufio.NewReader(os.Stdin).ReadBytes('\n') bufio.NewReader(os.Stdin).ReadBytes('\n')
} }
func generatorWorker(returnChannel chan int) { // firstDigit returns the first/leftmost digit of the base10 representation of an integer
for {
returnChannel <- rand.Intn(randomMax-randomMin+1) + randomMin // We must use Intn instead of Int because from Base10's perspective, integers cut off at a really weird spot
}
}
func firstDigit(x int) int { func firstDigit(x int) int {
return int(math.Abs(float64(x)) / math.Pow(10, float64(numDigits(x)-1))) return int(math.Abs(float64(x)) / math.Pow(10, float64(numDigits(x)-1)))
} }
// numDigits returns the number of digits
func numDigits(x int) int { func numDigits(x int) int {
if x == 0 { if x == 0 {
return 1 return 1