usearch 2.20.4 → 2.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  //! # SpatialSimilarity - Hardware-Accelerated Similarity Metrics and Distance Functions
2
2
  //!
3
3
  //! * Targets ARM NEON, SVE, x86 AVX2, AVX-512 (VNNI, FP16) hardware backends.
4
- //! * Handles `f64` double-, `f32` single-, and `f16` half-precision, `i8` integral, and binary vectors.
4
+ //! * Handles `f64` double- and `f32` single-precision, integral, and binary vectors.
5
+ //! * Exposes half-precision (`f16`) and brain floating point (`bf16`) types.
5
6
  //! * Zero-dependency header-only C 99 library with bindings for Rust and other languages.
6
7
  //!
7
8
  //! ## Implemented distance functions include:
@@ -26,11 +27,32 @@
26
27
  //!
27
28
  //! // Compute squared Euclidean distance
28
29
  //! let l2sq_dist = i8::l2sq(a, b);
29
- //!
30
+ //!
30
31
  //! // Optimize performance by flushing denormals
31
32
  //! simsimd::capabilities::flush_denormals();
32
33
  //! ```
33
34
  //!
35
+ //! ## Mixed Precision Support
36
+ //!
37
+ //! ```rust
38
+ //! use simsimd::{SpatialSimilarity, f16, bf16};
39
+ //!
40
+ //! // Work with half-precision floats
41
+ //! let half_a: Vec<f16> = vec![1.0, 2.0, 3.0].iter().map(|&x| f16::from_f32(x)).collect();
42
+ //! let half_b: Vec<f16> = vec![4.0, 5.0, 6.0].iter().map(|&x| f16::from_f32(x)).collect();
43
+ //! let half_cos = f16::cos(&half_a, &half_b);
44
+ //!
45
+ //! // Work with brain floats
46
+ //! let brain_a: Vec<bf16> = vec![1.0, 2.0, 3.0].iter().map(|&x| bf16::from_f32(x)).collect();
47
+ //! let brain_b: Vec<bf16> = vec![4.0, 5.0, 6.0].iter().map(|&x| bf16::from_f32(x)).collect();
48
+ //! let brain_cos = bf16::cos(&brain_a, &brain_b);
49
+ //!
50
+ //! // Direct bit manipulation
51
+ //! let half = f16::from_f32(3.14);
52
+ //! let bits = half.0; // Access raw u16 representation
53
+ //! let reconstructed = f16(bits);
54
+ //! ```
55
+ //!
34
56
  //! ## Traits
35
57
  //!
36
58
  //! The `SpatialSimilarity` trait covers following methods:
@@ -50,10 +72,12 @@
50
72
  //! - `kullbackleibler(a: &[Self], b: &[Self]) -> Option<Distance>`: Computes Kullback-Leibler divergence between two slices.
51
73
  //!
52
74
  #![allow(non_camel_case_types)]
75
+ #![cfg_attr(not(test), no_std)]
53
76
 
54
- type Distance = f64;
55
- type ComplexProduct = (f64, f64);
77
+ pub type Distance = f64;
78
+ pub type ComplexProduct = (f64, f64);
56
79
 
