forked from Snider/Poindexter
Centralizes common math operations used across core/go-ai, core/go, and core/mining into Poindexter as the math pillar (alongside Borg=data, Enchantrix=encryption). New modules: - stats: Sum, Mean, Variance, StdDev, MinMax, IsUnderrepresented - scale: Lerp, InverseLerp, Remap, RoundToN, Clamp, MinMaxScale - epsilon: ApproxEqual, ApproxZero - score: WeightedScore, Ratio, Delta, DeltaPercent - signal: RampUp, SineWave, Oscillate, Noise (seeded RNG) 235 LOC implementation, 509 LOC tests, zero external deps, WASM-safe. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
148 lines
3.3 KiB
Go
148 lines
3.3 KiB
Go
package poindexter
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
func TestLerp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
t_, a, b float64
|
|
want float64
|
|
}{
|
|
{"start", 0, 10, 20, 10},
|
|
{"end", 1, 10, 20, 20},
|
|
{"mid", 0.5, 10, 20, 15},
|
|
{"quarter", 0.25, 0, 100, 25},
|
|
{"extrapolate", 2, 0, 10, 20},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := Lerp(tt.t_, tt.a, tt.b)
|
|
if math.Abs(got-tt.want) > 1e-9 {
|
|
t.Errorf("Lerp(%v, %v, %v) = %v, want %v", tt.t_, tt.a, tt.b, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInverseLerp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
v, a, b float64
|
|
want float64
|
|
}{
|
|
{"start", 10, 10, 20, 0},
|
|
{"end", 20, 10, 20, 1},
|
|
{"mid", 15, 10, 20, 0.5},
|
|
{"equal_range", 5, 5, 5, 0},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := InverseLerp(tt.v, tt.a, tt.b)
|
|
if math.Abs(got-tt.want) > 1e-9 {
|
|
t.Errorf("InverseLerp(%v, %v, %v) = %v, want %v", tt.v, tt.a, tt.b, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
v, inMin, inMax, outMin, outMax float64
|
|
want float64
|
|
}{
|
|
{"identity", 5, 0, 10, 0, 10, 5},
|
|
{"scale_up", 5, 0, 10, 0, 100, 50},
|
|
{"reverse", 3, 0, 10, 10, 0, 7},
|
|
{"offset", 0, 0, 1, 100, 200, 100},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := Remap(tt.v, tt.inMin, tt.inMax, tt.outMin, tt.outMax)
|
|
if math.Abs(got-tt.want) > 1e-9 {
|
|
t.Errorf("Remap = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRoundToN(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
f float64
|
|
decimals int
|
|
want float64
|
|
}{
|
|
{"zero_dec", 3.456, 0, 3},
|
|
{"one_dec", 3.456, 1, 3.5},
|
|
{"two_dec", 3.456, 2, 3.46},
|
|
{"three_dec", 3.4564, 3, 3.456},
|
|
{"negative", -2.555, 2, -2.56},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := RoundToN(tt.f, tt.decimals)
|
|
if math.Abs(got-tt.want) > 1e-9 {
|
|
t.Errorf("RoundToN(%v, %v) = %v, want %v", tt.f, tt.decimals, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClamp(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
v, min, max float64
|
|
want float64
|
|
}{
|
|
{"within", 5, 0, 10, 5},
|
|
{"below", -5, 0, 10, 0},
|
|
{"above", 15, 0, 10, 10},
|
|
{"at_min", 0, 0, 10, 0},
|
|
{"at_max", 10, 0, 10, 10},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := Clamp(tt.v, tt.min, tt.max)
|
|
if got != tt.want {
|
|
t.Errorf("Clamp(%v, %v, %v) = %v, want %v", tt.v, tt.min, tt.max, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClampInt(t *testing.T) {
|
|
if got := ClampInt(5, 0, 10); got != 5 {
|
|
t.Errorf("ClampInt(5, 0, 10) = %v, want 5", got)
|
|
}
|
|
if got := ClampInt(-1, 0, 10); got != 0 {
|
|
t.Errorf("ClampInt(-1, 0, 10) = %v, want 0", got)
|
|
}
|
|
if got := ClampInt(15, 0, 10); got != 10 {
|
|
t.Errorf("ClampInt(15, 0, 10) = %v, want 10", got)
|
|
}
|
|
}
|
|
|
|
func TestMinMaxScale(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
v, min, max float64
|
|
want float64
|
|
}{
|
|
{"mid", 5, 0, 10, 0.5},
|
|
{"at_min", 0, 0, 10, 0},
|
|
{"at_max", 10, 0, 10, 1},
|
|
{"equal_range", 5, 5, 5, 0},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := MinMaxScale(tt.v, tt.min, tt.max)
|
|
if math.Abs(got-tt.want) > 1e-9 {
|
|
t.Errorf("MinMaxScale(%v, %v, %v) = %v, want %v", tt.v, tt.min, tt.max, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|