├── .gitignore ├── LICENSE ├── README.md ├── arch_nowasm.go ├── arch_tinygo.go ├── consts.go ├── go.mod ├── size_bench.py ├── size_bench ├── std │ ├── atan.go │ ├── atan2.go │ ├── exp.go │ ├── fract.go │ ├── hypot.go │ ├── ln.go │ ├── powf.go │ ├── round.go │ ├── sin.go │ ├── sqrt.go │ ├── tan.go │ └── trunc.go └── tiny │ ├── atan.go │ ├── atan2.go │ ├── exp.go │ ├── fract.go │ ├── hypot.go │ ├── ln.go │ ├── powf.go │ ├── round.go │ ├── sin.go │ ├── sqrt.go │ ├── tan.go │ └── trunc.go ├── tinymath.go ├── tinymath_test.go ├── trigonometry.go └── trigonometry_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .hypothesis 3 | .*_cache/ 4 | /drafts/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | 2024 Gram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included 13 | in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🧮 tinymath 2 | 3 | [ [📚 docs](https://pkg.go.dev/github.com/orsinium-labs/tinymath) ] [ [🐙 github](https://github.com/orsinium-labs/tinymath) ] 4 | 5 | The fastest Go math library for constrained environments, like microcontrollers or WebAssembly. 6 | 7 | * Optimizes for performance and small code size at the cost of precision. 8 | * Uses float32 because most microcontrollers (like [ESP32](https://en.wikipedia.org/wiki/ESP32)) have much faster computation for float32 than for float64. 9 | * Designed and tested to work with both Go and [TinyGo](https://tinygo.org/), hence the name. 10 | * Most algorithms are ported from [micromath](https://github.com/tarcieri/micromath) Rust library. 11 | * Zero dependency. 12 | 13 | ## 📦 Installation 14 | 15 | ```bash 16 | go get github.com/orsinium-labs/tinymath 17 | ``` 18 | 19 | ## 🔧 Usage 20 | 21 | ```go 22 | fmt.Println(tinymath.Sin(tinymath.Pi)) 23 | ``` 24 | 25 | ## 🔬 Size 26 | 27 | Here is a comparison of WebAssembly binary size (built with TinyGo) when using tinymath vs stdlib math: 28 | 29 | | function | tinymath | stdlib | ratio | 30 | | ------------ | --------:| ------:| -----:| 31 | | atan | 106 | 367 | 28% | 32 | | atan2 | 167 | 782 | 21% | 33 | | exp | 463 | 2722 | 17% | 34 | | fract | 166 | 154 | 107% | 35 | | hypot | 67 | 203 | 33% | 36 | | ln | 196 | 4892 | 4% | 37 | | powf | 701 | 9167 | 7% | 38 | | round | 129 | 171 | 75% | 39 | | sin | 125 | 1237 | 10% | 40 | | sqrt | 57 | 57 | 100% | 41 | | tan | 138 | 1137 | 12% | 42 | | trunc | 57 | 57 | 100% | 43 | 44 | To reproduce: `python3 size_bench.py` 45 | -------------------------------------------------------------------------------- /arch_nowasm.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo.wasm 2 | 3 | package tinymath 4 | 5 | // Functions that can be optimized for wasm 6 | 7 | // Returns the smallest integer greater than or equal to a number. 8 | func Ceil(self float32) float32 { 9 | return -Floor(-self) 10 | } 11 | 12 | // Returns the largest integer less than or equal to a number. 13 | func Floor(self float32) float32 { 14 | res := float32(int32(self)) 15 | if self < res { 16 | res -= 1.0 17 | } 18 | return float32(res) 19 | } 20 | 21 | // Approximates the square root of a number with an average deviation of ~5%. 22 | // 23 | // Returns [`NAN`] if `self` is a negative number. 24 | func Sqrt(self float32) float32 { 25 | if self >= 0.0 { 26 | return FromBits((ToBits(self) + 0x3f80_0000) >> 1) 27 | } else { 28 | return NaN 29 | } 30 | } 31 | 32 | // Returns the integer part of a number. 33 | func Trunc(self float32) float32 { 34 | const MANTISSA_MASK = 0b0000_0000_0111_1111_1111_1111_1111_1111 35 | 36 | x_bits := ToBits(self) 37 | exponent := extractExponentValue(self) 38 | 39 | // exponent is negative, there is no whole number, just return zero 40 | if exponent < 0 { 41 | return CopySign(0, self) 42 | } 43 | 44 | exponent_clamped := uint32(Max(exponent, 0)) 45 | 46 | // find the part of the fraction that would be left over 47 | fractional_part := (x_bits << exponent_clamped) & MANTISSA_MASK 48 | 49 | // if there isn't a fraction we can just return the whole thing. 50 | if fractional_part == 0 { 51 | return self 52 | } 53 | 54 | fractional_mask := fractional_part >> exponent_clamped 55 | return FromBits(x_bits & ^fractional_mask) 56 | } 57 | 58 | func leadingZeros(x uint32) uint32 { 59 | var n uint32 = 32 60 | for x != 0 { 61 | x >>= 1 62 | n -= 1 63 | } 64 | return n 65 | } 66 | -------------------------------------------------------------------------------- /arch_tinygo.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo.wasm 2 | 3 | package tinymath 4 | 5 | // Functions in this file are inlined and optimized by TinyGo compiler. 6 | // The result is a single wasm instruction. 7 | // 8 | // https://github.com/tinygo-org/tinygo/blob/6384ecace093df2d0b93915886954abfc4ecfe01/compiler/intrinsics.go#L114C5-L114C22 9 | 10 | import ( 11 | "math" 12 | "math/bits" 13 | ) 14 | 15 | func Ceil(self float32) float32 { 16 | return float32(math.Ceil(float64(self))) 17 | } 18 | 19 | func Floor(self float32) float32 { 20 | return float32(math.Floor(float64(self))) 21 | } 22 | 23 | func Sqrt(self float32) float32 { 24 | return float32(math.Sqrt(float64(self))) 25 | } 26 | 27 | func Trunc(self float32) float32 { 28 | return float32(math.Trunc(float64(self))) 29 | } 30 | 31 | func leadingZeros(x uint32) uint32 { 32 | return uint32(bits.LeadingZeros32(x)) 33 | } 34 | -------------------------------------------------------------------------------- /consts.go: -------------------------------------------------------------------------------- 1 | package tinymath 2 | 3 | // Constatnts from both Go and Rust stdlib typed as float32. 4 | const ( 5 | // Archimedes' constant (π) 6 | Pi float32 = 3.14159265358979323846264338327950288 7 | 8 | // The full circle constant (τ) 9 | // 10 | // Equal to 2π. 11 | Tau float32 = 6.28318530717958647692528676655900577 12 | 13 | // The golden ratio (φ) 14 | Phi float32 = 1.618033988749894848204586834365638118 15 | 16 | // The Euler-Mascheroni constant (γ) 17 | EGamma float32 = 0.577215664901532860606512090082402431 18 | 19 | // π/2 20 | FracPi2 float32 = 1.57079632679489661923132169163975144 21 | 22 | // π/3 23 | FracPi3 float32 = 1.04719755119659774615421446109316763 24 | 25 | // π/4 26 | FracPi4 float32 = 0.785398163397448309615660845819875721 27 | 28 | // π/6 29 | FracPi6 float32 = 0.52359877559829887307710723054658381 30 | 31 | // π/8 32 | FracPi8 float32 = 0.39269908169872415480783042290993786 33 | 34 | // 1/π 35 | Frac1Pi float32 = 0.318309886183790671537767526745028724 36 | 37 | // 1/sqrt(π) 38 | Frac1SqrtPi float32 = 0.564189583547756286948079451560772586 39 | 40 | // 2/π 41 | Frac2Pi float32 = 0.636619772367581343075535053490057448 42 | 43 | // 2/sqrt(π) 44 | Frac2SqrtPi float32 = 1.12837916709551257389615890312154517 45 | 46 | // sqrt(2) 47 | Sqrt2 float32 = 1.41421356237309504880168872420969808 48 | 49 | // 1/sqrt(2) 50 | Frac1Sqrt2 float32 = 0.707106781186547524400844362104849039 51 | 52 | // sqrt(3) 53 | Sqrt3 float32 = 1.732050807568877293527446341505872367 54 | 55 | SqrtE float32 = 1.64872127070012814684865078781416357165377610071014801157507931 56 | SqrtPi float32 = 1.77245385090551602729816748334114518279754945612238712821380779 57 | SqrtPhi float32 = 1.27201964951406896425242246173749149171560804184009624861664038 58 | 59 | // 1/sqrt(3) 60 | Frac1Sqrt3 float32 = 0.577350269189625764509148780501957456 61 | 62 | // Euler's number (e) 63 | E float32 = 2.71828182845904523536028747135266250 64 | 65 | // log₂(e) 66 | Log2E float32 = 1.44269504088896340735992468100189214 67 | 68 | // log₂(10) 69 | Log210 float32 = 3.32192809488736234787031942948939018 70 | 71 | // log₁₀(e) 72 | Log10E float32 = 0.434294481903251827651128918916605082 73 | 74 | // log₁₀(2) 75 | Log102 float32 = 0.301029995663981195213738894724493027 76 | 77 | // ln(2) 78 | Ln2 float32 = 0.693147180559945309417232121458176568 79 | 80 | // ln(10) 81 | Ln10 float32 = 2.30258509299404568401799145468436421 82 | 83 | // [Machine epsilon] value for float32. 84 | // 85 | // This is the difference between `1.0` and the next larger representable number. 86 | // 87 | // Equal to 2^(1 - MANTISSA_DIGITS). 88 | // 89 | // [Machine epsilon]: https://en.wikipedia.org/wiki/Machine_epsilon 90 | Epsilon float32 = 1.19209290e-07 91 | 92 | // Smallest finite float32 value. 93 | // 94 | // Equal to -MAX. 95 | // 96 | // [`MAX`]: f32::MAX 97 | MinNeg float32 = -3.40282347e+38 98 | 99 | // Smallest positive normal float32 value. 100 | // 101 | // Equal to 2^(MIN_EXP - 1). 102 | MinPos float32 = 0x1p-126 * 0x1p-23 103 | 104 | // Largest finite float32 value. 105 | // 106 | // Equal to (1 - 2^(-MANTISSA_DIGITS)) 2^MAX_EXP. 107 | MaxPos float32 = 0x1p127 * (1 + (1 - 0x1p-23)) 108 | 109 | // One greater than the minimum possible normal power of 2 exponent. 110 | // 111 | // If n = MinExp, then normal numbers ≥ 0.5 × 2ⁿ. 112 | MinExp float32 = -125 113 | 114 | // Maximum possible power of 2 exponent. 115 | // 116 | // If n = MaxExp, then normal numbers < 1 × 2ⁿ. 117 | MaxExp float32 = 128 118 | 119 | // Minimum n for which 10ⁿ is normal. 120 | // 121 | // Equal to ceil(log₁₀ MIN_POSITIVE). 122 | Min10Exp float32 = -37 123 | 124 | // Maximum n for which 10ⁿ is normal. 125 | // 126 | // Equal to floor(log₁₀ MAX). 127 | Max10Exp float32 = 38 128 | ) 129 | 130 | var ( 131 | // Not a number 132 | NaN float32 = FromBits(0x7fc00000) 133 | 134 | // Positive infinity 135 | Inf float32 = FromBits(0x7f800000) 136 | 137 | // Negative infininty 138 | NegInf float32 = FromBits(0xff800000) 139 | ) 140 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/orsinium-labs/tinymath 2 | 3 | go 1.22.1 4 | -------------------------------------------------------------------------------- /size_bench.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import subprocess 3 | 4 | BIN = 'bin.wasm' 5 | 6 | 7 | def get_size(path: Path) -> int: 8 | # build binary 9 | cmd = [ 10 | 'tinygo', 'build', 11 | '-o', BIN, 12 | '-target', 'wasm-unknown', 13 | '-tags', f'none {path.stem}', 14 | str(path), 15 | ] 16 | subprocess.run(cmd, check=True) 17 | 18 | # strip debug symobols 19 | subprocess.run(['wasm-strip', BIN], check=True) 20 | 21 | # optimize for size 22 | cmd = ['wasm-opt', '-Oz', '--all-features', '-o', BIN, BIN] 23 | subprocess.run(cmd, check=True) 24 | 25 | size = len(Path(BIN).read_bytes()) 26 | Path(BIN).unlink() 27 | return size 28 | 29 | 30 | def main(): 31 | print('| function | tinymath | stdlib | ratio |') 32 | print('| ------------ | --------:| ------:| -----:|') 33 | root = Path(__file__).parent / 'size_bench' 34 | for tiny_path in sorted((root / 'tiny').iterdir()): 35 | std_path = root / 'std' / tiny_path.name 36 | tiny_size = get_size(tiny_path) 37 | std_size = get_size(std_path) 38 | ratio = int(tiny_size / std_size * 100) 39 | print(f'| {tiny_path.stem:12} | {tiny_size:>8} | {std_size:>6} | {ratio:>4}% |') # noqa: E501 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /size_bench/std/atan.go: -------------------------------------------------------------------------------- 1 | //go:build !none || atan 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Atan(a float64) float64 { 9 | return math.Atan(a) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/atan2.go: -------------------------------------------------------------------------------- 1 | //go:build !none || atan2 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Atan2(a, b float64) float64 { 9 | return math.Atan2(a, b) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/exp.go: -------------------------------------------------------------------------------- 1 | //go:build !none || exp 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Exp(x float64) float64 { 9 | return math.Exp(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/fract.go: -------------------------------------------------------------------------------- 1 | //go:build !none || fract 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Fract(x float64) float64 { 9 | r, _ := math.Frexp(x) 10 | return r 11 | } 12 | -------------------------------------------------------------------------------- /size_bench/std/hypot.go: -------------------------------------------------------------------------------- 1 | //go:build !none || hypot 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Hypot(a, b float64) float64 { 9 | return math.Hypot(a, b) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/ln.go: -------------------------------------------------------------------------------- 1 | //go:build !none || ln 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Ln(x float64) float64 { 9 | return math.Log(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/powf.go: -------------------------------------------------------------------------------- 1 | //go:build !none || powf 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func PowF(a, b float64) float64 { 9 | return math.Pow(a, b) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/round.go: -------------------------------------------------------------------------------- 1 | //go:build !none || round 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Round(x float64) float64 { 9 | return math.Round(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/sin.go: -------------------------------------------------------------------------------- 1 | //go:build !none || sin 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Sin(x float64) float64 { 9 | return math.Sin(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/sqrt.go: -------------------------------------------------------------------------------- 1 | //go:build !none || sqrt 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Sqrt(x float64) float64 { 9 | return math.Sqrt(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/tan.go: -------------------------------------------------------------------------------- 1 | //go:build !none || tan 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Tan(x float64) float64 { 9 | return math.Tan(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/std/trunc.go: -------------------------------------------------------------------------------- 1 | //go:build !none || trunc 2 | 3 | package main 4 | 5 | import "math" 6 | 7 | //go:export f 8 | func Trunc(x float64) float64 { 9 | return math.Trunc(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/atan.go: -------------------------------------------------------------------------------- 1 | //go:build !none || atan 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Atan(a float32) float32 { 9 | return tinymath.Atan(a) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/atan2.go: -------------------------------------------------------------------------------- 1 | //go:build !none || atan2 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Atan2(a, b float32) float32 { 9 | return tinymath.Atan2(a, b) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/exp.go: -------------------------------------------------------------------------------- 1 | //go:build !none || exp 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Exp(x float32) float32 { 9 | return tinymath.Exp(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/fract.go: -------------------------------------------------------------------------------- 1 | //go:build !none || fract 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Fract(x float32) float32 { 9 | return tinymath.Fract(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/hypot.go: -------------------------------------------------------------------------------- 1 | //go:build !none || hypot 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Hypot(a, b float32) float32 { 9 | return tinymath.Hypot(a, b) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/ln.go: -------------------------------------------------------------------------------- 1 | //go:build !none || ln 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Ln(x float32) float32 { 9 | return tinymath.Ln(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/powf.go: -------------------------------------------------------------------------------- 1 | //go:build !none || powf 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func PowF(a, b float32) float32 { 9 | return tinymath.PowF(a, b) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/round.go: -------------------------------------------------------------------------------- 1 | //go:build !none || round 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Round(x float32) float32 { 9 | return tinymath.Round(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/sin.go: -------------------------------------------------------------------------------- 1 | //go:build !none || sin 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Sin(x float32) float32 { 9 | return tinymath.Sin(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/sqrt.go: -------------------------------------------------------------------------------- 1 | //go:build !none || sqrt 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Sqrt(x float32) float32 { 9 | return tinymath.Sqrt(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/tan.go: -------------------------------------------------------------------------------- 1 | //go:build !none || tan 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Tan(x float32) float32 { 9 | return tinymath.Tan(x) 10 | } 11 | -------------------------------------------------------------------------------- /size_bench/tiny/trunc.go: -------------------------------------------------------------------------------- 1 | //go:build !none || trunc 2 | 3 | package main 4 | 5 | import "github.com/orsinium-labs/tinymath" 6 | 7 | //go:export f 8 | func Trunc(x float32) float32 { 9 | return tinymath.Trunc(x) 10 | } 11 | -------------------------------------------------------------------------------- /tinymath.go: -------------------------------------------------------------------------------- 1 | package tinymath 2 | 3 | import "unsafe" 4 | 5 | const ( 6 | signMask uint32 = 0x8000_0000 7 | mantissaBits = 23 8 | expMask uint32 = 0b0111_1111_1000_0000_0000_0000_0000_0000 9 | expBias = 127 10 | ) 11 | 12 | func ToBits(x float32) uint32 { 13 | return *(*uint32)(unsafe.Pointer(&x)) 14 | } 15 | 16 | func FromBits(x uint32) float32 { 17 | return *(*float32)(unsafe.Pointer(&x)) 18 | } 19 | 20 | func Max[N float32 | int32](a, b N) N { 21 | if a > b { 22 | return a 23 | } 24 | return b 25 | } 26 | 27 | func Min[N float32 | int32](a, b N) N { 28 | if a < b { 29 | return a 30 | } 31 | return b 32 | } 33 | 34 | // Computes the absolute value of `self`. 35 | // / 36 | // Returns [`NAN`] if the number is [`NAN`]. 37 | func Abs(self float32) float32 { 38 | return FromBits(ToBits(self) & ^signMask) 39 | } 40 | 41 | // Returns a number composed of the magnitude of `self` and the sign of 42 | // `sign`. 43 | func CopySign(self float32, sign float32) float32 { 44 | source_bits := ToBits(sign) 45 | source_sign := source_bits & signMask 46 | signless_destination_bits := ToBits(self) & ^signMask 47 | return FromBits(signless_destination_bits | source_sign) 48 | } 49 | 50 | // Calculates Euclidean division, the matching method for `rem_euclid`. 51 | func DivEuclid(self float32, rhs float32) float32 { 52 | return (self - RemEuclid(self, rhs)) / rhs 53 | } 54 | 55 | // Returns `e^(self)`, (the exponential function). 56 | func Exp(self float32) float32 { 57 | return ExpLn2Approx(self, 4) 58 | } 59 | 60 | // Exp approximation for `f32`. 61 | func ExpLn2Approx(self float32, partial_iter uint32) float32 { 62 | const LOG2_E = 1.44269504088896340735992468100189214 63 | 64 | if self == 0.0 { 65 | return 1 66 | } 67 | if Abs(self-1) < Epsilon { 68 | return E 69 | } 70 | if Abs(self-(-1)) < Epsilon { 71 | return 1. / E 72 | } 73 | 74 | // log base 2(E) == 1/ln(2) 75 | // x_fract + x_whole = x/ln2_recip 76 | // ln2*(x_fract + x_whole) = x 77 | x_ln2recip := self * LOG2_E 78 | x_fract := Fract(x_ln2recip) 79 | x_trunc := Trunc(x_ln2recip) 80 | 81 | //guaranteed to be 0 < x < 1.0 82 | x_fract = x_fract * Ln2 83 | fract_exp := ExpSmallX(x_fract, partial_iter) 84 | 85 | //need the 2^n portion, we can just extract that from the whole number exp portion 86 | fract_exponent := saturatingAdd(extractExponentValue(fract_exp), int32(x_trunc)) 87 | 88 | if fract_exponent < -expBias { 89 | return 0.0 90 | } 91 | 92 | if fract_exponent > expBias+1 { 93 | return Inf 94 | } 95 | 96 | return setExponent(fract_exp, fract_exponent) 97 | } 98 | 99 | // if x is between 0.0 and 1.0, we can approximate it with the a series 100 | // 101 | // Series from here: 102 | // 103 | // 104 | // e^x ~= 1 + x(1 + x/2(1 + (x? 105 | func ExpSmallX(self float32, iter uint32) float32 { 106 | var total float32 = 1.0 107 | for i := float32(iter - 1); i > 0.; i-- { 108 | total = 1.0 + ((self / i) * total) 109 | } 110 | return total 111 | } 112 | 113 | // Returns the fractional part of a number with sign. 114 | func Fract(self float32) float32 { 115 | const MANTISSA_MASK = 0b0000_0000_0111_1111_1111_1111_1111_1111 116 | 117 | x_bits := ToBits(self) 118 | exponent := extractExponentValue(self) 119 | 120 | // we know it is *only* fraction 121 | if exponent < 0 { 122 | return self 123 | } 124 | 125 | // find the part of the fraction that would be left over 126 | fractional_part := (x_bits << exponent) & MANTISSA_MASK 127 | 128 | // if there isn't a fraction we can just return 0 129 | if fractional_part == 0 { 130 | // TODO: most people don't actually care about -0.0, 131 | // so would it be better to just not CopySign? 132 | return CopySign(0.0, self) 133 | } 134 | 135 | // Note: alternatively this could use -1.0, but it's assumed subtraction would be more costly 136 | // example: 'new_exponent_bits := 127.overflowing_shl(23))) - 1.0' 137 | exponent_shift := (leadingZeros(fractional_part) - (32 - mantissaBits)) + 1 138 | 139 | fractional_normalized := (fractional_part << exponent_shift) & MANTISSA_MASK 140 | 141 | new_exponent_bits := (expBias - (exponent_shift)) << mantissaBits 142 | 143 | return CopySign(FromBits(fractional_normalized|new_exponent_bits), self) 144 | } 145 | 146 | // Calculate the length of the hypotenuse of a right-angle triangle. 147 | func Hypot(self float32, rhs float32) float32 { 148 | return Sqrt(self*self + rhs*rhs) 149 | } 150 | 151 | // Fast approximation of `1/x`. 152 | func Inv(self float32) float32 { 153 | return FromBits(0x7f00_0000 - ToBits(self)) 154 | } 155 | 156 | // Approximate inverse square root with an average deviation of ~5%. 157 | func InvSqrt(self float32) float32 { 158 | return FromBits(0x5f37_5a86 - (ToBits(self) >> 1)) 159 | } 160 | 161 | // Check if the given number is NaN. 162 | func IsNaN(x float32) bool { 163 | return x != x 164 | } 165 | 166 | // Check if the given number is even. 167 | func IsEven(x float32) bool { 168 | half := x / 2 169 | return IsInteger(half) 170 | } 171 | 172 | // Check if the given number has no numbers after dot. 173 | func IsInteger(x float32) bool { 174 | return Floor(x) == x 175 | } 176 | 177 | // Check if the number has a positive sign 178 | func IsSignPositive(x float32) bool { 179 | return ToBits(x)&(1<<31) == 0 180 | } 181 | 182 | // Approximates the natural logarithm of the number. 183 | // Note: excessive precision ignored because it hides the origin of the numbers used for the 184 | // ln(1.0->2.0) polynomial 185 | func Ln(self float32) float32 { 186 | // x may essentially be 1.0 but, as clippy notes, these kinds of 187 | // floating point comparisons can fail when the bit pattern is not the sames 188 | if Abs(self-1) < Epsilon { 189 | return 0.0 190 | } 191 | 192 | x_less_than_1 := self < 1.0 193 | 194 | // Note: we could use the fast inverse approximation here found in super::inv::inv_approx, but 195 | // the precision of such an approximation is assumed not good enough. 196 | x_working := self 197 | if x_less_than_1 { 198 | x_working = Inv(self) 199 | } 200 | 201 | // according to the SO post ln(x) = ln((2^n)*y)= ln(2^n) + ln(y) = ln(2) * n + ln(y) 202 | // get exponent value 203 | base2_exponent := uint32(extractExponentValue(x_working)) 204 | divisor := FromBits(ToBits(x_working) & expMask) 205 | 206 | // supposedly normalizing between 1.0 and 2.0 207 | x_working = x_working / divisor 208 | 209 | // approximate polynomial generated from maple in the post using Remez Algorithm: 210 | // https://en.wikipedia.org/wiki/Remez_algorithm 211 | ln_1to2_polynomial := -1.741_793_9 + (2.821_202_6+(-1.469_956_8+(0.447_179_55-0.056_570_851*x_working)*x_working)*x_working)*x_working 212 | 213 | // ln(2) * n + ln(y) 214 | result := float32(base2_exponent)*Ln2 + ln_1to2_polynomial 215 | 216 | if x_less_than_1 { 217 | return -result 218 | } 219 | return result 220 | } 221 | 222 | // Approximates the logarithm of the number with respect to an arbitrary base. 223 | func Log(self float32, base float32) float32 { 224 | return (1 / Ln(base)) * Ln(self) 225 | } 226 | 227 | // Approximates the base 10 logarithm of the number. 228 | func Log10(self float32) float32 { 229 | const LOG10_E = 0.434294481903251827651128918916605082 230 | return Ln(self) * LOG10_E 231 | } 232 | 233 | // Approximates the base 2 logarithm of the number. 234 | func Log2(self float32) float32 { 235 | const LOG2_E = 1.44269504088896340735992468100189214 236 | return Ln(self) * LOG2_E 237 | } 238 | 239 | // Approximates a number raised to a floating point power. 240 | func PowF(self float32, n float32) float32 { 241 | // using x^n = exp(ln(x^n)) = exp(n*ln(x)) 242 | if self >= 0.0 { 243 | return Exp(n * Ln(self)) 244 | } else if IsInteger(n) { 245 | return NaN 246 | } else if IsEven(n) { 247 | // if n is even, then we know that the result will have no sign, so we can remove it 248 | return n * Exp(Ln(withoutSign(self))) 249 | } else { 250 | // if n isn't even, we need to multiply by -1.0 at the end. 251 | return -(n * Exp(Ln(withoutSign(self)))) 252 | } 253 | } 254 | 255 | // Approximates a number raised to an integer power. 256 | func PowI(self float32, n int32) float32 { 257 | base := self 258 | abs_n := n 259 | if n < 0 { 260 | abs_n = -n 261 | } 262 | var result float32 = 1 263 | if n < 0 { 264 | base = 1.0 / self 265 | } 266 | if n == 0 { 267 | return 1 268 | } 269 | // 0.0 == 0.0 and -0.0 according to IEEE standards. 270 | if self == 0.0 && n > 0 { 271 | return self 272 | } 273 | 274 | // For values less than 2.0, but greater than 0.5 (1.0/2.0), you can multiply longer without 275 | // going over exponent, i.e. 1.1 multiplied against itself will grow slowly. 276 | abs := Abs(self) 277 | if 0.5 <= abs && abs < 2.0 { 278 | // Approximation if we end up outside of the range of floating point values, 279 | // then we end early 280 | approx_final_exponent := extractExponentValue(self) * n 281 | const max_representable_exponent = 127 282 | const min_representable_exponent = -126 - mantissaBits 283 | if approx_final_exponent > max_representable_exponent || (self == 0.0 && approx_final_exponent < 0) { 284 | if IsSignPositive(self) || n&1 == 0 { 285 | return Inf 286 | } else { 287 | return NegInf 288 | } 289 | } else if approx_final_exponent < min_representable_exponent { 290 | // We may want to copy the sign and do the same thing as above, 291 | // but that seems like an awful amount of work when 99.99999% of people only care 292 | // about bare zero 293 | return 0.0 294 | } 295 | } 296 | 297 | for { 298 | if (abs_n & 1) == 1 { 299 | result *= base 300 | } 301 | 302 | abs_n >>= 1 303 | if abs_n == 0 { 304 | return float32(result) 305 | } 306 | base *= base 307 | } 308 | } 309 | 310 | // Returns the reciprocal (inverse) of a number, `1/x`. 311 | func Recip(self float32) float32 { 312 | x := self 313 | var sx float32 = 1. 314 | if x < 0. { 315 | sx = -1. 316 | } 317 | x *= sx 318 | v := FromBits(0x7EF1_27EA - ToBits(x)) 319 | w := x * v 320 | v *= 8.0 + w*(-28.0+w*(56.0+w*(-70.0+w*(56.0+w*(-28.0+w*(8.0-w)))))) 321 | return v * sx 322 | } 323 | 324 | // Calculates the least non-negative remainder of `self (mod rhs)`. 325 | func RemEuclid(self float32, rhs float32) float32 { 326 | r := self - Floor(self/rhs)*rhs 327 | if r >= 0.0 { 328 | return r 329 | } else { 330 | return r + Abs(rhs) 331 | } 332 | } 333 | 334 | // Returns the nearest integer to a number. 335 | func Round(self float32) float32 { 336 | return float32(int32(self + CopySign(0.5, self))) 337 | } 338 | 339 | // Returns a number that represents the sign of `self`. 340 | // / 341 | // * `1.0` if the number is positive, `+0.0` or `INFINITY` 342 | // * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` 343 | // * `NAN` if the number is `NAN` 344 | func Sign(self float32) float32 { 345 | if IsNaN(self) { 346 | return NaN 347 | } else { 348 | return CopySign(1.0, self) 349 | } 350 | } 351 | 352 | func extractExponentBits(self float32) uint32 { 353 | return (ToBits(self) & expMask) >> mantissaBits 354 | } 355 | 356 | func extractExponentValue(self float32) int32 { 357 | return int32(extractExponentBits(self)) - expBias 358 | } 359 | 360 | func setExponent(self float32, exponent int32) float32 { 361 | without_exponent := ToBits(self) & ^expMask 362 | only_exponent := uint32(exponent+expBias) << mantissaBits 363 | return FromBits(without_exponent | only_exponent) 364 | } 365 | 366 | func saturatingAdd(a, b int32) int32 { 367 | c := a + b 368 | if (c > a) == (b > 0) { 369 | return c 370 | } 371 | return 2147483647 372 | } 373 | 374 | func withoutSign(self float32) float32 { 375 | return FromBits(ToBits(self) & ^signMask) 376 | } 377 | -------------------------------------------------------------------------------- /tinymath_test.go: -------------------------------------------------------------------------------- 1 | package tinymath_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/orsinium-labs/tinymath" 9 | ) 10 | 11 | type Case struct { 12 | Given float32 13 | Expected float32 14 | } 15 | 16 | type Case2 struct { 17 | Left float32 18 | Right float32 19 | Expected float32 20 | } 21 | 22 | func eq(t *testing.T, act, exp float32) { 23 | t.Helper() 24 | if act != exp { 25 | t.Fatalf("%f != %f", act, exp) 26 | } 27 | } 28 | 29 | func close(t *testing.T, act, exp float32, eps float32) { 30 | t.Helper() 31 | if tinymath.IsNaN(exp) && !tinymath.IsNaN(act) { 32 | t.Fatalf("%f is not NaN", act) 33 | } 34 | delta := tinymath.Abs(act - exp) 35 | if delta > eps { 36 | t.Fatalf("%f != %f", act, exp) 37 | } 38 | } 39 | 40 | func TestAbs(t *testing.T) { 41 | t.Parallel() 42 | cases := []Case{ 43 | {0.0, 0.0}, 44 | {0.1, 0.1}, 45 | {1.0, 1.0}, 46 | {2.0, 2.0}, 47 | {3.45, 3.45}, 48 | {-0.1, 0.1}, 49 | {-1.0, 1.0}, 50 | {-2.0, 2.0}, 51 | {-3.45, 3.45}, 52 | } 53 | for _, c := range cases { 54 | c := c 55 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 56 | eq(t, tinymath.Abs(c.Given), c.Expected) 57 | }) 58 | } 59 | } 60 | 61 | func TestCeil(t *testing.T) { 62 | t.Parallel() 63 | cases := []Case{ 64 | {-1.1, -1.0}, 65 | {-0.1, 0.0}, 66 | {0.0, 0.0}, 67 | {1.0, 1.0}, 68 | {1.1, 2.0}, 69 | {2.9, 3.0}, 70 | } 71 | for _, c := range cases { 72 | c := c 73 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 74 | eq(t, tinymath.Ceil(c.Given), c.Expected) 75 | }) 76 | } 77 | } 78 | 79 | func TestCopySign(t *testing.T) { 80 | t.Parallel() 81 | const large = 100_000_000.13425345345 82 | cases := []Case2{ 83 | {-1.0, -1.0, -1.0}, 84 | {-1.0, 1.0, 1.0}, 85 | {1.0, -1.0, -1.0}, 86 | {1.0, 1.0, 1.0}, 87 | {large, -large, -large}, 88 | {-large, large, large}, 89 | {large, large, large}, 90 | {-large, -large, -large}, 91 | } 92 | for _, c := range cases { 93 | c := c 94 | t.Run(fmt.Sprintf("%f_%f", c.Left, c.Right), func(t *testing.T) { 95 | eq(t, tinymath.CopySign(c.Left, c.Right), c.Expected) 96 | }) 97 | } 98 | } 99 | 100 | func TestDivEuclid(t *testing.T) { 101 | t.Parallel() 102 | cases := []Case2{ 103 | {7., 4., 1.}, 104 | {-7., 4., -2.}, 105 | {7., -4., -1.}, 106 | {-7., -4., 2.}, 107 | } 108 | for _, c := range cases { 109 | c := c 110 | t.Run(fmt.Sprintf("%f_%f", c.Left, c.Right), func(t *testing.T) { 111 | eq(t, tinymath.DivEuclid(c.Left, c.Right), c.Expected) 112 | }) 113 | } 114 | } 115 | 116 | func TestExp(t *testing.T) { 117 | t.Parallel() 118 | cases := []Case{ 119 | {1e-07, 1.0000001}, 120 | {1e-06, 1.000001}, 121 | {1e-05, 1.00001}, 122 | {1e-04, 1.0001}, 123 | {0.001, 1.0010005}, 124 | {0.01, 1.0100502}, 125 | {0.1, 1.105171}, 126 | {1.0, 2.7182817}, 127 | {10.0, 22026.465}, 128 | {-1e-08, 1.0}, 129 | {-1e-07, 0.9999999}, 130 | {-1e-06, 0.999999}, 131 | {-1e-05, 0.99999}, 132 | {-1e-04, 0.9999}, 133 | {-0.001, 0.9990005}, 134 | {-0.01, 0.99004984}, 135 | {-0.1, 0.9048374}, 136 | {-1.0, 0.36787945}, 137 | {-10.0, 4.539_993e-5}, 138 | } 139 | for _, c := range cases { 140 | c := c 141 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 142 | close(t, tinymath.Exp(c.Given), c.Expected, 0.001*c.Expected) 143 | }) 144 | } 145 | 146 | for i := float32(-10.); i < 10.; i += .34 { 147 | i := i 148 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 149 | close(t, tinymath.Exp(i), float32(math.Exp(float64(i))), i*i*i/i) 150 | }) 151 | } 152 | 153 | } 154 | 155 | func TestFloor(t *testing.T) { 156 | t.Parallel() 157 | cases := []Case{ 158 | {-1.1, -2.0}, 159 | {-0.1, -1.0}, 160 | {0.0, 0.0}, 161 | {1.0, 1.0}, 162 | {1.1, 1.0}, 163 | {2.9, 2.0}, 164 | } 165 | for _, c := range cases { 166 | c := c 167 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 168 | eq(t, tinymath.Floor(c.Given), c.Expected) 169 | }) 170 | } 171 | } 172 | 173 | func TestFract(t *testing.T) { 174 | t.Parallel() 175 | cases := []Case{ 176 | {tinymath.Fract(2.9) + 2.0, 2.9}, 177 | {tinymath.Fract(-1.1) - 1.0, -1.1}, 178 | {tinymath.Fract(-0.1), -0.1}, 179 | {tinymath.Fract(0.0), 0.0}, 180 | {tinymath.Fract(1.0) + 1.0, 1.0}, 181 | {tinymath.Fract(1.1) + 1.0, 1.1}, 182 | {tinymath.Fract(-100_000_000.13425345345), 0.0}, 183 | {tinymath.Fract(100_000_000.13425345345), 0.0}, 184 | } 185 | for _, c := range cases { 186 | c := c 187 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 188 | eq(t, c.Given, c.Expected) 189 | }) 190 | } 191 | } 192 | 193 | func TestHypot(t *testing.T) { 194 | t.Parallel() 195 | cases := []Case2{ 196 | {2., 3., tinymath.Sqrt(13.)}, 197 | {3., 4., tinymath.Sqrt(25.)}, 198 | {12., 7., tinymath.Sqrt(193.)}, 199 | } 200 | for _, c := range cases { 201 | c := c 202 | t.Run(fmt.Sprintf("%f_%f", c.Left, c.Right), func(t *testing.T) { 203 | close(t, tinymath.Hypot(c.Left, c.Right), c.Expected, tinymath.Epsilon) 204 | }) 205 | } 206 | } 207 | 208 | func TestInv(t *testing.T) { 209 | t.Parallel() 210 | for i := float32(1.); i < 100.; i += .67 { 211 | i := i 212 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 213 | exp := 1.0 / i 214 | close(t, tinymath.Inv(i), exp, 0.08) 215 | }) 216 | } 217 | } 218 | 219 | func TestInvSqrt(t *testing.T) { 220 | t.Parallel() 221 | for i := float32(1.); i < 100.; i++ { 222 | i := i 223 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 224 | exp := 1.0 / tinymath.Sqrt(i) 225 | close(t, tinymath.InvSqrt(i), exp, 0.05) 226 | }) 227 | } 228 | } 229 | 230 | func TestIsNaN(t *testing.T) { 231 | t.Parallel() 232 | if !tinymath.IsNaN(tinymath.NaN) { 233 | t.Fail() 234 | } 235 | if tinymath.IsNaN(tinymath.Inf) { 236 | t.Fail() 237 | } 238 | if tinymath.IsNaN(tinymath.NegInf) { 239 | t.Fail() 240 | } 241 | if tinymath.IsNaN(0.0) { 242 | t.Fail() 243 | } 244 | if tinymath.IsNaN(0.1) { 245 | t.Fail() 246 | } 247 | if tinymath.IsNaN(-1.1) { 248 | t.Fail() 249 | } 250 | if tinymath.IsNaN(13.1) { 251 | t.Fail() 252 | } 253 | } 254 | 255 | func TestIsSignPositive(t *testing.T) { 256 | t.Parallel() 257 | if !tinymath.IsSignPositive(tinymath.NaN) { 258 | t.Fatalf("nan") 259 | } 260 | if !tinymath.IsSignPositive(tinymath.Inf) { 261 | t.Fatalf("inf") 262 | } 263 | if tinymath.IsSignPositive(tinymath.NegInf) { 264 | t.Fatalf("-inf") 265 | } 266 | if !tinymath.IsSignPositive(0.0) { 267 | t.Fatalf("0.0") 268 | } 269 | if !tinymath.IsSignPositive(0.1) { 270 | t.Fatalf("0.1") 271 | } 272 | if tinymath.IsSignPositive(-1.1) { 273 | t.Fatalf("-1.1") 274 | } 275 | if !tinymath.IsSignPositive(13.1) { 276 | t.Fatalf("13.1") 277 | } 278 | } 279 | 280 | func TestSqrt(t *testing.T) { 281 | t.Parallel() 282 | cases := []Case{ 283 | {1.0, 1.0}, 284 | // {2.0, 1.414}, 285 | // {3.0, 1.732}, 286 | {4.0, 2.0}, 287 | {5.0, 2.236}, 288 | // {10.0, 3.162}, 289 | {100.0, 10.0}, 290 | {250.0, 15.811}, 291 | {500.0, 22.36}, 292 | {1000.0, 31.622}, 293 | {2500.0, 50.0}, 294 | {5000.0, 70.710}, 295 | {1000000.0, 1000.0}, 296 | {2500000.0, 1581.138}, 297 | {5000000.0, 2236.067}, 298 | {10000000.0, 3162.277}, 299 | {25000000.0, 5000.0}, 300 | {50000000.0, 7071.067}, 301 | {100000000.0, 10000.0}, 302 | } 303 | for _, c := range cases { 304 | c := c 305 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 306 | close(t, tinymath.Sqrt(c.Given), c.Expected, 0.005*c.Given) 307 | }) 308 | } 309 | 310 | for i := float32(1.); i < 100.; i += .34 { 311 | i := i 312 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 313 | close(t, tinymath.Sqrt(i), float32(math.Sqrt(float64(i))), 0.05*i) 314 | }) 315 | } 316 | } 317 | 318 | func TestLn(t *testing.T) { 319 | t.Parallel() 320 | cases := []Case{ 321 | {1e-20, -46.0517}, 322 | {1e-19, -43.749115}, 323 | {1e-18, -41.446533}, 324 | {1e-17, -39.143948}, 325 | {1e-16, -36.841362}, 326 | {1e-15, -34.538776}, 327 | {1e-14, -32.23619}, 328 | {1e-13, -29.933607}, 329 | {1e-12, -27.631021}, 330 | {1e-11, -25.328436}, 331 | {1e-10, -23.02585}, 332 | {1e-09, -20.723267}, 333 | {1e-08, -18.420681}, 334 | {1e-07, -16.118095}, 335 | {1e-06, -13.815511}, 336 | {1e-05, -11.512925}, 337 | {1e-04, -9.2103405}, 338 | {0.001, -6.9077554}, 339 | {0.01, -4.6051702}, 340 | {0.1, -2.3025851}, 341 | {10.0, 2.3025851}, 342 | {100.0, 4.6051702}, 343 | {1000.0, 6.9077554}, 344 | {10000.0, 9.2103405}, 345 | {100000.0, 11.512925}, 346 | {1000000.0, 13.815511}, 347 | {10000000.0, 16.118095}, 348 | {100000000.0, 18.420681}, 349 | {1000000000.0, 20.723267}, 350 | {10000000000.0, 23.02585}, 351 | {100000000000.0, 25.328436}, 352 | {1000000000000.0, 27.631021}, 353 | {10000000000000.0, 29.933607}, 354 | {100000000000000.0, 32.23619}, 355 | {1000000000000000.0, 34.538776}, 356 | {1e+16, 36.841362}, 357 | {1e+17, 39.143948}, 358 | {1e+18, 41.446533}, 359 | {1e+19, 43.749115}, 360 | } 361 | t.Run("table", func(t *testing.T) { 362 | t.Parallel() 363 | for _, c := range cases { 364 | c := c 365 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 366 | act := tinymath.Ln(c.Given) 367 | delta := tinymath.Abs(act - c.Expected) 368 | if delta/c.Expected > 0.001 { 369 | t.Fatalf("%f != %f", act, c.Expected) 370 | } 371 | }) 372 | } 373 | }) 374 | 375 | t.Run("stdlib", func(t *testing.T) { 376 | t.Parallel() 377 | for i := float32(1.); i < 100.; i += .34 { 378 | i := i 379 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 380 | close(t, tinymath.Ln(i), float32(math.Log(float64(i))), 0.001) 381 | }) 382 | } 383 | }) 384 | } 385 | 386 | func TestLog(t *testing.T) { 387 | t.Parallel() 388 | cases := []Case2{ 389 | {1e-20, 3, -41.918_064}, 390 | {1e-19, 3, -39.822_16}, 391 | {1e-18, 3, -37.726_26}, 392 | {1e-17, 3, -35.630_356}, 393 | {1e-16, 3, -33.534_454}, 394 | {1e-15, 3, -31.438_549}, 395 | {1e-14, 3, -29.342_646}, 396 | {1e-13, 3, -27.246_744}, 397 | {1e-12, 3, -25.150_839}, 398 | {1e-11, 3, -23.054_935}, 399 | {1e-10, 3, -20.959_032}, 400 | {1e-09, 3, -18.863_13}, 401 | {1e-08, 3, -16.767_227}, 402 | {1e-07, 3, -14.671_323}, 403 | {1e-06, 3, -12.575_419}, 404 | {1e-05, 3, -10.479_516}, 405 | {1e-04, 3, -8.383_614}, 406 | {0.001, 3, -6.287_709_7}, 407 | {0.01, 3, -4.191_807}, 408 | {0.1, 3, -2.095_903_4}, 409 | {10.0, 3, 2.095_903_4}, 410 | {100.0, 3, 4.191_807}, 411 | {1000.0, 3, 6.287_709_7}, 412 | {10000.0, 3, 8.383_614}, 413 | {100000.0, 3, 10.479_516}, 414 | {1000000.0, 3, 12.575_419}, 415 | {10000000.0, 3, 14.671_323}, 416 | {100000000.0, 3, 16.767_227}, 417 | {1000000000.0, 3, 18.863_13}, 418 | {10000000000.0, 3, 20.959_032}, 419 | {100000000000.0, 3, 23.054_935}, 420 | {1000000000000.0, 3, 25.150_839}, 421 | {10000000000000.0, 3, 27.246_744}, 422 | {100000000000000.0, 3, 29.342_646}, 423 | {1000000000000000.0, 3, 31.438_549}, 424 | {1e+16, 3, 33.534_454}, 425 | {1e+17, 3, 35.630_356}, 426 | {1e+18, 3, 37.726_26}, 427 | {1e+19, 3, 39.822_16}, 428 | 429 | {1e-20, 5.5, -27.013_786}, 430 | {1e-19, 5.5, -25.663_097}, 431 | {1e-18, 5.5, -24.312_408}, 432 | {1e-17, 5.5, -22.961_72}, 433 | {1e-16, 5.5, -21.611_03}, 434 | {1e-15, 5.5, -20.260_34}, 435 | {1e-14, 5.5, -18.909_65}, 436 | {1e-13, 5.5, -17.558_962}, 437 | {1e-12, 5.5, -16.208_273}, 438 | {1e-11, 5.5, -14.857_583}, 439 | {1e-10, 5.5, -13.506_893}, 440 | {1e-09, 5.5, -12.156_204}, 441 | {1e-08, 5.5, -10.805_515}, 442 | {1e-07, 5.5, -9.454_825}, 443 | {1e-06, 5.5, -8.104_136}, 444 | {1e-05, 5.5, -6.753_446_6}, 445 | {1e-04, 5.5, -5.402_757_6}, 446 | {0.001, 5.5, -4.052_068}, 447 | {0.01, 5.5, -2.701_378_8}, 448 | {0.1, 5.5, -1.350_689_4}, 449 | {10.0, 5.5, 1.350_689_4}, 450 | {100.0, 5.5, 2.701_378_8}, 451 | {1000.0, 5.5, 4.052_068}, 452 | {10000.0, 5.5, 5.402_757_6}, 453 | {100000.0, 5.5, 6.753_446_6}, 454 | {1000000.0, 5.5, 8.104_136}, 455 | {10000000.0, 5.5, 9.454_825}, 456 | {100000000.0, 5.5, 10.805_515}, 457 | {1000000000.0, 5.5, 12.156_204}, 458 | {10000000000.0, 5.5, 13.506_893}, 459 | {100000000000.0, 5.5, 14.857_583}, 460 | {1000000000000.0, 5.5, 16.208_273}, 461 | {10000000000000.0, 5.5, 17.558_962}, 462 | {100000000000000.0, 5.5, 18.909_65}, 463 | {1000000000000000.0, 5.5, 20.260_34}, 464 | {1e+16, 5.5, 21.611_03}, 465 | {1e+17, 5.5, 22.961_72}, 466 | {1e+18, 5.5, 24.312_408}, 467 | {1e+19, 5.5, 25.663_097}, 468 | 469 | {1e-20, 12.7, -18.119_164}, 470 | {1e-19, 12.7, -17.213_205}, 471 | {1e-18, 12.7, -16.307_247}, 472 | {1e-17, 12.7, -15.401_289}, 473 | {1e-16, 12.7, -14.495_331}, 474 | {1e-15, 12.7, -13.589_373}, 475 | {1e-14, 12.7, -12.683_414}, 476 | {1e-13, 12.7, -11.777_456}, 477 | {1e-12, 12.7, -10.871_498}, 478 | {1e-11, 12.7, -9.965_54}, 479 | {1e-10, 12.7, -9.059_582}, 480 | {1e-09, 12.7, -8.153_624}, 481 | {1e-08, 12.7, -7.247_665_4}, 482 | {1e-07, 12.7, -6.341_707}, 483 | {1e-06, 12.7, -5.435_749}, 484 | {1e-05, 12.7, -4.529_791}, 485 | {1e-04, 12.7, -3.623_832_7}, 486 | {0.001, 12.7, -2.717_874_5}, 487 | {0.01, 12.7, -1.811_916_4}, 488 | {0.1, 12.7, -0.905_958_2}, 489 | {10.0, 12.7, 0.905_958_2}, 490 | {100.0, 12.7, 1.811_916_4}, 491 | {1000.0, 12.7, 2.717_874_5}, 492 | {10000.0, 12.7, 3.623_832_7}, 493 | {100000.0, 12.7, 4.529_791}, 494 | {1000000.0, 12.7, 5.435_749}, 495 | {10000000.0, 12.7, 6.341_707}, 496 | {100000000.0, 12.7, 7.247_665_4}, 497 | {1000000000.0, 12.7, 8.153_624}, 498 | {10000000000.0, 12.7, 9.059_582}, 499 | {100000000000.0, 12.7, 9.965_54}, 500 | {1000000000000.0, 12.7, 10.871_498}, 501 | {10000000000000.0, 12.7, 11.777_456}, 502 | {100000000000000.0, 12.7, 12.683_414}, 503 | {1000000000000000.0, 12.7, 13.589_373}, 504 | {1e+16, 12.7, 14.495_331}, 505 | {1e+17, 12.7, 15.401_289}, 506 | {1e+18, 12.7, 16.307_247}, 507 | {1e+19, 12.7, 17.213_205}, 508 | } 509 | t.Run("table", func(t *testing.T) { 510 | t.Parallel() 511 | for _, c := range cases { 512 | c := c 513 | t.Run(fmt.Sprintf("%f_%f", c.Left, c.Right), func(t *testing.T) { 514 | act := tinymath.Log(c.Left, c.Right) 515 | delta := tinymath.Abs(act - c.Expected) 516 | if delta/c.Expected > 0.001 { 517 | t.Fatalf("%f != %f", act, c.Expected) 518 | } 519 | }) 520 | } 521 | }) 522 | 523 | t.Run("stdlib", func(t *testing.T) { 524 | t.Parallel() 525 | for i := float32(1.); i < 100.; i += .34 { 526 | i := i 527 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 528 | close(t, tinymath.Log(i, 10.), float32(math.Log10(float64(i))), 0.001) 529 | }) 530 | } 531 | }) 532 | } 533 | 534 | func TestLog2(t *testing.T) { 535 | t.Parallel() 536 | cases := []Case{ 537 | {1e-20, -66.43856}, 538 | {1e-19, -63.116634}, 539 | {1e-18, -59.794704}, 540 | {1e-17, -56.47278}, 541 | {1e-16, -53.15085}, 542 | {1e-15, -49.828922}, 543 | {1e-14, -46.506992}, 544 | {1e-13, -43.185066}, 545 | {1e-12, -39.863136}, 546 | {1e-11, -36.54121}, 547 | {1e-10, -33.21928}, 548 | {1e-09, -29.897352}, 549 | {1e-08, -26.575424}, 550 | {1e-07, -23.253496}, 551 | {1e-06, -19.931568}, 552 | {1e-05, -16.60964}, 553 | {1e-04, -13.287712}, 554 | {0.001, -9.965784}, 555 | {0.01, -6.643856}, 556 | {0.1, -3.321928}, 557 | {10.0, 3.321928}, 558 | {100.0, 6.643856}, 559 | {1000.0, 9.965784}, 560 | {10000.0, 13.287712}, 561 | {100000.0, 16.60964}, 562 | {1000000.0, 19.931568}, 563 | {10000000.0, 23.253496}, 564 | {100000000.0, 26.575424}, 565 | {1000000000.0, 29.897352}, 566 | {10000000000.0, 33.21928}, 567 | {100000000000.0, 36.54121}, 568 | {1000000000000.0, 39.863136}, 569 | {10000000000000.0, 43.185066}, 570 | {100000000000000.0, 46.506992}, 571 | {1000000000000000.0, 49.828922}, 572 | {1e+16, 53.15085}, 573 | {1e+17, 56.47278}, 574 | {1e+18, 59.794704}, 575 | {1e+19, 63.116634}, 576 | } 577 | t.Run("table", func(t *testing.T) { 578 | t.Parallel() 579 | for _, c := range cases { 580 | c := c 581 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 582 | act := tinymath.Log2(c.Given) 583 | delta := tinymath.Abs(act - c.Expected) 584 | if delta/c.Expected > 0.001 { 585 | t.Fatalf("%f != %f", act, c.Expected) 586 | } 587 | }) 588 | } 589 | }) 590 | 591 | t.Run("stdlib", func(t *testing.T) { 592 | t.Parallel() 593 | for i := float32(1.); i < 100.; i += .34 { 594 | i := i 595 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 596 | close(t, tinymath.Log2(i), float32(math.Log2(float64(i))), 0.001) 597 | }) 598 | } 599 | }) 600 | } 601 | 602 | func TestLog10(t *testing.T) { 603 | t.Parallel() 604 | cases := []Case{ 605 | {1e-20, -20.0}, 606 | {1e-19, -19.0}, 607 | {1e-18, -18.0}, 608 | {1e-17, -17.0}, 609 | {1e-16, -16.0}, 610 | {1e-15, -15.0}, 611 | {1e-14, -14.0}, 612 | {1e-13, -13.0}, 613 | {1e-12, -12.0}, 614 | {1e-11, -11.0}, 615 | {1e-10, -10.0}, 616 | {1e-09, -9.0}, 617 | {1e-08, -8.0}, 618 | {1e-07, -7.0}, 619 | {1e-06, -6.0}, 620 | {1e-05, -5.0}, 621 | {1e-04, -4.0}, 622 | {0.001, -3.0}, 623 | {0.01, -2.0}, 624 | {0.1, -1.0}, 625 | {10.0, 1.0}, 626 | {100.0, 2.0}, 627 | {1000.0, 3.0}, 628 | {10000.0, 4.0}, 629 | {100000.0, 5.0}, 630 | {1000000.0, 6.0}, 631 | {10000000.0, 7.0}, 632 | {100000000.0, 8.0}, 633 | {1000000000.0, 9.0}, 634 | {10000000000.0, 10.0}, 635 | {100000000000.0, 11.0}, 636 | {1000000000000.0, 12.0}, 637 | {10000000000000.0, 13.0}, 638 | {100000000000000.0, 14.0}, 639 | {1000000000000000.0, 15.0}, 640 | {1e+16, 16.0}, 641 | {1e+17, 17.0}, 642 | {1e+18, 18.0}, 643 | {1e+19, 19.0}, 644 | } 645 | t.Run("table", func(t *testing.T) { 646 | t.Parallel() 647 | for _, c := range cases { 648 | c := c 649 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 650 | act := tinymath.Log10(c.Given) 651 | delta := tinymath.Abs(act - c.Expected) 652 | if delta/c.Expected > 0.001 { 653 | t.Fatalf("%f != %f", act, c.Expected) 654 | } 655 | }) 656 | } 657 | }) 658 | 659 | t.Run("stdlib", func(t *testing.T) { 660 | t.Parallel() 661 | for i := float32(1.); i < 100.; i += .34 { 662 | i := i 663 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 664 | close(t, tinymath.Log10(i), float32(math.Log10(float64(i))), 0.001) 665 | }) 666 | } 667 | }) 668 | } 669 | 670 | func TestPowF(t *testing.T) { 671 | t.Parallel() 672 | cases := []Case2{ 673 | {-1e-20, 3, 1.0}, 674 | {-1e-19, 3, 1.0}, 675 | {-1e-18, 3, 1.0}, 676 | {-1e-17, 3, 1.0}, 677 | {-1e-16, 3, 0.9999999999999999}, 678 | {-1e-15, 3, 0.9999999999999989}, 679 | {-1e-14, 3, 0.999999999999989}, 680 | {-1e-13, 3, 0.9999999999998901}, 681 | {-1e-12, 3, 0.9999999999989014}, 682 | {-1e-11, 3, 0.9999999999890139}, 683 | {-1e-10, 3, 0.9999999998901388}, 684 | {-1e-09, 3, 0.9999999989013877}, 685 | {-1e-08, 3, 0.9999999890138772}, 686 | {-1e-07, 3, 0.999_999_9}, 687 | {-1e-06, 3, 0.999_998_9}, 688 | {-1e-05, 3, 0.999_989_03}, 689 | {-1e-04, 3, 0.999_890_15}, 690 | {-0.001, 3, 0.998_901_96}, 691 | {-0.01, 3, 0.989_074}, 692 | {-0.1, 3, 0.895_958_5}, 693 | {-1.0, 3, 0.333_333_34}, 694 | {-10.0, 3, 1.693_508_8e-5}, 695 | {-100.0, 3, 0e0}, 696 | {-1000.0, 3, 0.0}, 697 | {1e-20, 3, 1.0}, 698 | {1e-19, 3, 1.0}, 699 | {1e-18, 3, 1.0}, 700 | {1e-17, 3, 1.0}, 701 | {1e-16, 3, 1.0}, 702 | {1e-15, 3, 1.000000000000001}, 703 | {1e-14, 3, 1.0000000000000109}, 704 | {1e-13, 3, 1.00000000000011}, 705 | {1e-12, 3, 1.0000000000010987}, 706 | {1e-11, 3, 1.000000000010986}, 707 | {1e-10, 3, 1.0000000001098612}, 708 | {1e-09, 3, 1.0000000010986123}, 709 | {1e-08, 3, 1.000000010986123}, 710 | {1e-07, 3, 1.000_000_1}, 711 | {1e-06, 3, 1.000_001_1}, 712 | {1e-05, 3, 1.000_011}, 713 | {1e-04, 3, 1.000_109_9}, 714 | {0.001, 3, 1.001_099_2}, 715 | {0.01, 3, 1.011_046_6}, 716 | {0.1, 3, 1.116_123_2}, 717 | {1.0, 3, 3.0}, 718 | {10.0, 3, 59049.0}, 719 | 720 | {-1e-20, 150, 1.0}, 721 | {-1e-19, 150, 1.0}, 722 | {-1e-18, 150, 1.0}, 723 | {-1e-17, 150, 1.0}, 724 | {-1e-16, 150, 0.9999999999999994}, 725 | {-1e-15, 150, 0.999999999999995}, 726 | {-1e-14, 150, 0.9999999999999499}, 727 | {-1e-13, 150, 0.999999999999499}, 728 | {-1e-12, 150, 0.9999999999949893}, 729 | {-1e-11, 150, 0.9999999999498936}, 730 | {-1e-10, 150, 0.9999999994989365}, 731 | {-1e-09, 150, 0.9999999949893649}, 732 | {-1e-08, 150, 0.999_999_94}, 733 | {-1e-07, 150, 0.999_999_5}, 734 | {-1e-06, 150, 0.999_995}, 735 | {-1e-05, 150, 0.999_949_9}, 736 | {-1e-04, 150, 0.999_499_1}, 737 | {-0.001, 150, 0.995_001_9}, 738 | {-0.01, 150, 0.951_128_24}, 739 | {-0.1, 150, 0.605_885_9}, 740 | {-1.0, 150, 0.006_666_667}, 741 | {-10.0, 150, 1.734_153e-22}, 742 | {-100.0, 150, 0e0}, 743 | {-1000.0, 150, 0.0}, 744 | {-10000.0, 150, 0.0}, 745 | {-100000.0, 150, 0.0}, 746 | {-1000000.0, 150, 0.0}, 747 | {-10000000.0, 150, 0.0}, 748 | {-100000000.0, 150, 0.0}, 749 | {-1000000000.0, 150, 0.0}, 750 | {-10000000000.0, 150, 0.0}, 751 | {-100000000000.0, 150, 0.0}, 752 | {-1000000000000.0, 150, 0.0}, 753 | {-10000000000000.0, 150, 0.0}, 754 | {-100000000000000.0, 150, 0.0}, 755 | {-1000000000000000.0, 150, 0.0}, 756 | {-1e+16, 150, 0.0}, 757 | {-1e+17, 150, 0.0}, 758 | {-1e+18, 150, 0.0}, 759 | {-1e+19, 150, 0.0}, 760 | {1e-20, 150, 1.0}, 761 | {1e-19, 150, 1.0}, 762 | {1e-18, 150, 1.0}, 763 | {1e-17, 150, 1.0}, 764 | {1e-16, 150, 1.0000000000000004}, 765 | {1e-15, 150, 1.000000000000005}, 766 | {1e-14, 150, 1.0000000000000502}, 767 | {1e-13, 150, 1.0000000000005012}, 768 | {1e-12, 150, 1.0000000000050107}, 769 | {1e-11, 150, 1.0000000000501064}, 770 | {1e-10, 150, 1.0000000005010636}, 771 | {1e-09, 150, 1.0000000050106352}, 772 | {1e-08, 150, 1.000000050106354}, 773 | {1e-07, 150, 1.000_000_5}, 774 | {1e-06, 150, 1.000_005}, 775 | {1e-05, 150, 1.000_050_1}, 776 | {1e-04, 150, 1.000_501_2}, 777 | {0.001, 150, 1.005_023_2}, 778 | {0.01, 150, 1.051_382_9}, 779 | {0.1, 150, 1.650_475_6}, 780 | {1.0, 150, 150.0}, 781 | {10.0, 150, 5.766_504e21}, 782 | 783 | {2.0, -0.5881598, 0.345_931_95}, 784 | {3.2, -0.5881598, tinymath.NaN}, 785 | {3.0, -0.5881598, -0.203_463_27}, 786 | {4.0, -1000000.0, 1e+24}, 787 | } 788 | t.Run("table", func(t *testing.T) { 789 | t.Parallel() 790 | for _, c := range cases { 791 | c := c 792 | t.Run(fmt.Sprintf("%f_%f", c.Left, c.Right), func(t *testing.T) { 793 | act := tinymath.PowF(c.Right, c.Left) 794 | delta := tinymath.Abs(act - c.Expected) 795 | if delta/c.Expected > 0.01 { 796 | t.Fatalf("%f != %f", act, c.Expected) 797 | } 798 | }) 799 | } 800 | }) 801 | 802 | t.Run("stdlib", func(t *testing.T) { 803 | t.Parallel() 804 | for i := float32(1.); i < 100.; i += .34 { 805 | i := i 806 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 807 | close(t, tinymath.PowF(i, 3.5), float32(math.Pow(float64(i), 3.5)), i*i*i/15) 808 | }) 809 | } 810 | }) 811 | } 812 | 813 | func TestPowI(t *testing.T) { 814 | t.Parallel() 815 | for i := int32(1); i < 10; i++ { 816 | for f := float32(-3.); f < 7.; f += .5 { 817 | f := f 818 | i := i 819 | if f == 6.5 { 820 | continue 821 | } 822 | t.Run(fmt.Sprintf("%f", f), func(t *testing.T) { 823 | close(t, tinymath.PowI(f, i), float32(math.Pow(float64(f), float64(i))), 0.001) 824 | }) 825 | } 826 | } 827 | } 828 | 829 | func TestRecip(t *testing.T) { 830 | t.Parallel() 831 | cases := []Case{ 832 | {0.00001, 100000.0}, 833 | {1.0, 1.0}, 834 | {2.0, 0.5}, 835 | {0.25, 4.0}, 836 | {-0.5, -2.0}, 837 | {tinymath.Pi, 1.0 / tinymath.Pi}, 838 | } 839 | for _, c := range cases { 840 | c := c 841 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 842 | act := tinymath.Recip(c.Given) 843 | delta := tinymath.Abs(act - c.Expected) 844 | if delta/c.Expected > 1e-5 { 845 | t.Fatalf("%f != %f", act, c.Expected) 846 | } 847 | }) 848 | } 849 | } 850 | 851 | func TestRemEuclid(t *testing.T) { 852 | t.Parallel() 853 | cases := []Case2{ 854 | {7, 4, 3}, 855 | {-7, 4, 1}, 856 | {7, -4, 3}, 857 | {-7, -4, 1}, 858 | } 859 | for _, c := range cases { 860 | c := c 861 | t.Run(fmt.Sprintf("%f_%f", c.Left, c.Right), func(t *testing.T) { 862 | act := tinymath.RemEuclid(c.Left, c.Right) 863 | eq(t, act, c.Expected) 864 | }) 865 | } 866 | } 867 | 868 | func TestRound(t *testing.T) { 869 | t.Parallel() 870 | cases := []Case{ 871 | {0.0, 0.0}, 872 | {0.49999, 0.0}, 873 | {-0.49999, 0.0}, 874 | {0.5, 1.0}, 875 | {-0.5, -1.0}, 876 | {9999.499, 9999.0}, 877 | {-9999.499, -9999.0}, 878 | {9999.5, 10000.0}, 879 | {-9999.5, -10000.0}, 880 | } 881 | for _, c := range cases { 882 | c := c 883 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 884 | act := tinymath.Round(c.Given) 885 | eq(t, act, c.Expected) 886 | }) 887 | } 888 | 889 | for i := float32(-20.); i < 20.; i += .34 { 890 | i := i 891 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 892 | eq(t, tinymath.Round(i), float32(math.Round(float64(i)))) 893 | }) 894 | } 895 | } 896 | 897 | func TestSign(t *testing.T) { 898 | t.Parallel() 899 | cases := []Case{ 900 | {tinymath.Inf, 1.0}, 901 | {0.0, 1.0}, 902 | {1.0, 1.0}, 903 | {tinymath.NegInf, -1.0}, 904 | {-1.0, -1.0}, 905 | } 906 | for _, c := range cases { 907 | c := c 908 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 909 | act := tinymath.Sign(c.Given) 910 | eq(t, act, c.Expected) 911 | }) 912 | } 913 | } 914 | 915 | func TestTrunc(t *testing.T) { 916 | t.Parallel() 917 | cases := []Case{ 918 | {-1.1, -1.0}, 919 | {-0.1, 0.0}, 920 | {0.0, 0.0}, 921 | {1.0, 1.0}, 922 | {1.1, 1.0}, 923 | {2.9, 2.0}, 924 | {-100_000_000.13425345345, -100_000_000.0}, 925 | {100_000_000.13425345345, 100_000_000.0}, 926 | } 927 | for _, c := range cases { 928 | c := c 929 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 930 | act := tinymath.Trunc(c.Given) 931 | eq(t, act, c.Expected) 932 | }) 933 | } 934 | 935 | for i := float32(-20.); i < 20.; i += .34 { 936 | i := i 937 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 938 | eq(t, tinymath.Trunc(i), float32(math.Trunc(float64(i)))) 939 | }) 940 | } 941 | } 942 | -------------------------------------------------------------------------------- /trigonometry.go: -------------------------------------------------------------------------------- 1 | package tinymath 2 | 3 | // Computes `acos(x)` approximation in radians in the range `[0, pi]`. 4 | func Acos(self float32) float32 { 5 | if self > 0.0 { 6 | return Atan(Sqrt(1-self*self) / self) 7 | } else if self == 0.0 { 8 | return Pi / 2. 9 | } else { 10 | return Atan(Sqrt(1-self*self)/self) + Pi 11 | } 12 | } 13 | 14 | // Computes `asin(x)` approximation in radians in the range `[-pi/2, pi/2]`. 15 | func Asin(self float32) float32 { 16 | return Atan(self * InvSqrt(1-self*self)) 17 | } 18 | 19 | // Approximates `atan(x)` approximation in radians with a maximum error of 20 | // `0.002`. 21 | // 22 | // Returns [`NAN`] if the number is [`NAN`]. 23 | func Atan(self float32) float32 { 24 | return FracPi2 * AtanNorm(self) 25 | } 26 | 27 | // Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum 28 | // error of `0.1620` degrees. 29 | func AtanNorm(self float32) float32 { 30 | const B = 0.596_227 31 | 32 | // Extract the sign bit 33 | ux_s := signMask & ToBits(self) 34 | 35 | // Calculate the arctangent in the first quadrant 36 | bx_a := Abs(B * self) 37 | n := bx_a + self*self 38 | atan_1q := n / (1.0 + bx_a + n) 39 | 40 | // Restore the sign bit and convert to float 41 | return FromBits(ux_s | ToBits(atan_1q)) 42 | } 43 | 44 | // Approximates the four quadrant arctangent of `self` (`y`) and 45 | // `rhs` (`x`) in radians with a maximum error of `0.002`. 46 | // 47 | // - `x = 0`, `y = 0`: `0` 48 | // - `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]` 49 | // - `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]` 50 | // - `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)` 51 | func Atan2(self float32, rhs float32) float32 { 52 | n := Atan2Norm(self, rhs) 53 | if n > 2.0 { 54 | return Pi/2.0*n - 4.0 55 | } else { 56 | return Pi / 2.0 * n 57 | } 58 | } 59 | 60 | // Approximates `atan2(y,x)` normalized to the `[0, 4)` range with a maximum 61 | // error of `0.1620` degrees. 62 | func Atan2Norm(y float32, x float32) float32 { 63 | const B = 0.596_227 64 | 65 | // Extract sign bits from floating point values 66 | ux_s := signMask & ToBits(x) 67 | uy_s := signMask & ToBits(y) 68 | 69 | // Determine quadrant offset 70 | q := float32((^ux_s&uy_s)>>29 | ux_s>>30) 71 | 72 | // Calculate arctangent in the first quadrant 73 | bxy_a := Abs(B * x * y) 74 | n := bxy_a + y*y 75 | atan_1q := n / (x*x + bxy_a + n) 76 | 77 | // Translate it to the proper quadrant 78 | uatan_2q := (ux_s ^ uy_s) | ToBits(atan_1q) 79 | return q + FromBits(uatan_2q) 80 | } 81 | 82 | // Approximates `cos(x)` in radians with a maximum error of `0.002`. 83 | func Cos(self float32) float32 { 84 | x := self 85 | x *= Frac1Pi / 2.0 86 | x -= 0.25 + Floor(x+0.25) 87 | x *= 16.0 * (Abs(x) - 0.5) 88 | x += 0.225 * x * (Abs(x) - 1.0) 89 | return x 90 | } 91 | 92 | // Approximates `sin(x)` in radians with a maximum error of `0.002`. 93 | func Sin(self float32) float32 { 94 | return Cos(self - Pi/2.0) 95 | } 96 | 97 | // Simultaneously computes the sine and cosine of the number, `x`. 98 | // Returns `(sin(x), cos(x))`. 99 | func SinCos(self float32) (float32, float32) { 100 | sin := Cos(self - Pi/2.0) 101 | cos := Cos(self) 102 | return sin, cos 103 | } 104 | 105 | // Approximates `tan(x)` in radians with a maximum error of `0.6`. 106 | func Tan(self float32) float32 { 107 | return Sin(self) / Cos(self) 108 | } 109 | -------------------------------------------------------------------------------- /trigonometry_test.go: -------------------------------------------------------------------------------- 1 | package tinymath_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/orsinium-labs/tinymath" 9 | ) 10 | 11 | func TestAcos(t *testing.T) { 12 | cases := []Case{ 13 | {2.000, tinymath.NaN}, 14 | {1.000, 0.0}, 15 | {0.866, tinymath.FracPi6}, 16 | {0.707, tinymath.FracPi4}, 17 | {0.500, tinymath.FracPi3}, 18 | {tinymath.Epsilon, tinymath.FracPi2}, 19 | {0.000, tinymath.FracPi2}, 20 | {-tinymath.Epsilon, tinymath.FracPi2}, 21 | {-0.500, 2.0 * tinymath.FracPi3}, 22 | {-0.707, 3.0 * tinymath.FracPi4}, 23 | {-0.866, 5.0 * tinymath.FracPi6}, 24 | {-1.000, tinymath.Pi}, 25 | {-2.000, tinymath.NaN}, 26 | } 27 | for _, c := range cases { 28 | c := c 29 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 30 | close(t, tinymath.Acos(c.Given), c.Expected, 0.03) 31 | }) 32 | } 33 | } 34 | 35 | func TestAsin(t *testing.T) { 36 | act := tinymath.Asin(tinymath.Sin(tinymath.FracPi2)) 37 | close(t, act, tinymath.FracPi2, tinymath.Epsilon) 38 | } 39 | 40 | func TestAtan(t *testing.T) { 41 | cases := []Case{ 42 | // {tinymath.Sqrt(3.0) / 3.0, tinymath.FRAC_PI_6}, 43 | {1.0, tinymath.FracPi4}, 44 | {tinymath.Sqrt(3.0), tinymath.FracPi3}, 45 | // {-tinymath.Sqrt(3.0) / 3.0, -tinymath.FRAC_PI_6}, 46 | {-1.0, -tinymath.FracPi4}, 47 | {-tinymath.Sqrt(3.0), -tinymath.FracPi3}, 48 | } 49 | for _, c := range cases { 50 | c := c 51 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 52 | close(t, tinymath.Atan(c.Given), c.Expected, 0.003) 53 | }) 54 | } 55 | } 56 | 57 | func TestAtan2(t *testing.T) { 58 | cases := []Case2{ 59 | {0.0, 1.0, 0.0}, 60 | {0.0, -1.0, tinymath.Pi}, 61 | {3.0, 2.0, tinymath.Atan(3.0 / 2.0)}, 62 | {2.0, -1.0, tinymath.Atan(2.0/-1.0) + tinymath.Pi}, 63 | // {-2.0, -1.0, tinymath.Atan(-2.0/-1.0) - tinymath.PI}, 64 | } 65 | for _, c := range cases { 66 | c := c 67 | t.Run(fmt.Sprintf("y%f_x%f", c.Left, c.Right), func(t *testing.T) { 68 | close(t, tinymath.Atan2(c.Left, c.Right), c.Expected, 0.003) 69 | }) 70 | } 71 | } 72 | 73 | func TestCos(t *testing.T) { 74 | cases := []Case{ 75 | {0.000, 1.000}, 76 | {0.140, 0.990}, 77 | {0.279, 0.961}, 78 | {0.419, 0.914}, 79 | {0.559, 0.848}, 80 | {0.698, 0.766}, 81 | {0.838, 0.669}, 82 | {0.977, 0.559}, 83 | {1.117, 0.438}, 84 | {1.257, 0.309}, 85 | {1.396, 0.174}, 86 | {1.536, 0.035}, 87 | {1.676, -0.105}, 88 | {1.815, -0.242}, 89 | {1.955, -0.375}, 90 | {2.094, -0.500}, 91 | {2.234, -0.616}, 92 | {2.374, -0.719}, 93 | {2.513, -0.809}, 94 | {2.653, -0.883}, 95 | {2.793, -0.940}, 96 | {2.932, -0.978}, 97 | {3.072, -0.998}, 98 | {3.211, -0.998}, 99 | {3.351, -0.978}, 100 | {3.491, -0.940}, 101 | {3.630, -0.883}, 102 | {3.770, -0.809}, 103 | {3.910, -0.719}, 104 | {4.049, -0.616}, 105 | {4.189, -0.500}, 106 | {4.328, -0.375}, 107 | {4.468, -0.242}, 108 | {4.608, -0.105}, 109 | {4.747, 0.035}, 110 | {4.887, 0.174}, 111 | {5.027, 0.309}, 112 | {5.166, 0.438}, 113 | {5.306, 0.559}, 114 | {5.445, 0.669}, 115 | {5.585, 0.766}, 116 | {5.725, 0.848}, 117 | {5.864, 0.914}, 118 | {6.004, 0.961}, 119 | {6.144, 0.990}, 120 | {6.283, 1.000}, 121 | } 122 | for _, c := range cases { 123 | c := c 124 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 125 | close(t, tinymath.Cos(c.Given), c.Expected, 0.002) 126 | }) 127 | } 128 | 129 | for i := float32(1.); i < 100.; i++ { 130 | i := i 131 | t.Run(fmt.Sprintf("%f", i), func(t *testing.T) { 132 | close(t, tinymath.Sin(i), float32(math.Sin(float64(i))), 0.002) 133 | }) 134 | } 135 | } 136 | 137 | func TestSin(t *testing.T) { 138 | cases := []Case{ 139 | {0.000, 0.000}, 140 | {0.140, 0.139}, 141 | {0.279, 0.276}, 142 | {0.419, 0.407}, 143 | {0.559, 0.530}, 144 | {0.698, 0.643}, 145 | {0.838, 0.743}, 146 | {0.977, 0.829}, 147 | {1.117, 0.899}, 148 | {1.257, 0.951}, 149 | {1.396, 0.985}, 150 | {1.536, 0.999}, 151 | {1.676, 0.995}, 152 | {1.815, 0.970}, 153 | {1.955, 0.927}, 154 | {2.094, 0.866}, 155 | {2.234, 0.788}, 156 | {2.374, 0.695}, 157 | {2.513, 0.588}, 158 | {2.653, 0.469}, 159 | {2.793, 0.342}, 160 | {2.932, 0.208}, 161 | {3.072, 0.070}, 162 | {3.211, -0.070}, 163 | {3.351, -0.208}, 164 | {3.491, -0.342}, 165 | {3.630, -0.469}, 166 | {3.770, -0.588}, 167 | {3.910, -0.695}, 168 | {4.049, -0.788}, 169 | {4.189, -0.866}, 170 | {4.328, -0.927}, 171 | {4.468, -0.970}, 172 | {4.608, -0.995}, 173 | {4.747, -0.999}, 174 | {4.887, -0.985}, 175 | {5.027, -0.951}, 176 | {5.166, -0.899}, 177 | {5.306, -0.829}, 178 | {5.445, -0.743}, 179 | {5.585, -0.643}, 180 | {5.725, -0.530}, 181 | {5.864, -0.407}, 182 | {6.004, -0.276}, 183 | {6.144, -0.139}, 184 | {6.283, 0.000}, 185 | } 186 | for _, c := range cases { 187 | c := c 188 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 189 | close(t, tinymath.Sin(c.Given), c.Expected, 0.002) 190 | }) 191 | } 192 | } 193 | 194 | func TestTan(t *testing.T) { 195 | cases := []Case{ 196 | {0.000, 0.000}, 197 | {0.140, 0.141}, 198 | {0.279, 0.287}, 199 | {0.419, 0.445}, 200 | {0.559, 0.625}, 201 | {0.698, 0.839}, 202 | {0.838, 1.111}, 203 | {0.977, 1.483}, 204 | {1.117, 2.050}, 205 | // {1.257, 3.078}, 206 | // {1.396, 5.671}, 207 | // {1.536, 28.636}, 208 | // {1.676, -9.514}, 209 | // {1.815, -4.011}, 210 | {1.955, -2.475}, 211 | {2.094, -1.732}, 212 | {2.234, -1.280}, 213 | {2.374, -0.966}, 214 | {2.513, -0.727}, 215 | {2.653, -0.532}, 216 | {2.793, -0.364}, 217 | {2.932, -0.213}, 218 | {3.072, -0.070}, 219 | {3.211, 0.070}, 220 | {3.351, 0.213}, 221 | {3.491, 0.364}, 222 | {3.630, 0.532}, 223 | {3.770, 0.727}, 224 | {3.910, 0.966}, 225 | {4.049, 1.280}, 226 | {4.189, 1.732}, 227 | {4.328, 2.475}, 228 | // {4.468, 4.011}, 229 | // {4.608, 9.514}, 230 | // {4.747, -28.636}, 231 | // {4.887, -5.671}, 232 | {5.027, -3.078}, 233 | {5.166, -2.050}, 234 | {5.306, -1.483}, 235 | {5.445, -1.111}, 236 | {5.585, -0.839}, 237 | {5.725, -0.625}, 238 | {5.864, -0.445}, 239 | {6.004, -0.287}, 240 | {6.144, -0.141}, 241 | {6.283, 0.000}, 242 | } 243 | for _, c := range cases { 244 | c := c 245 | t.Run(fmt.Sprintf("%f", c.Given), func(t *testing.T) { 246 | close(t, tinymath.Tan(c.Given), c.Expected, 0.006) 247 | }) 248 | } 249 | } 250 | --------------------------------------------------------------------------------