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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
//! Lookup tables for fast trigonometric operations
//!
//! Uses 256-entry tables for sin/cos (1KB total)
//! ~50-100x faster than f32::sin()/cos() on embedded systems without FPU
use core::f32::consts::PI;
#[cfg(not(feature = "std"))]
use micromath::F32Ext;
/// 256-entry sine lookup table covering 0 to 2π
/// Each entry is scaled by 32768 for fixed-point arithmetic
const SIN_TABLE: [i16; 256] = generate_sin_table();
/// Generate sine lookup table at compile time
const fn generate_sin_table() -> [i16; 256] {
let mut table = [0i16; 256];
let mut i = 0;
while i < 256 {
// We need to use approximation since sin() isn't const
// This is a compile-time approximation using higher-order Taylor series
let angle_rad = (i as f32 * 2.0 * PI) / 256.0;
// Taylor series approximation for sin(x) with more terms for better accuracy
// sin(x) = x - x³/3! + x⁵/5! - x⁷/7! + x⁹/9! - x¹¹/11!
let x = angle_rad;
let x2 = x * x;
let x3 = x2 * x;
let x5 = x3 * x2;
let x7 = x5 * x2;
let x9 = x7 * x2;
let x11 = x9 * x2;
let sin_val = x
- x3 / 6.0 // x³/3!
+ x5 / 120.0 // x⁵/5!
- x7 / 5040.0 // x⁷/7!
+ x9 / 362880.0 // x⁹/9!
- x11 / 39916800.0; // x¹¹/11!
table[i] = (sin_val * 32768.0) as i16;
i += 1;
}
table
}
/// Fast sine using lookup table
///
/// Input: angle in radians (any range)
/// Output: sine value scaled by 32768 (-32768 to 32767)
///
/// To convert to float: result as f32 / 32768.0
#[inline(always)]
pub fn fast_sin_i16(angle: f32) -> i16 {
// Normalize angle to 0..2π and map to 0..255
let normalized = (angle % (2.0 * PI)) / (2.0 * PI);
let index = ((normalized * 256.0) as usize) & 0xFF;
SIN_TABLE[index]
}
/// Fast cosine using lookup table (cos(x) = sin(x + π/2))
#[inline(always)]
pub fn fast_cos_i16(angle: f32) -> i16 {
fast_sin_i16(angle + PI / 2.0)
}
/// Fast sine returning f32 — uses micromath polynomial (~0.001 error vs 2% for the i16 table).
#[inline(always)]
pub fn fast_sin(angle: f32) -> f32 {
angle.sin()
}
/// Fast cosine returning f32 — uses micromath polynomial (~0.001 error vs 2% for the i16 table).
#[inline(always)]
pub fn fast_cos(angle: f32) -> f32 {
angle.cos()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sin_accuracy() {
extern crate std;
// Test accuracy at various angles
let test_angles = [
0.0,
PI / 6.0,
PI / 4.0,
PI / 3.0,
PI / 2.0,
PI,
3.0 * PI / 2.0,
];
for angle in test_angles.iter() {
let fast = fast_sin(*angle);
let accurate = angle.sin();
let error = (fast - accurate).abs();
std::println!(
"sin({:.3}): fast={:.4}, accurate={:.4}, error={:.4}",
angle,
fast,
accurate,
error
);
// For a 256-entry lookup table, error < 0.02 (2%) is quite good
// This is a reasonable trade-off for embedded systems
assert!(
error < 0.02,
"Error too large at angle {}: {}",
angle,
error
);
}
}
#[test]
fn test_cos_accuracy() {
extern crate std;
let test_angles = [0.0, PI / 6.0, PI / 4.0, PI / 3.0, PI / 2.0, PI];
for angle in test_angles.iter() {
let fast = fast_cos(*angle);
let accurate = angle.cos();
let error = (fast - accurate).abs();
std::println!(
"cos({:.3}): fast={:.4}, accurate={:.4}, error={:.4}",
angle,
fast,
accurate,
error
);
// For a 256-entry lookup table, error < 0.02 (2%) is acceptable
assert!(
error < 0.02,
"Error too large at angle {}: {}",
angle,
error
);
}
}
#[test]
fn test_periodicity() {
extern crate std;
// Test that sin(x) ≈ sin(x + 2π)
// Note: Due to floating-point precision in modulo and table indexing,
// we may hit adjacent table entries, so we use a relaxed threshold
let angle = PI / 4.0;
let sin1 = fast_sin(angle);
let sin2 = fast_sin(angle + 2.0 * PI);
let sin3 = fast_sin(angle + 4.0 * PI);
std::println!(
"Periodicity test: sin1={:.4}, sin2={:.4}, sin3={:.4}",
sin1,
sin2,
sin3
);
std::println!(
"Differences: |sin1-sin2|={:.4}, |sin1-sin3|={:.4}",
(sin1 - sin2).abs(),
(sin1 - sin3).abs()
);
// Allow up to 0.02 difference due to table quantization
assert!(
(sin1 - sin2).abs() < 0.02,
"Periodicity error too large: {}",
(sin1 - sin2).abs()
);
assert!(
(sin1 - sin3).abs() < 0.02,
"Periodicity error too large: {}",
(sin1 - sin3).abs()
);
}
#[test]
fn test_pythagorean_identity() {
let angles = [0.0, 0.5, 1.0, PI / 4.0, PI / 3.0, 2.0, 3.14];
for &a in &angles {
let s = fast_sin(a);
let c = fast_cos(a);
let pyth = s * s + c * c;
assert!(
(pyth - 1.0).abs() < 0.01,
"Pythagorean identity failed at angle {}: sin^2 + cos^2 = {}",
a,
pyth
);
}
}
#[test]
fn test_trig_symmetry() {
let angles = [0.1, 0.785, 1.5, 2.3];
for &a in &angles {
let sin_pos = fast_sin(a);
let sin_neg = fast_sin(-a);
assert!(
(sin_pos + sin_neg).abs() < 0.01,
"Sin odd symmetry failed at {}: sin(a)={}, sin(-a)={}",
a,
sin_pos,
sin_neg
);
let cos_pos = fast_cos(a);
let cos_neg = fast_cos(-a);
assert!(
(cos_pos - cos_neg).abs() < 0.01,
"Cos even symmetry failed at {}: cos(a)={}, cos(-a)={}",
a,
cos_pos,
cos_neg
);
}
}
}