80
+ #[link(name = "simsimd")]
57
81
  extern "C" {
58
82
 
59
83
  fn simsimd_dot_i8(a: *const i8, b: *const i8, c: usize, d: *mut Distance);
@@ -136,19 +160,360 @@ extern "C" {
136
160
 
137
161
  fn simsimd_flush_denormals() -> i32;
138
162
  fn simsimd_uses_dynamic_dispatch() -> i32;
163
+
164
+ fn simsimd_f32_to_f16(f32_value: f32, result_ptr: *mut u16);
165
+ fn simsimd_f16_to_f32(f16_ptr: *const u16) -> f32;
166
+ fn simsimd_f32_to_bf16(f32_value: f32, result_ptr: *mut u16);
167
+ fn simsimd_bf16_to_f32(bf16_ptr: *const u16) -> f32;
139
168
  }
140
169
 
141
- /// A half-precision floating point number.
170
+ /// A half-precision (16-bit) floating point number.
171
+ ///
172
+ /// This type represents IEEE 754 half-precision binary floating-point format.
173
+ /// It provides conversion methods to and from f32, and the underlying u16
174
+ /// representation is publicly accessible for direct bit manipulation.
175
+ ///
176
+ /// # Examples
177
+ ///
178
+ /// ```
179
+ /// use simsimd::f16;
180
+ ///
181
+ /// // Create from f32
182
+ /// let half = f16::from_f32(3.14);
183
+ ///
184
+ /// // Convert back to f32
185
+ /// let float = half.to_f32();
186
+ ///
187
+ /// // Direct access to bits
188
+ /// let bits = half.0;
189
+ /// ```
142
190
  #[repr(transparent)]
143
- pub struct f16(u16);
191
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
192
+ pub struct f16(pub u16);
193
+
194
+ impl f16 {
195
+ /// Positive zero.
196
+ pub const ZERO: Self = f16(0);
197
+
198
+ /// Positive one.
199
+ pub const ONE: Self = f16(0x3C00);
200
+
201
+ /// Negative one.
202
+ pub const NEG_ONE: Self = f16(0xBC00);
203
+
204
+ /// Converts an f32 to f16 representation.
205
+ ///
206
+ /// # Examples
207
+ ///
208
+ /// ```
209
+ /// use simsimd::f16;
210
+ /// let half = f16::from_f32(3.14159);
211
+ /// ```
212
+ #[inline(always)]
213
+ pub fn from_f32(value: f32) -> Self {
214
+ let mut result: u16 = 0;
215
+ unsafe { simsimd_f32_to_f16(value, &mut result) };
216
+ f16(result)
217
+ }
218
+
219
+ /// Converts the f16 to an f32.
220
+ ///
221
+ /// # Examples
222
+ ///
223
+ /// ```
224
+ /// use simsimd::f16;
225
+ /// let half = f16::from_f32(3.14159);
226
+ /// let float = half.to_f32();
227
+ /// ```
228
+ #[inline(always)]
229
+ pub fn to_f32(self) -> f32 {
230
+ unsafe { simsimd_f16_to_f32(&self.0) }
231
+ }
232
+
233
+ /// Returns true if this value is NaN.
234
+ #[inline(always)]
235
+ pub fn is_nan(self) -> bool {
236
+ self.to_f32().is_nan()
237
+ }
238
+
239
+ /// Returns true if this value is positive or negative infinity.
240
+ #[inline(always)]
241
+ pub fn is_infinite(self) -> bool {
242
+ self.to_f32().is_infinite()
243
+ }
244
+
245
+ /// Returns true if this number is neither infinite nor NaN.
246
+ #[inline(always)]
247
+ pub fn is_finite(self) -> bool {
248
+ self.to_f32().is_finite()
249
+ }
250
+
251
+ /// Returns the absolute value of self.
252
+ #[inline(always)]
253
+ pub fn abs(self) -> Self {
254
+ Self::from_f32(self.to_f32().abs())
255
+ }
256
+
257
+ /// Returns the largest integer less than or equal to a number.
258
+ ///
259
+ /// This method is only available when the `std` feature is enabled.
260
+ #[cfg(feature = "std")]
261
+ #[inline(always)]
262
+ pub fn floor(self) -> Self {
263
+ Self::from_f32(self.to_f32().floor())
264
+ }
265
+
266
+ /// Returns the smallest integer greater than or equal to a number.
267
+ ///
268
+ /// This method is only available when the `std` feature is enabled.
269
+ #[cfg(feature = "std")]
270
+ #[inline(always)]
271
+ pub fn ceil(self) -> Self {
272
+ Self::from_f32(self.to_f32().ceil())
273
+ }
274
+
275
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
276
+ ///
277
+ /// This method is only available when the `std` feature is enabled.
278
+ #[cfg(feature = "std")]
279
+ #[inline(always)]
280
+ pub fn round(self) -> Self {
281
+ Self::from_f32(self.to_f32().round())
282
+ }
283
+ }
284
+
285
+ #[cfg(feature = "std")]
286
+ impl std::fmt::Display for f16 {
287
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288
+ write!(f, "{}", self.to_f32())
289
+ }
290
+ }
291
+
292
+ impl core::ops::Add for f16 {
293
+ type Output = Self;
294
+
295
+ #[inline(always)]
296
+ fn add(self, rhs: Self) -> Self::Output {
297
+ Self::from_f32(self.to_f32() + rhs.to_f32())
298
+ }
299
+ }
300
+
301
+ impl core::ops::Sub for f16 {
302
+ type Output = Self;
144
303
 
145
- impl f16 {}
304
+ #[inline(always)]
305
+ fn sub(self, rhs: Self) -> Self::Output {
306
+ Self::from_f32(self.to_f32() - rhs.to_f32())
307
+ }
308
+ }
309
+
310
+ impl core::ops::Mul for f16 {
311
+ type Output = Self;
312
+
313
+ #[inline(always)]
314
+ fn mul(self, rhs: Self) -> Self::Output {
315
+ Self::from_f32(self.to_f32() * rhs.to_f32())
316
+ }
317
+ }
146
318
 
