lib/simd/src/arithmetic.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 pub fn add(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
  4     return if (comptime isInteger(D.Lane)) a +% b else a + b;
  5 }
  6 
  7 pub fn sub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
  8     return if (comptime isInteger(D.Lane)) a -% b else a - b;
  9 }
 10 
 11 pub fn mul(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 12     return if (comptime isInteger(D.Lane)) a *% b else a * b;
 13 }
 14 
 15 pub fn addSub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 16     var result: D.Vector = undefined;
 17     inline for (0..D.lane_count) |index| {
 18         result[index] = if (index & 1 == 0)
 19             subLane(D.Lane, a[index], b[index])
 20         else
 21             addLane(D.Lane, a[index], b[index]);
 22     }
 23     return result;
 24 }
 25 
 26 pub fn neg(comptime D: type, value: D.Vector) D.Vector {
 27     return switch (@typeInfo(D.Lane)) {
 28         .int => |info| if (info.signedness == .signed)
 29             @as(D.Vector, @splat(0)) -% value
 30         else
 31             @compileError("neg requires signed integer or floating-point lanes"),
 32         .float => -value,
 33         else => unreachable,
 34     };
 35 }
 36 
 37 pub fn saturatedNeg(comptime D: type, value: D.Vector) D.Vector {
 38     requireSignedInteger(D.Lane, "saturatedNeg");
 39     var result: D.Vector = undefined;
 40     inline for (0..D.lane_count) |index| {
 41         result[index] = if (value[index] == std.math.minInt(D.Lane))
 42             std.math.maxInt(D.Lane)
 43         else
 44             -value[index];
 45     }
 46     return result;
 47 }
 48 
 49 pub fn abs(comptime D: type, value: D.Vector) D.Vector {
 50     return switch (@typeInfo(D.Lane)) {
 51         .int => |info| if (info.signedness == .signed)
 52             absInteger(D, value)
 53         else
 54             @compileError("abs requires signed integer or floating-point lanes"),
 55         .float => @abs(value),
 56         else => unreachable,
 57     };
 58 }
 59 
 60 pub fn saturatedAbs(comptime D: type, value: D.Vector) D.Vector {
 61     requireSignedInteger(D.Lane, "saturatedAbs");
 62     var result: D.Vector = undefined;
 63     inline for (0..D.lane_count) |index| {
 64         result[index] = if (value[index] == std.math.minInt(D.Lane))
 65             std.math.maxInt(D.Lane)
 66         else if (value[index] < 0)
 67             -value[index]
 68         else
 69             value[index];
 70     }
 71     return result;
 72 }
 73 
 74 pub fn absDiff(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 75     if (comptime @typeInfo(D.Lane) == .float) return @abs(a - b);
 76     if (comptime @typeInfo(D.Lane) != .int) @compileError("absDiff requires numeric lanes");
 77     const U = @Int(.unsigned, @bitSizeOf(D.Lane));
 78     var result: D.Vector = undefined;
 79     inline for (0..D.lane_count) |index| {
 80         const a_bits: U = @bitCast(a[index]);
 81         const b_bits: U = @bitCast(b[index]);
 82         const difference = if (a[index] >= b[index]) a_bits -% b_bits else b_bits -% a_bits;
 83         result[index] = @bitCast(difference);
 84     }
 85     return result;
 86 }
 87 
 88 pub fn saturatedAdd(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 89     requireInteger(D.Lane, "saturatedAdd");
 90     var result: D.Vector = undefined;
 91     inline for (0..D.lane_count) |index| {
 92         result[index] = saturatedAddLane(D.Lane, a[index], b[index]);
 93     }
 94     return result;
 95 }
 96 
 97 pub fn saturatedSub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 98     requireInteger(D.Lane, "saturatedSub");
 99     var result: D.Vector = undefined;
