lib/simd/src/sign.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub fn signBit(comptime D: type) D.Vector {
4 const U = UnsignedLane(D);
5 const mask: U = @as(U, 1) << (@bitSizeOf(D.Lane) - 1);
6 return @bitCast(@as(UnsignedVector(D), @splat(mask)));
7 }
8
9 pub fn copySign(comptime D: type, magnitude: D.Vector, sign: D.Vector) D.Vector {
10 validateFloat(D);
11 const mask: UnsignedVector(D) = @bitCast(signBit(D));
12 const magnitude_bits: UnsignedVector(D) = @bitCast(magnitude);
13 const sign_bits: UnsignedVector(D) = @bitCast(sign);
14 return @bitCast((magnitude_bits & ~mask) | (sign_bits & mask));
15 }
16
17 pub fn copySignToAbs(comptime D: type, absolute: D.Vector, sign: D.Vector) D.Vector {
18 validateFloat(D);
19 const mask: UnsignedVector(D) = @bitCast(signBit(D));
20 const absolute_bits: UnsignedVector(D) = @bitCast(absolute);
21 const sign_bits: UnsignedVector(D) = @bitCast(sign);
22 return @bitCast(absolute_bits | (sign_bits & mask));
23 }
24
25 pub fn broadcastSignBit(comptime D: type, value: D.Vector) D.Vector {
26 if (@typeInfo(D.Lane) != .int or
27 @typeInfo(D.Lane).int.signedness != .signed)
28 {
29 @compileError("broadcastSignBit requires signed integer lanes");
30 }
31 const Amount = @Vector(D.lane_count, std.math.Log2Int(UnsignedLane(D)));
32 const amount: Amount = @splat(@bitSizeOf(D.Lane) - 1);
33 return value >> amount;
34 }
35
36 fn UnsignedLane(comptime D: type) type {
37 return @Int(.unsigned, @bitSizeOf(D.Lane));
38 }
39
40 fn UnsignedVector(comptime D: type) type {
41 return @Vector(D.lane_count, UnsignedLane(D));
42 }
43
44 fn validateFloat(comptime D: type) void {
45 if (@typeInfo(D.Lane) != .float) @compileError("copySign requires floating-point lanes");
46 }
47
48 fn verifyBroadcast(comptime T: type) !void {
49 const simd = @import("root.zig");
50 const D = simd.FixedTag(T, 4);
51 const value: D.Vector = .{ 0, 1, -1, std.math.minInt(T) };
52 try std.testing.expect(@reduce(.And, broadcastSignBit(D, value) ==
53 @as(D.Vector, .{ 0, 0, -1, -1 })));
54 }
55
56 fn verifyCopySign(comptime T: type) !void {
57 const simd = @import("root.zig");
58 const D = simd.FixedTag(T, 4);
59 const U = @Int(.unsigned, @bitSizeOf(T));
60 const UV = @Vector(4, U);
61 const sign_mask: U = @as(U, 1) << (@bitSizeOf(T) - 1);
62 const one_bits: U = @bitCast(@as(T, 1));
63 const signs: D.Vector = @bitCast(@as(UV, .{ 0, sign_mask, sign_mask, 0 }));
64 const magnitude: D.Vector = @splat(1);
65 const expected: UV = .{ one_bits, one_bits | sign_mask, one_bits | sign_mask, one_bits };
66 try std.testing.expect(@reduce(.And, @as(UV, @bitCast(copySign(D, magnitude, signs))) == expected));
67 try std.testing.expect(@reduce(.And, @as(UV, @bitCast(copySignToAbs(D, magnitude, signs))) == expected));
68 }
69
70 test "Highway sign masks copy floating sign bits including negative zero" {
71 const simd = @import("root.zig");
72 const D = simd.FixedTag(f32, 4);
73 const U = simd.FixedTag(u32, 4);
74 const magnitude: D.Vector = @bitCast(@as(U.Vector, .{
75 0, 0x3f80_0000, 0xc000_0000, 0x7fc0_1234,
76 }));
77 const sign: D.Vector = @bitCast(@as(U.Vector, .{
78 0x8000_0000, 0, 0x8000_0000, 0,
79 }));
80 const expected: U.Vector = .{
81 0x8000_0000, 0x3f80_0000, 0xc000_0000, 0x7fc0_1234,
82 };
83 try std.testing.expect(@reduce(.And, @as(U.Vector, @bitCast(copySign(D, magnitude, sign))) == expected));
84 const absolute: D.Vector = @bitCast(@as(U.Vector, .{
85 0, 0x3f80_0000, 0x4000_0000, 0x7fc0_1234,
86 }));
87 try std.testing.expect(@reduce(.And, @as(U.Vector, @bitCast(copySignToAbs(D, absolute, sign))) == expected));
88 }
89
90 test "Highway broadcast sign covers all signed lane widths" {
91 inline for (.{ i8, i16, i32, i64 }) |T| try verifyBroadcast(T);
92 }
93
94 test "Highway copy sign covers every floating lane width" {
95 inline for (.{ f16, f32, f64 }) |T| try verifyCopySign(T);
96 }