147
- /// A half-precision floating point number, called brain float.
319
+ impl core::ops::Div for f16 {
320
+ type Output = Self;
321
+
322
+ #[inline(always)]
323
+ fn div(self, rhs: Self) -> Self::Output {
324
+ Self::from_f32(self.to_f32() / rhs.to_f32())
325
+ }
326
+ }
327
+
328
+ impl core::ops::Neg for f16 {
329
+ type Output = Self;
330
+
331
+ #[inline(always)]
332
+ fn neg(self) -> Self::Output {
333
+ Self::from_f32(-self.to_f32())
334
+ }
335
+ }
336
+
337
+ impl core::cmp::PartialOrd for f16 {
338
+ #[inline(always)]
339
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
340
+ self.to_f32().partial_cmp(&other.to_f32())
341
+ }
342
+ }
343
+
344
+ /// A brain floating point (bfloat16) number.
345
+ ///
346
+ /// This type represents Google's bfloat16 format, which truncates IEEE 754
347
+ /// single-precision to 16 bits by keeping the exponent bits but reducing
348
+ /// the mantissa. This provides a wider range than f16 but lower precision.
349
+ ///
350
+ /// # Examples
351
+ ///
352
+ /// ```
353
+ /// use simsimd::bf16;
354
+ ///
355
+ /// // Create from f32
356
+ /// let brain_half = bf16::from_f32(3.14);
357
+ ///
358
+ /// // Convert back to f32
359
+ /// let float = brain_half.to_f32();
360
+ ///
361
+ /// // Direct access to bits
362
+ /// let bits = brain_half.0;
363
+ /// ```
148
364
  #[repr(transparent)]