100     inline for (0..D.lane_count) |index| {
101         result[index] = saturatedSubLane(D.Lane, a[index], b[index]);
102     }
103     return result;
104 }
105 
106 pub fn div(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
107     if (comptime @typeInfo(D.Lane) == .float) return a / b;
108     requireInteger(D.Lane, "div");
109     var result: D.Vector = undefined;
110     inline for (0..D.lane_count) |index| {
111         result[index] = safeDivLane(D.Lane, a[index], b[index]);
112     }
113     return result;
114 }
115 
116 pub fn mod(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
117     requireInteger(D.Lane, "mod");
118     var result: D.Vector = undefined;
119     inline for (0..D.lane_count) |index| {
120         result[index] = safeModLane(D.Lane, a[index], b[index]);
121     }
122     return result;
123 }
124 
125 pub fn min(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
126     return @select(D.Lane, a < b, a, b);
127 }
128 
129 pub fn max(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
130     return @select(D.Lane, a > b, a, b);
131 }
132 
133 pub fn minNumber(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
134     if (comptime @typeInfo(D.Lane) != .float) return min(D, a, b);
135     const a_nan = a != a;
136     const b_nan = b != b;
137     return @select(
138         D.Lane,
139         a_nan,
140         b,
141         @select(D.Lane, b_nan, a, min(D, a, b)),
142     );
143 }
144 
145 pub fn maxNumber(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
146     if (comptime @typeInfo(D.Lane) != .float) return max(D, a, b);
147     const a_nan = a != a;
148     const b_nan = b != b;
149     return @select(
150         D.Lane,
151         a_nan,
152         b,
153         @select(D.Lane, b_nan, a, max(D, a, b)),
154     );
155 }
156 
157 pub fn minMagnitude(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
158     return magnitudeChoice(D, a, b, false);
159 }
160 
161 pub fn maxMagnitude(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
162     return magnitudeChoice(D, a, b, true);
163 }
164 
165 pub fn min128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
166     return choose128(D, a, b, false, false);
167 }
168 
169 pub fn max128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
170     return choose128(D, a, b, true, false);
171 }
172 
173 pub fn min128Upper(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
174     return choose128(D, a, b, false, true);
175 }
176 
177 pub fn max128Upper(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
178     return choose128(D, a, b, true, true);
179 }
180 
181 pub fn clamp(
182     comptime D: type,
183     value: D.Vector,
184     lower: D.Vector,
185     upper: D.Vector,
186 ) D.Vector {
187     return max(D, lower, min(D, value, upper));
188 }
189 
190 pub fn mulAdd(
191     comptime D: type,
192     multiplicand: D.Vector,
193     multiplier: D.Vector,
194     addend: D.Vector,
195 ) D.Vector {
196     if (comptime isInteger(D.Lane)) {
197         return add(D, mul(D, multiplicand, multiplier), addend);
198     }
199     return @mulAdd(D.Vector, multiplicand, multiplier, addend);
200 }
201 
202 pub fn negMulAdd(
203     comptime D: type,
204     multiplicand: D.Vector,
205     multiplier: D.Vector,
206     addend: D.Vector,
207 ) D.Vector {
208     if (comptime isInteger(D.Lane)) {
209         return add(D, mul(D, neg(D, multiplicand), multiplier), addend);
210     }
211     return @mulAdd(D.Vector, -multiplicand, multiplier, addend);
212 }
213 
214 pub fn mulSub(
215     comptime D: type,
216     multiplicand: D.Vector,
217     multiplier: D.Vector,
218     subtrahend: D.Vector,
219 ) D.Vector {
220     if (comptime isInteger(D.Lane)) {
221         return sub(D, mul(D, multiplicand, multiplier), subtrahend);
222     }
223     return @mulAdd(D.Vector, multiplicand, multiplier, -subtrahend);
224 }
225 
226 pub fn negMulSub(
227     comptime D: type,
228     multiplicand: D.Vector,
229     multiplier: D.Vector,
230     subtrahend: D.Vector,
231 ) D.Vector {
232     if (comptime isInteger(D.Lane)) {
233         return sub(D, mul(D, neg(D, multiplicand), multiplier), subtrahend);
234     }
235     return @mulAdd(D.Vector, -multiplicand, multiplier, -subtrahend);
236 }
237 
238 pub fn mulAddSub(
239     comptime D: type,
240     multiplicand: D.Vector,
241     multiplier: D.Vector,
242     addend: D.Vector,
243 ) D.Vector {
244     return alternate(D, mulSub(D, multiplicand, multiplier, addend), mulAdd(D, multiplicand, multiplier, addend));
245 }
246 
247 pub fn mulSubAdd(
248     comptime D: type,
249     multiplicand: D.Vector,
250     multiplier: D.Vector,
251     addend: D.Vector,
252 ) D.Vector {
253     return alternate(D, mulAdd(D, multiplicand, multiplier, addend), mulSub(D, multiplicand, multiplier, addend));
254 }
255 
256 pub fn maskedMinOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
257     return maskedOr(D, no, mask, min(D, a, b));
258 }
259 
260 pub fn maskedMaxOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
261     return maskedOr(D, no, mask, max(D, a, b));
262 }
263 
264 pub fn maskedAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
265     return maskedOr(D, no, mask, add(D, a, b));
266 }
267 
268 pub fn maskedSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
269     return maskedOr(D, no, mask, sub(D, a, b));
270 }
271 
272 pub fn maskedMulOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
273     return maskedOr(D, no, mask, mul(D, a, b));
274 }
275 
276 pub fn maskedDivOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
277     return maskedOr(D, no, mask, div(D, a, b));
278 }
279 
280 pub fn maskedModOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
281     return maskedOr(D, no, mask, mod(D, a, b));
282 }
283 
284 pub fn maskedSatAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
285     return maskedOr(D, no, mask, saturatedAdd(D, a, b));
286 }
287 
288 pub fn maskedSatSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
289     return maskedOr(D, no, mask, saturatedSub(D, a, b));
290 }
291 
292 pub fn maskedAbsOr(comptime D: type, no: D.Vector, mask: D.Mask, value: D.Vector) D.Vector {
293     return maskedOr(D, no, mask, abs(D, value));
294 }
295 
296 pub fn maskedMulAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
297     return maskedOr(D, no, mask, mulAdd(D, a, b, c));
298 }
299 
300 pub fn maskedMulSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
301     return maskedOr(D, no, mask, mulSub(D, a, b, c));
302 }
303 
304 pub fn maskedNegMulAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
305     return maskedOr(D, no, mask, negMulAdd(D, a, b, c));
306 }
307 
308 pub fn maskedNegMulSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
309     return maskedOr(D, no, mask, negMulSub(D, a, b, c));
310 }
311 
312 pub fn maskedAbs(comptime D: type, mask: D.Mask, value: D.Vector) D.Vector {
313     return maskedZero(D, mask, abs(D, value));
314 }
315 
316 pub fn maskedMax(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
317     return maskedZero(D, mask, max(D, a, b));
318 }
319 
320 pub fn maskedAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
321     return maskedZero(D, mask, add(D, a, b));
322 }
323 
324 pub fn maskedSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
325     return maskedZero(D, mask, sub(D, a, b));
326 }
327 
328 pub fn maskedMul(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
329     return maskedZero(D, mask, mul(D, a, b));
330 }
331 
332 pub fn maskedDiv(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
333     return maskedZero(D, mask, div(D, a, b));
334 }
335 
336 pub fn maskedSaturatedAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
337     return maskedZero(D, mask, saturatedAdd(D, a, b));
338 }
339 
340 pub fn maskedSaturatedSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
341     return maskedZero(D, mask, saturatedSub(D, a, b));
342 }
343 
344 pub fn maskedMulAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
345     return maskedZero(D, mask, mulAdd(D, a, b, c));
346 }
347 
348 pub fn maskedMulSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
349     return maskedZero(D, mask, mulSub(D, a, b, c));
350 }
351 
352 pub fn maskedNegMulAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
353     return maskedZero(D, mask, negMulAdd(D, a, b, c));
354 }
355 
356 pub fn maskedNegMulSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
357     return maskedZero(D, mask, negMulSub(D, a, b, c));
358 }
359 
360 pub fn pairwiseAdd(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
361     if (D.lane_count < 2) @compileError("pairwiseAdd requires at least two lanes");
362     var result: D.Vector = undefined;
363     inline for (0..D.lane_count / 2) |pair| {
364         result[pair * 2] = addLane(D.Lane, a[pair * 2], a[pair * 2 + 1]);
365         result[pair * 2 + 1] = addLane(D.Lane, b[pair * 2], b[pair * 2 + 1]);
366     }
367     return result;
368 }
369 
370 pub fn pairwiseSub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
371     if (D.lane_count < 2) @compileError("pairwiseSub requires at least two lanes");
372     var result: D.Vector = undefined;
373     inline for (0..D.lane_count / 2) |pair| {
374         result[pair * 2] = subLane(D.Lane, a[pair * 2 + 1], a[pair * 2]);
375         result[pair * 2 + 1] = subLane(D.Lane, b[pair * 2 + 1], b[pair * 2]);
376     }
377     return result;
378 }
379 
380 pub fn pairwiseAdd128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
381     return pairwise128(D, false, a, b);
382 }
383 
384 pub fn pairwiseSub128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
385     return pairwise128(D, true, a, b);
386 }
387 
388 fn pairwise128(comptime D: type, comptime subtract: bool, a: D.Vector, b: D.Vector) D.Vector {
389     if (D.byte_count < 16) @compileError("128-bit pairwise operations require at least one full block");
390     const lanes_per_block = 16 / @sizeOf(D.Lane);
391     if (lanes_per_block < 2) @compileError("pairwise operations require at least two lanes per block");
392     var result: D.Vector = undefined;
393     inline for (0..D.lane_count / lanes_per_block) |block| {
394         inline for (0..lanes_per_block / 2) |pair| {
395             const source = block * lanes_per_block + pair * 2;
396             const destination = block * lanes_per_block + pair;
397             result[destination] = if (subtract)
398                 subLane(D.Lane, a[source + 1], a[source])
399             else
400                 addLane(D.Lane, a[source], a[source + 1]);
401             result[destination + lanes_per_block / 2] = if (subtract)
402                 subLane(D.Lane, b[source + 1], b[source])
403             else
404                 addLane(D.Lane, b[source], b[source + 1]);
405         }
406     }
407     return result;
408 }
409 
410 fn addLane(comptime T: type, a: T, b: T) T {
411     return if (@typeInfo(T) == .int) a +% b else a + b;
412 }
413 
414 fn subLane(comptime T: type, a: T, b: T) T {
415     return if (@typeInfo(T) == .int) a -% b else a - b;
416 }
417 
418 fn isInteger(comptime T: type) bool {
419     return @typeInfo(T) == .int;
420 }
421 
422 fn absInteger(comptime D: type, value: D.Vector) D.Vector {
423     var result: D.Vector = undefined;
424     inline for (0..D.lane_count) |index| {
425         result[index] = if (value[index] < 0) 0 -% value[index] else value[index];
426     }
427     return result;
428 }
429 
430 fn saturatedAddLane(comptime T: type, a: T, b: T) T {
431     return if (@typeInfo(T).int.signedness == .signed) blk: {
432         const sum = @as(i128, a) + @as(i128, b);
433         break :blk @intCast(std.math.clamp(
434             sum,
435             @as(i128, std.math.minInt(T)),
436             @as(i128, std.math.maxInt(T)),
437         ));
438     } else blk: {
439         const sum = @as(u128, a) + @as(u128, b);
440         break :blk @intCast(@min(sum, @as(u128, std.math.maxInt(T))));
441     };
442 }
443 
444 fn saturatedSubLane(comptime T: type, a: T, b: T) T {
445     return if (@typeInfo(T).int.signedness == .signed) blk: {
446         const difference = @as(i128, a) - @as(i128, b);
447         break :blk @intCast(std.math.clamp(
448             difference,
449             @as(i128, std.math.minInt(T)),
450             @as(i128, std.math.maxInt(T)),
451         ));
452     } else if (a < b) 0 else a - b;
453 }
454 
455 fn safeDivLane(comptime T: type, a: T, b: T) T {
456     if (b == 0) return 0;
457     if (@typeInfo(T).int.signedness == .signed and
458         a == std.math.minInt(T) and b == -1)
459     {
460         return 0;
461     }
462     return @divTrunc(a, b);
463 }
464 
465 fn safeModLane(comptime T: type, a: T, b: T) T {
466     if (b == 0) return 0;
467     if (@typeInfo(T).int.signedness == .signed and
468         a == std.math.minInt(T) and b == -1)
469     {
470         return 0;
471     }
472     return @rem(a, b);
473 }
474 
475 fn magnitudeChoice(
476     comptime D: type,
477     a: D.Vector,
478     b: D.Vector,
479     comptime choose_maximum: bool,
480 ) D.Vector {
481     var result: D.Vector = undefined;
482     inline for (0..D.lane_count) |index| {
483         const a_smaller = magnitudeLess(D.Lane, a[index], b[index]);
484         result[index] = if (choose_maximum)
485             (if (a_smaller) b[index] else a[index])
486         else
487             (if (a_smaller) a[index] else b[index]);
488     }
489     return result;
490 }
491 
492 fn magnitudeLess(comptime T: type, a: T, b: T) bool {
493     return switch (@typeInfo(T)) {
494         .float => blk: {
495             const magnitude_a = @abs(a);
496             const magnitude_b = @abs(b);
497             break :blk magnitude_a < magnitude_b or
498                 (magnitude_a == magnitude_b and a < b);
499         },
500         .int => |info| if (info.signedness == .unsigned)
501             a < b
502         else blk: {
503             const U = @Int(.unsigned, @bitSizeOf(T));
504             const a_bits: U = @bitCast(a);
505             const b_bits: U = @bitCast(b);
506             const magnitude_a = if (a < 0) 0 -% a_bits else a_bits;
507             const magnitude_b = if (b < 0) 0 -% b_bits else b_bits;
508             break :blk magnitude_a < magnitude_b or
509                 (magnitude_a == magnitude_b and a < b);
510         },
511         else => unreachable,
512     };
513 }
514 
515 fn choose128(
516     comptime D: type,
517     a: D.Vector,
518     b: D.Vector,
519     comptime choose_maximum: bool,
520     comptime upper_only: bool,
521 ) D.Vector {
522     if (D.Lane != u64 or D.lane_count < 2 or D.lane_count & 1 != 0) {
523         @compileError("128-bit min/max requires an even number of u64 lanes");
524     }
525     var result: D.Vector = undefined;
526     inline for (0..D.lane_count / 2) |pair| {
527         const low = pair * 2;
528         const high = low + 1;
529         const a_less = if (upper_only)
530             a[high] < b[high]
531         else
532             a[high] < b[high] or (a[high] == b[high] and a[low] < b[low]);
533         const a_greater = if (upper_only)
534             a[high] > b[high]
535         else
536             a[high] > b[high] or (a[high] == b[high] and a[low] > b[low]);
537         const take_a = if (choose_maximum) a_greater else a_less;
538         result[low] = if (take_a) a[low] else b[low];
539         result[high] = if (take_a) a[high] else b[high];
540     }
541     return result;
542 }
543 
544 fn alternate(comptime D: type, even: D.Vector, odd: D.Vector) D.Vector {
545     var result: D.Vector = undefined;
546     inline for (0..D.lane_count) |index| {
547         result[index] = if (index & 1 == 0) even[index] else odd[index];
548     }
549     return result;
550 }
551 
552 fn maskedOr(comptime D: type, no: D.Vector, mask: D.Mask, yes: D.Vector) D.Vector {
553     return @select(D.Lane, mask, yes, no);
554 }
555 
556 fn maskedZero(comptime D: type, mask: D.Mask, yes: D.Vector) D.Vector {
557     return maskedOr(D, @as(D.Vector, @splat(0)), mask, yes);
558 }
559 
560 fn requireInteger(comptime T: type, comptime operation: []const u8) void {
561     if (@typeInfo(T) != .int) @compileError(operation ++ " requires integer lanes");
562 }
563 
564 fn requireSignedInteger(comptime T: type, comptime operation: []const u8) void {
565     if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .signed) {
566         @compileError(operation ++ " requires signed integer lanes");
567     }
568 }
569 
570 test "integer arithmetic wraps lane by lane" {
571     const simd = @import("root.zig");
572     const D = simd.FixedTag(u8, 4);
573     const a: D.Vector = .{ 255, 0, 200, 3 };
574     const b: D.Vector = .{ 1, 1, 2, 100 };
575     try std.testing.expect(@reduce(.And, add(D, a, b) ==
576         @as(D.Vector, .{ 0, 1, 202, 103 })));
577     try std.testing.expect(@reduce(.And, sub(D, a, b) ==
578         @as(D.Vector, .{ 254, 255, 198, 159 })));
579     try std.testing.expect(@reduce(.And, mul(D, a, b) ==
580         @as(D.Vector, .{ 255, 0, 144, 44 })));
581 }
582 
583 test "floating arithmetic and clamp retain lane semantics" {
584     const simd = @import("root.zig");
585     const D = simd.FixedTag(f32, 4);
586     const a: D.Vector = .{ -2, 1, 4, 8 };
587     const b: D.Vector = @splat(2);
588     try std.testing.expect(@reduce(.And, add(D, a, b) ==
589         @as(D.Vector, .{ 0, 3, 6, 10 })));
590     try std.testing.expect(@reduce(.And, clamp(D, a, @splat(0), @splat(5)) ==
591         @as(D.Vector, .{ 0, 1, 4, 5 })));
592 }
593 
594 test "Highway pairwise arithmetic interleaves source folds" {
595     const simd = @import("root.zig");
596     const D = simd.FixedTag(i16, 8);
597     const a: D.Vector = .{ 1, 2, 3, 4, 5, 6, 7, 8 };
598     const b: D.Vector = .{ 10, 20, 30, 40, 50, 60, 70, 80 };
599     try std.testing.expect(@reduce(.And, pairwiseAdd(D, a, b) == @as(D.Vector, .{
600         3, 30, 7, 70, 11, 110, 15, 150,
601     })));
602     try std.testing.expect(@reduce(.And, pairwiseSub(D, a, b) == @as(D.Vector, .{
603         1, 10, 1, 10, 1, 10, 1, 10,
604     })));
605 }
606 
607 test "Highway 128-bit pairwise arithmetic packs each source half per block" {
608     const simd = @import("root.zig");
609     const D = simd.FixedTag(u32, 8);
610     const a: D.Vector = .{ 1, 2, 3, 4, 5, 6, 7, 8 };
611     const b: D.Vector = .{ 10, 20, 30, 40, 50, 60, 70, 80 };
612     try std.testing.expect(@reduce(.And, pairwiseAdd128(D, a, b) == @as(D.Vector, .{
613         3, 7, 30, 70, 11, 15, 110, 150,
614     })));
615     try std.testing.expect(@reduce(.And, pairwiseSub128(D, a, b) == @as(D.Vector, .{
616         1, 1, 10, 10, 1, 1, 10, 10,
617     })));
618 }
619 
620 fn verifyPairwiseLaneType(comptime T: type) !void {
621     const simd = @import("root.zig");
622     const D = simd.FixedTag(T, @max(2, 16 / @sizeOf(T)));
623     const value: D.Vector = @splat(0);
624     try std.testing.expect(@reduce(.And, pairwiseAdd(D, value, value) == value));
625     try std.testing.expect(@reduce(.And, pairwiseSub(D, value, value) == value));
626     try std.testing.expect(@reduce(.And, pairwiseAdd128(D, value, value) == value));
627     try std.testing.expect(@reduce(.And, pairwiseSub128(D, value, value) == value));
628 }
629 
630 test "Highway pairwise arithmetic instantiates every lane type" {
631     inline for (.{ u8, i8, u16, i16, u32, i32, u64, i64, f16, f32, f64 }) |T| {
632         try verifyPairwiseLaneType(T);
633     }
634 }
635 
636 test "Highway absolute saturating division and alternating arithmetic retain edge semantics" {
637     const simd = @import("root.zig");
638     const D = simd.FixedTag(i16, 8);
639     const a: D.Vector = .{ std.math.minInt(i16), -300, -7, -1, 0, 7, 300, std.math.maxInt(i16) };
640     const b: D.Vector = .{ -1, 1000, 3, 2, 0, -3, 1000, 1 };
641     try std.testing.expect(@reduce(.And, abs(D, a) == @as(D.Vector, .{
642         std.math.minInt(i16), 300, 7, 1, 0, 7, 300, std.math.maxInt(i16),
643     })));
644     try std.testing.expect(@reduce(.And, saturatedAbs(D, a) == @as(D.Vector, .{
645         std.math.maxInt(i16), 300, 7, 1, 0, 7, 300, std.math.maxInt(i16),
646     })));
647     try std.testing.expect(@reduce(.And, saturatedNeg(D, a) == @as(D.Vector, .{
648         std.math.maxInt(i16), 300, 7, 1, 0, -7, -300, -std.math.maxInt(i16),
649     })));
650     try std.testing.expect(@reduce(.And, saturatedAdd(D, a, b) == @as(D.Vector, .{
651         std.math.minInt(i16), 700, -4, 1, 0, 4, 1300, std.math.maxInt(i16),
652     })));
653     try std.testing.expect(@reduce(.And, saturatedSub(D, a, b) == @as(D.Vector, .{
654         -32767, -1300, -10, -3, 0, 10, -700, 32766,
655     })));
656     try std.testing.expect(@reduce(.And, div(D, a, b) == @as(D.Vector, .{
657         0, 0, -2, 0, 0, -2, 0, 32767,
658     })));
659     try std.testing.expect(@reduce(.And, mod(D, a, b) == @as(D.Vector, .{
660         0, -300, -1, -1, 0, 1, 300, 0,
661     })));
662     try std.testing.expect(@reduce(.And, addSub(D, a, b) == @as(D.Vector, .{
663         -32767, 700, -10, 1, 0, 4, -700, -32768,
664     })));
665 }
666 
667 test "Highway absolute difference and saturation instantiate every integer lane" {
668     const simd = @import("root.zig");
669     inline for (.{ u8, i8, u16, i16, u32, i32, u64, i64 }) |T| {
670         const D = simd.FixedTag(T, 4);
671         const zero: D.Vector = @splat(0);
672         const one: D.Vector = @splat(1);
673         _ = absDiff(D, zero, one);
674         _ = saturatedAdd(D, zero, one);
675         _ = saturatedSub(D, zero, one);
676         _ = div(D, one, one);
677         _ = mod(D, one, one);
678         _ = maskedSaturatedAdd(D, @as(D.Mask, @splat(true)), zero, one);
679         _ = maskedSaturatedSub(D, @as(D.Mask, @splat(true)), zero, one);
680     }
681 }
682 
683 test "Highway number and magnitude extrema handle NaN ties and signed minima" {
684     const simd = @import("root.zig");
685     const F = simd.FixedTag(f32, 4);
686     const nan_value = std.math.nan(f32);
687     const a: F.Vector = .{ nan_value, 3, -4, -2 };
688     const b: F.Vector = .{ 7, nan_value, 2, 2 };
689     const minimum = minNumber(F, a, b);
690     const maximum = maxNumber(F, a, b);
691     try std.testing.expectEqual(@as(f32, 7), minimum[0]);
692     try std.testing.expectEqual(@as(f32, 3), minimum[1]);
693     try std.testing.expectEqual(@as(f32, -4), minimum[2]);
694     try std.testing.expectEqual(@as(f32, -2), minimum[3]);
695     try std.testing.expectEqual(@as(f32, 7), maximum[0]);
696     try std.testing.expectEqual(@as(f32, 3), maximum[1]);
697     const I = simd.FixedTag(i32, 4);
698     const x: I.Vector = .{ std.math.minInt(i32), -7, -3, 5 };
699     const y: I.Vector = .{ std.math.maxInt(i32), 6, 3, -5 };
700     try std.testing.expect(@reduce(.And, minMagnitude(I, x, y) == @as(I.Vector, .{
701         std.math.maxInt(i32), 6, -3, -5,
702     })));
703     try std.testing.expect(@reduce(.And, maxMagnitude(I, x, y) == @as(I.Vector, .{
704         std.math.minInt(i32), -7, 3, 5,
705     })));
706 }
707 
708 test "Highway 128-bit extrema compare complete pairs or upper keys" {
709     const simd = @import("root.zig");
710     const D = simd.FixedTag(u64, 8);
711     const a: D.Vector = .{ 9, 1, 7, 4, 10, 6, 20, 8 };
712     const b: D.Vector = .{ 10, 1, 8, 3, 11, 6, 19, 8 };
713     try std.testing.expect(@reduce(.And, min128(D, a, b) == @as(D.Vector, .{
714         9, 1, 8, 3, 10, 6, 19, 8,
715     })));
716     try std.testing.expect(@reduce(.And, max128(D, a, b) == @as(D.Vector, .{
717         10, 1, 7, 4, 11, 6, 20, 8,
718     })));
719     try std.testing.expect(@reduce(.And, min128Upper(D, a, b) == @as(D.Vector, .{
720         10, 1, 8, 3, 11, 6, 19, 8,
721     })));
722     try std.testing.expect(@reduce(.And, max128Upper(D, a, b) == @as(D.Vector, .{
723         10, 1, 7, 4, 11, 6, 19, 8,
724     })));
725 }
726 
727 test "Highway fused and masked arithmetic alternate and merge exact lanes" {
728     const simd = @import("root.zig");
729     const D = simd.FixedTag(f32, 4);
730     const a: D.Vector = .{ 2, 3, 4, 5 };
731     const b: D.Vector = @splat(2);
732     const c: D.Vector = @splat(1);
733     try std.testing.expect(@reduce(.And, mulAddSub(D, a, b, c) == @as(D.Vector, .{ 3, 7, 7, 11 })));
734     try std.testing.expect(@reduce(.And, mulSubAdd(D, a, b, c) == @as(D.Vector, .{ 5, 5, 9, 9 })));
735     const mask: D.Mask = .{ true, false, true, false };
736     try std.testing.expect(@reduce(.And, maskedMulAddOr(D, @splat(9), mask, a, b, c) ==
737         @as(D.Vector, .{ 5, 9, 9, 9 })));
738     try std.testing.expect(@reduce(.And, maskedNegMulSub(D, mask, a, b, c) ==
739         @as(D.Vector, .{ -5, 0, -9, 0 })));
740 }