@@ -0,0 +1,88 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func RandomDecimal(bits int) (string, error) {
|
||||
max := new(big.Int).Lsh(big.NewInt(1), uint(bits))
|
||||
n, err := rand.Int(rand.Reader, max)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return n.String(), nil
|
||||
}
|
||||
|
||||
func RandomSeed() (string, error) {
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func ExpectedGuess(taskID, seed, clientID string, seq int64, bits int) string {
|
||||
h := sha256.Sum256([]byte(fmt.Sprintf("%s|%s|%s|%d", taskID, seed, clientID, seq)))
|
||||
n := new(big.Int).SetBytes(h[:])
|
||||
mod := new(big.Int).Lsh(big.NewInt(1), uint(bits))
|
||||
n.Mod(n, mod)
|
||||
return n.String()
|
||||
}
|
||||
|
||||
func Distance(a, b string) (*big.Int, error) {
|
||||
x, ok := new(big.Int).SetString(a, 10)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid integer")
|
||||
}
|
||||
y, ok := new(big.Int).SetString(b, 10)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid integer")
|
||||
}
|
||||
d := new(big.Int).Sub(x, y)
|
||||
d.Abs(d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func log2Big(x *big.Int) float64 {
|
||||
if x.Sign() <= 0 {
|
||||
return 0
|
||||
}
|
||||
n := x.BitLen()
|
||||
if n <= 53 {
|
||||
return math.Log2(float64(x.Uint64()))
|
||||
}
|
||||
shift := n - 53
|
||||
top := new(big.Int).Rsh(new(big.Int).Set(x), uint(shift))
|
||||
return math.Log2(float64(top.Uint64())) + float64(shift)
|
||||
}
|
||||
|
||||
func Score(distance *big.Int, bits int) float64 {
|
||||
if distance.Sign() == 0 {
|
||||
return 100
|
||||
}
|
||||
s := 100 * (1 - log2Big(new(big.Int).Add(distance, big.NewInt(1)))/float64(bits))
|
||||
if s < 0 {
|
||||
return 0
|
||||
}
|
||||
if s > 100 {
|
||||
return 100
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func Position(clientID string, score float64) (float64, float64, float64) {
|
||||
h := sha256.Sum256([]byte(clientID))
|
||||
f := func(i int) float64 { v := uint16(h[i])<<8 | uint16(h[i+1]); return float64(v)/65535*2 - 1 }
|
||||
x, y, z := f(0), f(2), f(4)
|
||||
norm := math.Sqrt(x*x + y*y + z*z)
|
||||
if norm < 0.001 {
|
||||
norm = 1
|
||||
}
|
||||
r := 0.25 + 13*(1-score/100)
|
||||
return r * x / norm, r * y / norm, r * z / norm
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExpectedGuessDeterministic(t *testing.T) {
|
||||
a := ExpectedGuess("task_x", "seed", "client", 7, 28)
|
||||
b := ExpectedGuess("task_x", "seed", "client", 7, 28)
|
||||
if a != b {
|
||||
t.Fatalf("not deterministic: %s != %s", a, b)
|
||||
}
|
||||
n, ok := new(big.Int).SetString(a, 10)
|
||||
if !ok || n.Sign() < 0 || n.BitLen() > 28 {
|
||||
t.Fatalf("guess outside range: %s", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestScore(t *testing.T) {
|
||||
if Score(big.NewInt(0), 32) != 100 {
|
||||
t.Fatal("exact hit must score 100")
|
||||
}
|
||||
near := Score(big.NewInt(1), 32)
|
||||
far := Score(new(big.Int).Lsh(big.NewInt(1), 30), 32)
|
||||
if near <= far {
|
||||
t.Fatalf("near score %f should exceed far %f", near, far)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user