149
- pub struct bf16(u16);
365
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
366
+ pub struct bf16(pub u16);
367
+
368
+ impl bf16 {
369
+ /// Positive zero.
370
+ pub const ZERO: Self = bf16(0);
371
+
372
+ /// Positive one.
373
+ pub const ONE: Self = bf16(0x3F80);
374
+
375
+ /// Negative one.
376
+ pub const NEG_ONE: Self = bf16(0xBF80);
377
+
378
+ /// Converts an f32 to bf16 representation.
379
+ ///
380
+ /// # Examples
381
+ ///
382
+ /// ```
383
+ /// use simsimd::bf16;
384
+ /// let brain_half = bf16::from_f32(3.14159);
385
+ /// ```
386
+ #[inline(always)]
387
+ pub fn from_f32(value: f32) -> Self {
388
+ let mut result: u16 = 0;
389
+ unsafe { simsimd_f32_to_bf16(value, &mut result) };
390
+ bf16(result)
391
+ }
392
+
393
+ /// Converts the bf16 to an f32.
394
+ ///
395
+ /// # Examples
396
+ ///
397
+ /// ```
398
+ /// use simsimd::bf16;
399
+ /// let brain_half = bf16::from_f32(3.14159);
400
+ /// let float = brain_half.to_f32();
401
+ /// ```
402
+ #[inline(always)]
403
+ pub fn to_f32(self) -> f32 {
404
+ unsafe { simsimd_bf16_to_f32(&self.0) }
405
+ }
406
+
407
+ /// Returns true if this value is NaN.
408
+ #[inline(always)]
409
+ pub fn is_nan(self) -> bool {
410
+ self.to_f32().is_nan()
411
+ }
412
+
413
+ /// Returns true if this value is positive or negative infinity.
414
+ #[inline(always)]
415
+ pub fn is_infinite(self) -> bool {
416
+ self.to_f32().is_infinite()
417
+ }
418
+
419
+ /// Returns true if this number is neither infinite nor NaN.
420
+ #[inline(always)]
421
+ pub fn is_finite(self) -> bool {
422
+ self.to_f32().is_finite()
423
+ }
424
+
425
+ /// Returns the absolute value of self.
426
+ #[inline(always)]
427
+ pub fn abs(self) -> Self {
428
+ Self::from_f32(self.to_f32().abs())
429
+ }
430
+
431
+ /// Returns the largest integer less than or equal to a number.
432
+ ///
433
+ /// This method is only available when the `std` feature is enabled.
434
+ #[cfg(feature = "std")]
435
+ #[inline(always)]
436
+ pub fn floor(self) -> Self {
437
+ Self::from_f32(self.to_f32().floor())
438
+ }
439
+
440
+ /// Returns the smallest integer greater than or equal to a number.
441
+ ///
442
+ /// This method is only available when the `std` feature is enabled.
443
+ #[cfg(feature = "std")]
444
+ #[inline(always)]
445
+ pub fn ceil(self) -> Self {
446
+ Self::from_f32(self.to_f32().ceil())
447
+ }
448
+
449
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
450
+ ///
451
+ /// This method is only available when the `std` feature is enabled.
452
+ #[cfg(feature = "std")]
453
+ #[inline(always)]
454
+ pub fn round(self) -> Self {
455
+ Self::from_f32(self.to_f32().round())
456
+ }
457
+ }
458
+
459
+ #[cfg(feature = "std")]
460
+ impl std::fmt::Display for bf16 {
461
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462
+ write!(f, "{}", self.to_f32())
463
+ }
464
+ }
465
+
466
+ impl core::ops::Add for bf16 {
467
+ type Output = Self;
468
+
469
+ #[inline(always)]
470
+ fn add(self, rhs: Self) -> Self::Output {
471
+ Self::from_f32(self.to_f32() + rhs.to_f32())
472
+ }
473
+ }
474
+
475
+ impl core::ops::Sub for bf16 {
476
+ type Output = Self;
477
+
478
+ #[inline(always)]
479
+ fn sub(self, rhs: Self) -> Self::Output {
480
+ Self::from_f32(self.to_f32() - rhs.to_f32())
481
+ }
482
+ }
150
483
 
151
- impl bf16 {}
484
+ impl core::ops::Mul for bf16 {
485
+ type Output = Self;
486
+
487
+ #[inline(always)]
488
+ fn mul(self, rhs: Self) -> Self::Output {
489
+ Self::from_f32(self.to_f32() * rhs.to_f32())
490
+ }
491
+ }
492
+
493
+ impl core::ops::Div for bf16 {
494
+ type Output = Self;
495
+
496
+ #[inline(always)]
497
+ fn div(self, rhs: Self) -> Self::Output {
498
+ Self::from_f32(self.to_f32() / rhs.to_f32())
499
+ }
500
+ }
501
+
502
+ impl core::ops::Neg for bf16 {
503
+ type Output = Self;
504
+
505
+ #[inline(always)]
506
+ fn neg(self) -> Self::Output {
507
+ Self::from_f32(-self.to_f32())
508
+ }
509
+ }
510
+
511
+ impl core::cmp::PartialOrd for bf16 {
512
+ #[inline(always)]
513
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
514
+ self.to_f32().partial_cmp(&other.to_f32())
515
+ }
516
+ }
152
517
 
153
518
  /// The `capabilities` module provides functions for detecting the hardware features
154
519
  /// available on the current system.
@@ -215,22 +580,22 @@ pub mod capabilities {
215
580
  }
