238 lines
9.5 KiB
Zig
238 lines
9.5 KiB
Zig
const std = @import("std");
|
|
const vm = @import("root.zig");
|
|
|
|
/// The number of radians per one turn (τ).
|
|
pub const rad_per_turn = 1.0 * std.math.tau;
|
|
|
|
/// The number of degrees per one turn (360).
|
|
pub const deg_per_turn = 360.0;
|
|
|
|
/// The number of turns per one radian (1 / τ).
|
|
pub const turns_per_rad = 1.0 / std.math.tau;
|
|
|
|
/// The number of turns per one degree (1 / 360).
|
|
pub const turns_per_deg = 1.0 / 360.0;
|
|
|
|
/// Converts an angle in turns to radians. `@TypeOf(ang)` must be a float or
|
|
/// comptime number or a vector of floats.
|
|
pub fn turnsToRadians(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
|
|
const T = @TypeOf(ang);
|
|
switch (@typeInfo(T)) {
|
|
.float, .comptime_float, .comptime_int => return ang * rad_per_turn,
|
|
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(rad_per_turn)),
|
|
else => {},
|
|
}
|
|
@compileError("Input must be float or a comptime number, or a vector of floats.");
|
|
}
|
|
|
|
test turnsToRadians {
|
|
try std.testing.expectEqual(0, turnsToRadians(@as(f32, 0)));
|
|
try std.testing.expectApproxEqAbs(0.25 * std.math.tau, turnsToRadians(@as(f32, 0.25)), 0x1p-5);
|
|
try std.testing.expectApproxEqAbs(0.5 * std.math.tau, turnsToRadians(@as(f32, 0.5)), 0x1p-5);
|
|
try std.testing.expectApproxEqAbs(0.75 * std.math.tau, turnsToRadians(@as(f32, 0.75)), 0x1p-5);
|
|
try std.testing.expectApproxEqAbs(std.math.tau, turnsToRadians(@as(f32, 1)), 0x1p-5);
|
|
}
|
|
|
|
/// Converts an angle in turns to degrees. `@TypeOf(ang)` must be a float or
|
|
/// comptime number or a vector of floats.
|
|
pub fn turnsToDegrees(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
|
|
const T = @TypeOf(ang);
|
|
switch (@typeInfo(T)) {
|
|
.float, .comptime_float, .comptime_int => return ang * deg_per_turn,
|
|
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(deg_per_turn)),
|
|
else => {},
|
|
}
|
|
@compileError("Input must be float or a comptime number, or a vector of floats.");
|
|
}
|
|
|
|
test turnsToDegrees {
|
|
try std.testing.expectEqual(0, turnsToDegrees(@as(f32, 0)));
|
|
try std.testing.expectEqual(90, turnsToDegrees(@as(f32, 0.25)));
|
|
try std.testing.expectEqual(180, turnsToDegrees(@as(f32, 0.5)));
|
|
try std.testing.expectEqual(270, turnsToDegrees(@as(f32, 0.75)));
|
|
try std.testing.expectEqual(360, turnsToDegrees(@as(f32, 1)));
|
|
}
|
|
|
|
/// Converts an angle in radians to turns. `@TypeOf(ang)` must be a float or
|
|
/// comptime number or a vector of floats.
|
|
pub fn radiansToTurns(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
|
|
const T = @TypeOf(ang);
|
|
switch (@typeInfo(T)) {
|
|
.float, .comptime_float, .comptime_int => return ang * turns_per_rad,
|
|
.vector => |V| if (@typeInfo(V.child) == .float) return ang * @as(T, @splat(turns_per_rad)),
|
|
else => {},
|
|
}
|
|
@compileError("Input must be float or a comptime number, or a vector of floats.");
|
|
}
|
|
|
|
test radiansToTurns {
|
|
try std.testing.expectEqual(0, radiansToTurns(@as(f32, 0)));
|
|
try std.testing.expectApproxEqAbs(0.25, radiansToTurns(@as(f32, 0.25 * std.math.tau)), 0x1p-5);
|
|
try std.testing.expectApproxEqAbs(0.5, radiansToTurns(@as(f32, 0.5 * std.math.tau)), 0x1p-5);
|
|
try std.testing.expectApproxEqAbs(0.75, radiansToTurns(@as(f32, 0.75 * std.math.tau)), 0x1p-5);
|
|
try std.testing.expectApproxEqAbs(1, radiansToTurns(@as(f32, std.math.tau)), 0x1p-5);
|
|
}
|
|
|
|
/// Converts an angle in degrees to turns. `@TypeOf(ang)` must be a float or
|
|
/// comptime number or a vector of floats.
|
|
pub fn degreesToTurns(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_float else @TypeOf(ang) {
|
|
const T = @TypeOf(ang);
|
|
switch (@typeInfo(T)) {
|
|
.float, .comptime_float, .comptime_int => return ang / deg_per_turn,
|
|
.vector => |V| if (@typeInfo(V.child) == .float) return ang / @as(T, @splat(deg_per_turn)),
|
|
else => {},
|
|
}
|
|
@compileError("Input must be float or a comptime number, or a vector of floats.");
|
|
}
|
|
|
|
test degreesToTurns {
|
|
try std.testing.expectEqual(0, degreesToTurns(@as(f32, 0)));
|
|
try std.testing.expectEqual(0.25, degreesToTurns(@as(f32, 90)));
|
|
try std.testing.expectEqual(0.5, degreesToTurns(@as(f32, 180)));
|
|
try std.testing.expectEqual(0.75, degreesToTurns(@as(f32, 270)));
|
|
try std.testing.expectEqual(1, degreesToTurns(@as(f32, 360)));
|
|
}
|
|
|
|
pub fn cos(angle_turns: f32) f32 {
|
|
return cossin(angle_turns).re;
|
|
}
|
|
|
|
test cos {
|
|
try std.testing.expectEqual(1, cos(-1));
|
|
try std.testing.expectEqual(0, cos(-0.75));
|
|
try std.testing.expectEqual(-1, cos(-0.5));
|
|
try std.testing.expectEqual(0, cos(-0.25));
|
|
try std.testing.expectEqual(1, cos(0));
|
|
try std.testing.expectEqual(0, cos(0.25));
|
|
try std.testing.expectEqual(-1, cos(0.5));
|
|
try std.testing.expectEqual(0, cos(0.75));
|
|
}
|
|
|
|
pub fn cos_x8(angle_turns: vm.f32x8) vm.f32x8 {
|
|
return cossin_x8(angle_turns).re;
|
|
}
|
|
|
|
test cos_x8 {
|
|
try std.testing.expectEqual(
|
|
.{ 1, 0, -1, 0, 1, 0, -1, 0 },
|
|
cos_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
|
|
);
|
|
}
|
|
|
|
pub fn sin(angle_turns: f32) f32 {
|
|
return cossin(angle_turns).im;
|
|
}
|
|
|
|
test sin {
|
|
try std.testing.expectEqual(0, sin(-1));
|
|
try std.testing.expectEqual(1, sin(-0.75));
|
|
try std.testing.expectEqual(0, sin(-0.5));
|
|
try std.testing.expectEqual(-1, sin(-0.25));
|
|
try std.testing.expectEqual(0, sin(0));
|
|
try std.testing.expectEqual(1, sin(0.25));
|
|
try std.testing.expectEqual(0, sin(0.5));
|
|
try std.testing.expectEqual(-1, sin(0.75));
|
|
}
|
|
|
|
pub fn sin_x8(angle_turns: vm.f32x8) vm.f32x8 {
|
|
return cossin_x8(angle_turns).im;
|
|
}
|
|
|
|
test sin_x8 {
|
|
try std.testing.expectEqual(
|
|
.{ 0, 1, 0, -1, 0, 1, 0, -1 },
|
|
sin_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
|
|
);
|
|
}
|
|
|
|
pub fn cossin(angle_turns: f32) vm.Complex {
|
|
@setFloatMode(.optimized);
|
|
// Taylor series expansion for f(x)=cos(xπ/2)
|
|
const term_cos_0: f32 = 1.0;
|
|
const term_cos_2: f32 = -1.23370055; // -π²/8
|
|
const term_cos_4: f32 = 0.253669508; // π⁴/384
|
|
const term_cos_6: f32 = -0.020863481; // -π⁶/46080
|
|
// Taylor series expansion for f(x)=sin(xπ/2)
|
|
const term_sin_1: f32 = 1.570796327; // π/2
|
|
const term_sin_3: f32 = -0.645964098; // -π³/48
|
|
const term_sin_5: f32 = 0.079692626; // π⁵/3840
|
|
|
|
const angle_01 = angle_turns - @floor(angle_turns);
|
|
const angle_04 = 4.0 * angle_01;
|
|
|
|
const quadrant: u32 = @intFromFloat(angle_04);
|
|
const quadrant_odd = (quadrant & 1) * 0xFFFFFFFF;
|
|
const sign_mask_cos = ((quadrant + 1) & 0b10) << 30;
|
|
const sign_mask_sin = (quadrant & 0b10) << 30;
|
|
|
|
const x = angle_04 - @floor(angle_04);
|
|
const x2 = x * x;
|
|
|
|
const c: u32 = @bitCast(((term_cos_6 * x2 + term_cos_4) * x2 + term_cos_2) * x2 + term_cos_0);
|
|
const s: u32 = @bitCast(((term_sin_5 * x2 + term_sin_3) * x2 + term_sin_1) * x);
|
|
|
|
const result_cos: f32 = @bitCast(((s & quadrant_odd) | (c & ~quadrant_odd)) ^ sign_mask_cos);
|
|
const result_sin: f32 = @bitCast(((c & quadrant_odd) | (s & ~quadrant_odd)) ^ sign_mask_sin);
|
|
|
|
return .init(result_cos, result_sin);
|
|
}
|
|
|
|
test cossin {
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_x), cossin(-1));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_y), cossin(-0.75));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_nx), cossin(-0.5));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_ny), cossin(-0.25));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_x), cossin(0));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_y), cossin(0.25));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_nx), cossin(0.5));
|
|
try std.testing.expectEqual(vm.Complex.initVector2(.unit_ny), cossin(0.75));
|
|
}
|
|
|
|
pub fn cossin_x8(angle_turns: vm.f32x8) vm.Complex_x8 {
|
|
@setFloatMode(.optimized);
|
|
// Taylor series expansion for f(x)=cos(xπ/2)
|
|
const term_cos_0 = vm.ps(1.0);
|
|
const term_cos_2 = vm.ps(-1.23370055); // -π²/8
|
|
const term_cos_4 = vm.ps(0.253669508); // π⁴/384
|
|
const term_cos_6 = vm.ps(-0.020863481); // -π⁶/46080
|
|
// Taylor series expansion for f(x)=sin(xπ/2)
|
|
const term_sin_1 = vm.ps(1.570796327); // π/2
|
|
const term_sin_3 = vm.ps(-0.645964098); // -π³/48
|
|
const term_sin_5 = vm.ps(0.079692626); // π⁵/3840
|
|
|
|
const angle_01 = angle_turns - @floor(angle_turns);
|
|
const angle_04 = vm.ps(4.0) * angle_01;
|
|
|
|
const quadrant: vm.u32x8 = @intFromFloat(angle_04);
|
|
const quadrant_odd = (quadrant & vm.epu32(1)) != vm.epu32(0);
|
|
const sign_mask_cos = ((quadrant + vm.epu32(1)) & vm.epu32(0b10)) << @splat(30);
|
|
const sign_mask_sin = (quadrant & vm.epu32(0b10)) << @splat(30);
|
|
|
|
const x = angle_04 - @floor(angle_04);
|
|
const x2 = x * x;
|
|
|
|
const c: vm.u32x8 = @bitCast(((term_cos_6 * x2 + term_cos_4) * x2 + term_cos_2) * x2 + term_cos_0);
|
|
const s: vm.u32x8 = @bitCast(((term_sin_5 * x2 + term_sin_3) * x2 + term_sin_1) * x);
|
|
|
|
const result_cos: vm.f32x8 = @bitCast(@select(u32, quadrant_odd, s, c) ^ sign_mask_cos);
|
|
const result_sin: vm.f32x8 = @bitCast(@select(u32, quadrant_odd, c, s) ^ sign_mask_sin);
|
|
|
|
return .init(result_cos, result_sin);
|
|
}
|
|
|
|
test cossin_x8 {
|
|
try std.testing.expectEqual(
|
|
vm.Complex_x8.initArrayOfComplex(.{
|
|
.initVector2(.unit_x),
|
|
.initVector2(.unit_y),
|
|
.initVector2(.unit_nx),
|
|
.initVector2(.unit_ny),
|
|
.initVector2(.unit_x),
|
|
.initVector2(.unit_y),
|
|
.initVector2(.unit_nx),
|
|
.initVector2(.unit_ny),
|
|
}),
|
|
cossin_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
|
|
);
|
|
}
|