216
581
 
217
582
  /// Flushes denormalized numbers to zero on the current CPU architecture.
218
- ///
583
+ ///
219
584
  /// This function should be called on each thread before any SIMD operations
220
- /// to avoid performance penalties. When facing denormalized values,
585
+ /// to avoid performance penalties. When facing denormalized values,
221
586
  /// Fused-Multiply-Add (FMA) operations can be up to 30x slower.
222
- ///
587
+ ///
223
588
  /// # Returns
224
- ///
589
+ ///
225
590
  /// Returns `true` if the operation was successful, `false` otherwise.
226
591
  pub fn flush_denormals() -> bool {
227
592
  unsafe { crate::simsimd_flush_denormals() != 0 }
228
593
  }
229
594
 
230
595
  /// Checks if the library is using dynamic dispatch for function selection.
231
- ///
596
+ ///
232
597
  /// # Returns
233
- ///
598
+ ///
234
599
  /// Returns `true` if dynamic dispatch is enabled, `false` otherwise.
235
600
  /// Currently always returns `false` as dynamic dispatch is not implemented.
236
601
  pub fn uses_dynamic_dispatch() -> bool {
@@ -1237,4 +1602,62 @@ mod tests {
1237
1602
  }
1238
1603
  }
1239
1604
  }
1605
+
1606
+ #[test]
1607
+ fn test_f16_arithmetic() {
1608
+ let a = f16::from_f32(3.5);
1609
+ let b = f16::from_f32(2.0);
1610
+
1611
+ // Test basic arithmetic
1612
+ assert!((a + b).to_f32() - 5.5 < 0.01);
1613
+ assert!((a - b).to_f32() - 1.5 < 0.01);
1614
+ assert!((a * b).to_f32() - 7.0 < 0.01);
1615
+ assert!((a / b).to_f32() - 1.75 < 0.01);
1616
+ assert!((-a).to_f32() + 3.5 < 0.01);
1617
+
1618
+ // Test constants
1619
+ assert!(f16::ZERO.to_f32() == 0.0);
1620
+ assert!((f16::ONE.to_f32() - 1.0).abs() < 0.01);
1621
+ assert!((f16::NEG_ONE.to_f32() + 1.0).abs() < 0.01);
1622
+
1623
+ // Test comparisons
1624
+ assert!(a > b);
1625
+ assert!(!(a < b));
1626
+ assert!(a == a);
1627
+
1628
+ // Test utility methods
1629
+ assert!((-a).abs().to_f32() - 3.5 < 0.01);
1630
+ assert!(a.is_finite());
1631
+ assert!(!a.is_nan());
1632
+ assert!(!a.is_infinite());
1633
+ }
1634
+
1635
+ #[test]
1636
+ fn test_bf16_arithmetic() {
1637
+ let a = bf16::from_f32(3.5);
1638
+ let b = bf16::from_f32(2.0);
1639
+
1640
+ // Test basic arithmetic
1641
+ assert!((a + b).to_f32() - 5.5 < 0.1);
1642
+ assert!((a - b).to_f32() - 1.5 < 0.1);
1643
+ assert!((a * b).to_f32() - 7.0 < 0.1);
1644
+ assert!((a / b).to_f32() - 1.75 < 0.1);
1645
+ assert!((-a).to_f32() + 3.5 < 0.1);
1646
+
1647
+ // Test constants
1648
+ assert!(bf16::ZERO.to_f32() == 0.0);
1649
+ assert!((bf16::ONE.to_f32() - 1.0).abs() < 0.01);
1650
+ assert!((bf16::NEG_ONE.to_f32() + 1.0).abs() < 0.01);
1651
+
1652
+ // Test comparisons
1653
+ assert!(a > b);
1654
+ assert!(!(a < b));
1655
+ assert!(a == a);
1656
+
1657
+ // Test utility methods
1658
+ assert!((-a).abs().to_f32() - 3.5 < 0.1);
1659
+ assert!(a.is_finite());
1660
+ assert!(!a.is_nan());
1661
+ assert!(!a.is_infinite());
1662
+ }
1240
1663
  }