usearch 2.21.4 → 2.25.0

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.
Files changed (602) hide show
  1. package/README.md +24 -24
  2. package/binding.gyp +15 -37
  3. package/c/CMakeLists.txt +4 -5
  4. package/c/README.md +7 -7
  5. package/c/lib.cpp +32 -14
  6. package/c/test.c +83 -1
  7. package/c/usearch.h +24 -3
  8. package/cpp/CMakeLists.txt +4 -0
  9. package/cpp/README.md +5 -5
  10. package/cpp/bench.cpp +97 -116
  11. package/cpp/test.cpp +117 -20
  12. package/include/usearch/index.hpp +224 -48
  13. package/include/usearch/index_dense.hpp +105 -40
  14. package/include/usearch/index_plugins.hpp +1387 -204
  15. package/javascript/dist/cjs/usearch.d.ts +8 -3
  16. package/javascript/dist/cjs/usearch.js +12 -4
  17. package/javascript/dist/esm/usearch.d.ts +8 -3
  18. package/javascript/dist/esm/usearch.js +12 -4
  19. package/javascript/lib.cpp +17 -0
  20. package/javascript/usearch.ts +14 -6
  21. package/numkong/.clang-tidy +30 -0
  22. package/{simsimd → numkong}/.cmake-format.py +18 -0
  23. package/numkong/.editorconfig +12 -0
  24. package/numkong/.git-blame-ignore-revs +123 -0
  25. package/numkong/.mailmap +15 -0
  26. package/numkong/.spi.yml +11 -0
  27. package/numkong/.swift-format +14 -0
  28. package/numkong/CITATION.cff +81 -0
  29. package/numkong/CONTRIBUTING.md +500 -0
  30. package/numkong/Cargo.toml +54 -0
  31. package/numkong/MANIFEST.in +8 -0
  32. package/numkong/Package.swift +84 -0
  33. package/numkong/README.md +569 -0
  34. package/numkong/bench/README.md +217 -0
  35. package/numkong/bench/bench-browser.html +323 -0
  36. package/numkong/bench/bench.cpp +344 -0
  37. package/numkong/bench/bench.hpp +816 -0
  38. package/numkong/bench/bench.js +148 -0
  39. package/numkong/bench/bench.mjs +616 -0
  40. package/numkong/bench/bench_attention.cpp +113 -0
  41. package/numkong/bench/bench_cast.cpp +264 -0
  42. package/numkong/bench/bench_cross_amx.cpp +62 -0
  43. package/numkong/bench/bench_cross_arm.cpp +245 -0
  44. package/numkong/bench/bench_cross_blas.cpp +309 -0
  45. package/numkong/bench/bench_cross_loongarch.cpp +106 -0
  46. package/numkong/bench/bench_cross_power.cpp +84 -0
  47. package/numkong/bench/bench_cross_rvv.cpp +121 -0
  48. package/numkong/bench/bench_cross_serial.cpp +151 -0
  49. package/numkong/bench/bench_cross_sme.cpp +163 -0
  50. package/numkong/bench/bench_cross_wasm.cpp +142 -0
  51. package/numkong/bench/bench_cross_x86.cpp +455 -0
  52. package/numkong/bench/bench_curved.cpp +210 -0
  53. package/numkong/bench/bench_dot.cpp +315 -0
  54. package/numkong/bench/bench_each.cpp +503 -0
  55. package/numkong/bench/bench_geospatial.cpp +112 -0
  56. package/numkong/bench/bench_maxsim.cpp +154 -0
  57. package/numkong/bench/bench_mesh.cpp +148 -0
  58. package/numkong/bench/bench_probability.cpp +60 -0
  59. package/numkong/bench/bench_reduce.cpp +371 -0
  60. package/numkong/bench/bench_set.cpp +73 -0
  61. package/numkong/bench/bench_sparse.cpp +198 -0
  62. package/numkong/bench/bench_spatial.cpp +347 -0
  63. package/numkong/bench/bench_trigonometry.cpp +137 -0
  64. package/numkong/bench/swift/Bench.swift +506 -0
  65. package/numkong/binding.gyp +169 -0
  66. package/numkong/build.rs +484 -0
  67. package/numkong/c/dispatch.h +512 -0
  68. package/numkong/c/dispatch_bf16.c +447 -0
  69. package/numkong/c/dispatch_bf16c.c +52 -0
  70. package/numkong/c/dispatch_e2m3.c +296 -0
  71. package/numkong/c/dispatch_e3m2.c +284 -0
  72. package/numkong/c/dispatch_e4m3.c +318 -0
  73. package/numkong/c/dispatch_e5m2.c +316 -0
  74. package/numkong/c/dispatch_f16.c +452 -0
  75. package/numkong/c/dispatch_f16c.c +58 -0
  76. package/numkong/c/dispatch_f32.c +434 -0
  77. package/numkong/c/dispatch_f32c.c +99 -0
  78. package/numkong/c/dispatch_f64.c +348 -0
  79. package/numkong/c/dispatch_f64c.c +98 -0
  80. package/numkong/c/dispatch_i16.c +96 -0
  81. package/numkong/c/dispatch_i32.c +89 -0
  82. package/numkong/c/dispatch_i4.c +153 -0
  83. package/numkong/c/dispatch_i64.c +86 -0
  84. package/numkong/c/dispatch_i8.c +348 -0
  85. package/numkong/c/dispatch_other.c +348 -0
  86. package/numkong/c/dispatch_u1.c +193 -0
  87. package/numkong/c/dispatch_u16.c +124 -0
  88. package/numkong/c/dispatch_u32.c +118 -0
  89. package/numkong/c/dispatch_u4.c +153 -0
  90. package/numkong/c/dispatch_u64.c +102 -0
  91. package/numkong/c/dispatch_u8.c +364 -0
  92. package/numkong/c/numkong.c +940 -0
  93. package/numkong/cmake/nk_arm_isa_probes.cmake +61 -0
  94. package/numkong/cmake/nk_isa_probe.cmake +90 -0
  95. package/numkong/cmake/nk_loongarch_isa_probes.cmake +15 -0
  96. package/numkong/cmake/nk_math_lib.cmake +46 -0
  97. package/numkong/cmake/nk_power_isa_probes.cmake +15 -0
  98. package/numkong/cmake/nk_riscv_isa_probes.cmake +19 -0
  99. package/numkong/cmake/nk_x86_isa_probes.cmake +35 -0
  100. package/numkong/cmake/toolchain-aarch64-gnu.cmake +86 -0
  101. package/numkong/cmake/toolchain-android-arm64.cmake +25 -0
  102. package/numkong/cmake/toolchain-android-armv7.cmake +25 -0
  103. package/numkong/cmake/toolchain-loongarch64-gnu.cmake +68 -0
  104. package/numkong/cmake/toolchain-ppc64le-gnu.cmake +74 -0
  105. package/numkong/cmake/toolchain-riscv64-gnu.cmake +107 -0
  106. package/numkong/cmake/toolchain-riscv64-llvm.cmake +114 -0
  107. package/numkong/cmake/toolchain-wasi.cmake +77 -0
  108. package/numkong/cmake/toolchain-wasm.cmake +70 -0
  109. package/numkong/cmake/toolchain-wasm64.cmake +62 -0
  110. package/numkong/cmake/toolchain-x86_64-llvm.cmake +47 -0
  111. package/numkong/include/README.md +643 -0
  112. package/numkong/include/module.modulemap +129 -0
  113. package/numkong/include/numkong/attention/sapphireamx.h +1363 -0
  114. package/numkong/include/numkong/attention/sme.h +2075 -0
  115. package/numkong/include/numkong/attention.h +49 -0
  116. package/numkong/include/numkong/capabilities.h +930 -0
  117. package/numkong/include/numkong/cast/README.md +262 -0
  118. package/numkong/include/numkong/cast/diamond.h +64 -0
  119. package/numkong/include/numkong/cast/haswell.h +823 -0
  120. package/numkong/include/numkong/cast/icelake.h +475 -0
  121. package/numkong/include/numkong/cast/loongsonasx.h +252 -0
  122. package/numkong/include/numkong/cast/neon.h +1174 -0
  123. package/numkong/include/numkong/cast/powervsx.h +449 -0
  124. package/numkong/include/numkong/cast/rvv.h +970 -0
  125. package/numkong/include/numkong/cast/sapphire.h +262 -0
  126. package/numkong/include/numkong/cast/serial.h +2336 -0
  127. package/numkong/include/numkong/cast/skylake.h +915 -0
  128. package/numkong/include/numkong/cast/v128relaxed.h +537 -0
  129. package/numkong/include/numkong/cast.h +254 -0
  130. package/numkong/include/numkong/cast.hpp +44 -0
  131. package/numkong/include/numkong/curved/README.md +223 -0
  132. package/numkong/include/numkong/curved/genoa.h +182 -0
  133. package/numkong/include/numkong/curved/haswell.h +276 -0
  134. package/numkong/include/numkong/curved/neon.h +329 -0
  135. package/numkong/include/numkong/curved/neonbfdot.h +211 -0
  136. package/numkong/include/numkong/curved/rvv.h +305 -0
  137. package/numkong/include/numkong/curved/serial.h +207 -0
  138. package/numkong/include/numkong/curved/skylake.h +457 -0
  139. package/numkong/include/numkong/curved/smef64.h +530 -0
  140. package/numkong/include/numkong/curved.h +513 -0
  141. package/numkong/include/numkong/curved.hpp +144 -0
  142. package/numkong/include/numkong/dot/README.md +442 -0
  143. package/numkong/include/numkong/dot/alder.h +563 -0
  144. package/numkong/include/numkong/dot/diamond.h +158 -0
  145. package/numkong/include/numkong/dot/genoa.h +289 -0
  146. package/numkong/include/numkong/dot/haswell.h +1723 -0
  147. package/numkong/include/numkong/dot/icelake.h +997 -0
  148. package/numkong/include/numkong/dot/loongsonasx.h +671 -0
  149. package/numkong/include/numkong/dot/neon.h +869 -0
  150. package/numkong/include/numkong/dot/neonbfdot.h +243 -0
  151. package/numkong/include/numkong/dot/neonfhm.h +358 -0
  152. package/numkong/include/numkong/dot/neonfp8.h +323 -0
  153. package/numkong/include/numkong/dot/neonsdot.h +622 -0
  154. package/numkong/include/numkong/dot/powervsx.h +752 -0
  155. package/numkong/include/numkong/dot/rvv.h +722 -0
  156. package/numkong/include/numkong/dot/rvvbb.h +72 -0
  157. package/numkong/include/numkong/dot/rvvbf16.h +123 -0
  158. package/numkong/include/numkong/dot/rvvhalf.h +129 -0
  159. package/numkong/include/numkong/dot/sapphire.h +141 -0
  160. package/numkong/include/numkong/dot/serial.h +874 -0
  161. package/numkong/include/numkong/dot/sierra.h +405 -0
  162. package/numkong/include/numkong/dot/skylake.h +1116 -0
  163. package/numkong/include/numkong/dot/sve.h +420 -0
  164. package/numkong/include/numkong/dot/svebfdot.h +75 -0
  165. package/numkong/include/numkong/dot/svehalf.h +168 -0
  166. package/numkong/include/numkong/dot/svesdot.h +90 -0
  167. package/numkong/include/numkong/dot/v128relaxed.h +1293 -0
  168. package/numkong/include/numkong/dot.h +1136 -0
  169. package/numkong/include/numkong/dot.hpp +94 -0
  170. package/numkong/include/numkong/dots/README.md +496 -0
  171. package/numkong/include/numkong/dots/alder.h +117 -0
  172. package/numkong/include/numkong/dots/diamond.h +86 -0
  173. package/numkong/include/numkong/dots/genoa.h +100 -0
  174. package/numkong/include/numkong/dots/graniteamx.h +733 -0
  175. package/numkong/include/numkong/dots/haswell.h +310 -0
  176. package/numkong/include/numkong/dots/icelake.h +180 -0
  177. package/numkong/include/numkong/dots/loongsonasx.h +176 -0
  178. package/numkong/include/numkong/dots/neon.h +123 -0
  179. package/numkong/include/numkong/dots/neonbfdot.h +59 -0
  180. package/numkong/include/numkong/dots/neonfhm.h +96 -0
  181. package/numkong/include/numkong/dots/neonfp8.h +99 -0
  182. package/numkong/include/numkong/dots/neonsdot.h +144 -0
  183. package/numkong/include/numkong/dots/powervsx.h +194 -0
  184. package/numkong/include/numkong/dots/rvv.h +2593 -0
  185. package/numkong/include/numkong/dots/sapphireamx.h +4017 -0
  186. package/numkong/include/numkong/dots/serial.h +2867 -0
  187. package/numkong/include/numkong/dots/sierra.h +100 -0
  188. package/numkong/include/numkong/dots/skylake.h +207 -0
  189. package/numkong/include/numkong/dots/sme.h +5041 -0
  190. package/numkong/include/numkong/dots/smebi32.h +477 -0
  191. package/numkong/include/numkong/dots/smef64.h +1328 -0
  192. package/numkong/include/numkong/dots/smehalf.h +47 -0
  193. package/numkong/include/numkong/dots/v128relaxed.h +317 -0
  194. package/numkong/include/numkong/dots.h +3110 -0
  195. package/numkong/include/numkong/dots.hpp +641 -0
  196. package/numkong/include/numkong/each/README.md +465 -0
  197. package/numkong/include/numkong/each/haswell.h +1658 -0
  198. package/numkong/include/numkong/each/icelake.h +272 -0
  199. package/numkong/include/numkong/each/neon.h +1138 -0
  200. package/numkong/include/numkong/each/neonbfdot.h +211 -0
  201. package/numkong/include/numkong/each/neonhalf.h +318 -0
  202. package/numkong/include/numkong/each/rvv.h +1121 -0
  203. package/numkong/include/numkong/each/sapphire.h +343 -0
  204. package/numkong/include/numkong/each/serial.h +282 -0
  205. package/numkong/include/numkong/each/skylake.h +1562 -0
  206. package/numkong/include/numkong/each/v128relaxed.h +562 -0
  207. package/numkong/include/numkong/each.h +2232 -0
  208. package/numkong/include/numkong/each.hpp +434 -0
  209. package/numkong/include/numkong/geospatial/README.md +147 -0
  210. package/numkong/include/numkong/geospatial/haswell.h +633 -0
  211. package/numkong/include/numkong/geospatial/neon.h +615 -0
  212. package/numkong/include/numkong/geospatial/rvv.h +701 -0
  213. package/numkong/include/numkong/geospatial/serial.h +309 -0
  214. package/numkong/include/numkong/geospatial/skylake.h +613 -0
  215. package/numkong/include/numkong/geospatial/v128relaxed.h +652 -0
  216. package/numkong/include/numkong/geospatial.h +453 -0
  217. package/numkong/include/numkong/geospatial.hpp +235 -0
  218. package/numkong/include/numkong/matrix.hpp +336 -0
  219. package/numkong/include/numkong/maxsim/README.md +187 -0
  220. package/numkong/include/numkong/maxsim/alder.h +511 -0
  221. package/numkong/include/numkong/maxsim/genoa.h +115 -0
  222. package/numkong/include/numkong/maxsim/haswell.h +553 -0
  223. package/numkong/include/numkong/maxsim/icelake.h +480 -0
  224. package/numkong/include/numkong/maxsim/neonsdot.h +394 -0
  225. package/numkong/include/numkong/maxsim/sapphireamx.h +877 -0
  226. package/numkong/include/numkong/maxsim/serial.h +490 -0
  227. package/numkong/include/numkong/maxsim/sme.h +993 -0
  228. package/numkong/include/numkong/maxsim/v128relaxed.h +280 -0
  229. package/numkong/include/numkong/maxsim.h +571 -0
  230. package/numkong/include/numkong/maxsim.hpp +133 -0
  231. package/numkong/include/numkong/mesh/README.md +219 -0
  232. package/numkong/include/numkong/mesh/haswell.h +2013 -0
  233. package/numkong/include/numkong/mesh/neon.h +1821 -0
  234. package/numkong/include/numkong/mesh/neonbfdot.h +771 -0
  235. package/numkong/include/numkong/mesh/rvv.h +936 -0
  236. package/numkong/include/numkong/mesh/serial.h +721 -0
  237. package/numkong/include/numkong/mesh/skylake.h +2344 -0
  238. package/numkong/include/numkong/mesh/v128relaxed.h +1081 -0
  239. package/numkong/include/numkong/mesh.h +666 -0
  240. package/numkong/include/numkong/mesh.hpp +701 -0
  241. package/numkong/include/numkong/numkong.h +78 -0
  242. package/numkong/include/numkong/numkong.hpp +58 -0
  243. package/numkong/include/numkong/probability/README.md +177 -0
  244. package/numkong/include/numkong/probability/haswell.h +267 -0
  245. package/numkong/include/numkong/probability/neon.h +255 -0
  246. package/numkong/include/numkong/probability/rvv.h +414 -0
  247. package/numkong/include/numkong/probability/serial.h +181 -0
  248. package/numkong/include/numkong/probability/skylake.h +324 -0
  249. package/numkong/include/numkong/probability.h +380 -0
  250. package/numkong/include/numkong/probability.hpp +120 -0
  251. package/numkong/include/numkong/random.h +50 -0
  252. package/numkong/include/numkong/random.hpp +285 -0
  253. package/numkong/include/numkong/reduce/README.md +552 -0
  254. package/numkong/include/numkong/reduce/alder.h +636 -0
  255. package/numkong/include/numkong/reduce/genoa.h +201 -0
  256. package/numkong/include/numkong/reduce/haswell.h +3785 -0
  257. package/numkong/include/numkong/reduce/icelake.h +549 -0
  258. package/numkong/include/numkong/reduce/neon.h +3980 -0
  259. package/numkong/include/numkong/reduce/neonbfdot.h +142 -0
  260. package/numkong/include/numkong/reduce/neonfhm.h +293 -0
  261. package/numkong/include/numkong/reduce/neonsdot.h +357 -0
  262. package/numkong/include/numkong/reduce/rvv.h +3533 -0
  263. package/numkong/include/numkong/reduce/serial.h +753 -0
  264. package/numkong/include/numkong/reduce/sierra.h +338 -0
  265. package/numkong/include/numkong/reduce/skylake.h +3794 -0
  266. package/numkong/include/numkong/reduce/sve.h +52 -0
  267. package/numkong/include/numkong/reduce/v128relaxed.h +2302 -0
  268. package/numkong/include/numkong/reduce.h +1582 -0
  269. package/numkong/include/numkong/reduce.hpp +778 -0
  270. package/numkong/include/numkong/scalar/README.md +89 -0
  271. package/numkong/include/numkong/scalar/haswell.h +122 -0
  272. package/numkong/include/numkong/scalar/loongsonasx.h +74 -0
  273. package/numkong/include/numkong/scalar/neon.h +122 -0
  274. package/numkong/include/numkong/scalar/neonhalf.h +70 -0
  275. package/numkong/include/numkong/scalar/powervsx.h +96 -0
  276. package/numkong/include/numkong/scalar/rvv.h +211 -0
  277. package/numkong/include/numkong/scalar/sapphire.h +74 -0
  278. package/numkong/include/numkong/scalar/serial.h +332 -0
  279. package/numkong/include/numkong/scalar/v128relaxed.h +56 -0
  280. package/numkong/include/numkong/scalar.h +696 -0
  281. package/numkong/include/numkong/set/README.md +179 -0
  282. package/numkong/include/numkong/set/haswell.h +334 -0
  283. package/numkong/include/numkong/set/icelake.h +485 -0
  284. package/numkong/include/numkong/set/loongsonasx.h +181 -0
  285. package/numkong/include/numkong/set/neon.h +363 -0
  286. package/numkong/include/numkong/set/powervsx.h +326 -0
  287. package/numkong/include/numkong/set/rvv.h +226 -0
  288. package/numkong/include/numkong/set/rvvbb.h +117 -0
  289. package/numkong/include/numkong/set/serial.h +174 -0
  290. package/numkong/include/numkong/set/sve.h +187 -0
  291. package/numkong/include/numkong/set/v128relaxed.h +240 -0
  292. package/numkong/include/numkong/set.h +471 -0
  293. package/numkong/include/numkong/set.hpp +114 -0
  294. package/numkong/include/numkong/sets/README.md +149 -0
  295. package/numkong/include/numkong/sets/haswell.h +63 -0
  296. package/numkong/include/numkong/sets/icelake.h +66 -0
  297. package/numkong/include/numkong/sets/loongsonasx.h +52 -0
  298. package/numkong/include/numkong/sets/neon.h +61 -0
  299. package/numkong/include/numkong/sets/powervsx.h +65 -0
  300. package/numkong/include/numkong/sets/serial.h +43 -0
  301. package/numkong/include/numkong/sets/smebi32.h +1135 -0
  302. package/numkong/include/numkong/sets/v128relaxed.h +58 -0
  303. package/numkong/include/numkong/sets.h +382 -0
  304. package/numkong/include/numkong/sparse/README.md +156 -0
  305. package/numkong/include/numkong/sparse/icelake.h +463 -0
  306. package/numkong/include/numkong/sparse/neon.h +288 -0
  307. package/numkong/include/numkong/sparse/serial.h +117 -0
  308. package/numkong/include/numkong/sparse/sve2.h +504 -0
  309. package/numkong/include/numkong/sparse/turin.h +322 -0
  310. package/numkong/include/numkong/sparse.h +363 -0
  311. package/numkong/include/numkong/sparse.hpp +113 -0
  312. package/numkong/include/numkong/spatial/README.md +439 -0
  313. package/numkong/include/numkong/spatial/alder.h +604 -0
  314. package/numkong/include/numkong/spatial/diamond.h +240 -0
  315. package/numkong/include/numkong/spatial/genoa.h +222 -0
  316. package/numkong/include/numkong/spatial/haswell.h +979 -0
  317. package/numkong/include/numkong/spatial/icelake.h +1067 -0
  318. package/numkong/include/numkong/spatial/loongsonasx.h +483 -0
  319. package/numkong/include/numkong/spatial/neon.h +846 -0
  320. package/numkong/include/numkong/spatial/neonbfdot.h +164 -0
  321. package/numkong/include/numkong/spatial/neonfp8.h +258 -0
  322. package/numkong/include/numkong/spatial/neonsdot.h +429 -0
  323. package/numkong/include/numkong/spatial/powervsx.h +738 -0
  324. package/numkong/include/numkong/spatial/rvv.h +991 -0
  325. package/numkong/include/numkong/spatial/rvvbf16.h +128 -0
  326. package/numkong/include/numkong/spatial/rvvhalf.h +120 -0
  327. package/numkong/include/numkong/spatial/serial.h +347 -0
  328. package/numkong/include/numkong/spatial/sierra.h +516 -0
  329. package/numkong/include/numkong/spatial/skylake.h +605 -0
  330. package/numkong/include/numkong/spatial/sve.h +246 -0
  331. package/numkong/include/numkong/spatial/svebfdot.h +125 -0
  332. package/numkong/include/numkong/spatial/svehalf.h +136 -0
  333. package/numkong/include/numkong/spatial/svesdot.h +143 -0
  334. package/numkong/include/numkong/spatial/v128relaxed.h +969 -0
  335. package/numkong/include/numkong/spatial.h +1681 -0
  336. package/numkong/include/numkong/spatial.hpp +183 -0
  337. package/numkong/include/numkong/spatials/README.md +580 -0
  338. package/numkong/include/numkong/spatials/alder.h +94 -0
  339. package/numkong/include/numkong/spatials/diamond.h +82 -0
  340. package/numkong/include/numkong/spatials/genoa.h +94 -0
  341. package/numkong/include/numkong/spatials/graniteamx.h +173 -0
  342. package/numkong/include/numkong/spatials/haswell.h +219 -0
  343. package/numkong/include/numkong/spatials/icelake.h +113 -0
  344. package/numkong/include/numkong/spatials/loongsonasx.h +153 -0
  345. package/numkong/include/numkong/spatials/neon.h +109 -0
  346. package/numkong/include/numkong/spatials/neonbfdot.h +60 -0
  347. package/numkong/include/numkong/spatials/neonfhm.h +92 -0
  348. package/numkong/include/numkong/spatials/neonfp8.h +111 -0
  349. package/numkong/include/numkong/spatials/neonsdot.h +143 -0
  350. package/numkong/include/numkong/spatials/powervsx.h +153 -0
  351. package/numkong/include/numkong/spatials/rvv.h +1976 -0
  352. package/numkong/include/numkong/spatials/sapphireamx.h +1149 -0
  353. package/numkong/include/numkong/spatials/serial.h +248 -0
  354. package/numkong/include/numkong/spatials/sierra.h +96 -0
  355. package/numkong/include/numkong/spatials/skylake.h +184 -0
  356. package/numkong/include/numkong/spatials/sme.h +1927 -0
  357. package/numkong/include/numkong/spatials/smef64.h +483 -0
  358. package/numkong/include/numkong/spatials/v128relaxed.h +240 -0
  359. package/numkong/include/numkong/spatials.h +3374 -0
  360. package/numkong/include/numkong/spatials.hpp +509 -0
  361. package/numkong/include/numkong/tensor.hpp +1981 -0
  362. package/numkong/include/numkong/trigonometry/README.md +184 -0
  363. package/numkong/include/numkong/trigonometry/haswell.h +653 -0
  364. package/numkong/include/numkong/trigonometry/neon.h +638 -0
  365. package/numkong/include/numkong/trigonometry/rvv.h +700 -0
  366. package/numkong/include/numkong/trigonometry/serial.h +703 -0
  367. package/numkong/include/numkong/trigonometry/skylake.h +725 -0
  368. package/numkong/include/numkong/trigonometry/v128relaxed.h +667 -0
  369. package/numkong/include/numkong/trigonometry.h +467 -0
  370. package/numkong/include/numkong/trigonometry.hpp +166 -0
  371. package/numkong/include/numkong/types.h +1695 -0
  372. package/numkong/include/numkong/types.hpp +6027 -0
  373. package/numkong/include/numkong/vector.hpp +766 -0
  374. package/numkong/javascript/@numkong-darwin-arm64/package.json +26 -0
  375. package/numkong/javascript/@numkong-darwin-x64/package.json +26 -0
  376. package/numkong/javascript/@numkong-linux-arm64/package.json +25 -0
  377. package/numkong/javascript/@numkong-linux-x64/package.json +25 -0
  378. package/numkong/javascript/@numkong-win32-arm64/package.json +25 -0
  379. package/numkong/javascript/@numkong-win32-x64/package.json +25 -0
  380. package/numkong/javascript/README.md +254 -0
  381. package/numkong/javascript/node-gyp-build.d.ts +4 -0
  382. package/numkong/javascript/numkong-browser.ts +40 -0
  383. package/numkong/javascript/numkong-wasm.ts +750 -0
  384. package/numkong/javascript/numkong.c +712 -0
  385. package/numkong/javascript/numkong.ts +613 -0
  386. package/{simsimd → numkong}/javascript/tsconfig-base.json +6 -3
  387. package/numkong/javascript/tsconfig-cjs.json +13 -0
  388. package/{simsimd → numkong}/javascript/tsconfig-esm.json +1 -1
  389. package/numkong/javascript/types.ts +784 -0
  390. package/numkong/numkong.gypi +104 -0
  391. package/numkong/package.json +108 -0
  392. package/numkong/probes/arm_neon.c +8 -0
  393. package/numkong/probes/arm_neon_bfdot.c +9 -0
  394. package/numkong/probes/arm_neon_fhm.c +9 -0
  395. package/numkong/probes/arm_neon_half.c +8 -0
  396. package/numkong/probes/arm_neon_sdot.c +9 -0
  397. package/numkong/probes/arm_neonfp8.c +9 -0
  398. package/numkong/probes/arm_sme.c +16 -0
  399. package/numkong/probes/arm_sme2.c +16 -0
  400. package/numkong/probes/arm_sme2p1.c +16 -0
  401. package/numkong/probes/arm_sme_bf16.c +16 -0
  402. package/numkong/probes/arm_sme_bi32.c +16 -0
  403. package/numkong/probes/arm_sme_f64.c +16 -0
  404. package/numkong/probes/arm_sme_fa64.c +14 -0
  405. package/numkong/probes/arm_sme_half.c +16 -0
  406. package/numkong/probes/arm_sme_lut2.c +15 -0
  407. package/numkong/probes/arm_sve.c +18 -0
  408. package/numkong/probes/arm_sve2.c +20 -0
  409. package/numkong/probes/arm_sve2p1.c +18 -0
  410. package/numkong/probes/arm_sve_bfdot.c +20 -0
  411. package/numkong/probes/arm_sve_half.c +18 -0
  412. package/numkong/probes/arm_sve_sdot.c +21 -0
  413. package/numkong/probes/loongarch_lasx.c +12 -0
  414. package/numkong/probes/power_vsx.c +12 -0
  415. package/numkong/probes/probe.js +127 -0
  416. package/numkong/probes/riscv_rvv.c +14 -0
  417. package/numkong/probes/riscv_rvv_bb.c +15 -0
  418. package/numkong/probes/riscv_rvv_bf16.c +17 -0
  419. package/numkong/probes/riscv_rvv_half.c +14 -0
  420. package/numkong/probes/wasm_v128relaxed.c +11 -0
  421. package/numkong/probes/x86_alder.c +17 -0
  422. package/numkong/probes/x86_diamond.c +17 -0
  423. package/numkong/probes/x86_genoa.c +17 -0
  424. package/numkong/probes/x86_graniteamx.c +19 -0
  425. package/numkong/probes/x86_haswell.c +11 -0
  426. package/numkong/probes/x86_icelake.c +17 -0
  427. package/numkong/probes/x86_sapphire.c +16 -0
  428. package/numkong/probes/x86_sapphireamx.c +18 -0
  429. package/numkong/probes/x86_sierra.c +17 -0
  430. package/numkong/probes/x86_skylake.c +15 -0
  431. package/numkong/probes/x86_turin.c +17 -0
  432. package/numkong/rust/README.md +698 -0
  433. package/numkong/rust/capabilities.rs +96 -0
  434. package/numkong/rust/cast.rs +314 -0
  435. package/numkong/rust/curved.rs +391 -0
  436. package/numkong/rust/each.rs +2721 -0
  437. package/numkong/rust/geospatial.rs +343 -0
  438. package/numkong/rust/matrix.rs +5424 -0
  439. package/numkong/rust/maxsim.rs +540 -0
  440. package/numkong/rust/mesh.rs +837 -0
  441. package/numkong/rust/numkong.rs +409 -0
  442. package/numkong/rust/probability.rs +272 -0
  443. package/numkong/rust/reduce.rs +1249 -0
  444. package/numkong/rust/set.rs +194 -0
  445. package/numkong/rust/sparse.rs +513 -0
  446. package/numkong/rust/spatial.rs +1815 -0
  447. package/numkong/rust/tensor.rs +7565 -0
  448. package/numkong/rust/types.rs +3666 -0
  449. package/numkong/rust/vector.rs +1899 -0
  450. package/numkong/rustfmt.toml +2 -0
  451. package/numkong/swift/Matrix.swift +1388 -0
  452. package/numkong/swift/MaxSim.swift +151 -0
  453. package/numkong/swift/NumKong.swift +283 -0
  454. package/numkong/swift/Numerics.swift +329 -0
  455. package/numkong/swift/README.md +433 -0
  456. package/numkong/swift/Scalar.swift +741 -0
  457. package/numkong/swift/Tensor.swift +209 -0
  458. package/numkong/test/README.md +315 -0
  459. package/numkong/test/playwright.config.ts +16 -0
  460. package/numkong/test/swift/Test.swift +534 -0
  461. package/numkong/test/test-browser-bundle-runner.mjs +36 -0
  462. package/numkong/test/test-browser-bundle.html +188 -0
  463. package/numkong/test/test-browser-runner.mjs +43 -0
  464. package/numkong/test/test-browser.html +305 -0
  465. package/numkong/test/test-dtypes.mjs +453 -0
  466. package/numkong/test/test-wasm.mjs +267 -0
  467. package/numkong/test/test.cpp +442 -0
  468. package/numkong/test/test.hpp +580 -0
  469. package/numkong/test/test.mjs +363 -0
  470. package/numkong/test/test_base.py +1041 -0
  471. package/numkong/test/test_cast.cpp +242 -0
  472. package/numkong/test/test_cross.hpp +551 -0
  473. package/numkong/test/test_cross.py +467 -0
  474. package/numkong/test/test_cross_amx.cpp +50 -0
  475. package/numkong/test/test_cross_arm.cpp +225 -0
  476. package/numkong/test/test_cross_blas.cpp +354 -0
  477. package/numkong/test/test_cross_loongarch.cpp +90 -0
  478. package/numkong/test/test_cross_power.cpp +73 -0
  479. package/numkong/test/test_cross_rvv.cpp +109 -0
  480. package/numkong/test/test_cross_serial.cpp +181 -0
  481. package/numkong/test/test_cross_sme.cpp +148 -0
  482. package/numkong/test/test_cross_wasm.cpp +141 -0
  483. package/numkong/test/test_cross_x86.cpp +442 -0
  484. package/numkong/test/test_curved.cpp +182 -0
  485. package/numkong/test/test_curved.py +207 -0
  486. package/numkong/test/test_dot.cpp +353 -0
  487. package/numkong/test/test_dot.py +296 -0
  488. package/numkong/test/test_each.cpp +437 -0
  489. package/numkong/test/test_each.py +756 -0
  490. package/numkong/test/test_geospatial.cpp +155 -0
  491. package/numkong/test/test_geospatial.py +241 -0
  492. package/numkong/test/test_maxsim.cpp +138 -0
  493. package/numkong/test/test_maxsim.py +209 -0
  494. package/numkong/test/test_mesh.cpp +203 -0
  495. package/numkong/test/test_mesh.py +394 -0
  496. package/numkong/test/test_probability.cpp +136 -0
  497. package/numkong/test/test_probability.py +166 -0
  498. package/numkong/test/test_reduce.cpp +563 -0
  499. package/numkong/test/test_reduce.py +781 -0
  500. package/numkong/test/test_set.cpp +151 -0
  501. package/numkong/test/test_set.py +120 -0
  502. package/numkong/test/test_similarities.py +579 -0
  503. package/numkong/test/test_sparse.cpp +121 -0
  504. package/numkong/test/test_sparse.py +118 -0
  505. package/numkong/test/test_spatial.cpp +518 -0
  506. package/numkong/test/test_spatial.py +269 -0
  507. package/numkong/test/test_tensor.cpp +884 -0
  508. package/numkong/test/test_tensor.py +1417 -0
  509. package/numkong/test/test_trigonometry.cpp +156 -0
  510. package/numkong/test/test_trigonometry.py +180 -0
  511. package/package.json +7 -8
  512. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  513. package/prebuilds/linux-arm64/usearch.node +0 -0
  514. package/prebuilds/linux-x64/usearch.node +0 -0
  515. package/fp16/CMakeLists.txt +0 -176
  516. package/fp16/LICENSE +0 -11
  517. package/fp16/README.md +0 -20
  518. package/fp16/bench/alt-element.cc +0 -89
  519. package/fp16/bench/from-alt-array.cc +0 -175
  520. package/fp16/bench/from-ieee-array.cc +0 -399
  521. package/fp16/bench/ieee-element.cc +0 -272
  522. package/fp16/bench/to-alt-array.cc +0 -91
  523. package/fp16/bench/to-ieee-array.cc +0 -303
  524. package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
  525. package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
  526. package/fp16/cmake/DownloadPSimd.cmake +0 -15
  527. package/fp16/configure.py +0 -65
  528. package/fp16/confu.yaml +0 -8
  529. package/fp16/include/fp16/__init__.py +0 -0
  530. package/fp16/include/fp16/avx.py +0 -50
  531. package/fp16/include/fp16/avx2.py +0 -53
  532. package/fp16/include/fp16/bitcasts.h +0 -92
  533. package/fp16/include/fp16/fp16.h +0 -451
  534. package/fp16/include/fp16/psimd.h +0 -131
  535. package/fp16/include/fp16.h +0 -11
  536. package/fp16/jni/Android.mk +0 -6
  537. package/fp16/jni/Application.mk +0 -4
  538. package/fp16/test/alt-from-fp32-value.cc +0 -498
  539. package/fp16/test/alt-to-fp32-bits.cc +0 -263
  540. package/fp16/test/alt-to-fp32-psimd.cc +0 -145
  541. package/fp16/test/alt-to-fp32-value.cc +0 -348
  542. package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
  543. package/fp16/test/bitcasts.cc +0 -56
  544. package/fp16/test/ieee-from-fp32-value.cc +0 -523
  545. package/fp16/test/ieee-to-fp32-bits.cc +0 -333
  546. package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
  547. package/fp16/test/ieee-to-fp32-value.cc +0 -420
  548. package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
  549. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
  550. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
  551. package/fp16/test/peachpy/stubs.py +0 -38
  552. package/fp16/test/tables.cc +0 -530
  553. package/fp16/test/tables.h +0 -13
  554. package/fp16/third-party/THHalf.h +0 -92
  555. package/fp16/third-party/eigen-half.h +0 -170
  556. package/fp16/third-party/float16-compressor.h +0 -80
  557. package/fp16/third-party/half.hpp +0 -2916
  558. package/fp16/third-party/npy-halffloat.h +0 -328
  559. package/simsimd/.git-blame-ignore-revs +0 -2
  560. package/simsimd/CONTRIBUTING.md +0 -321
  561. package/simsimd/Cargo.toml +0 -49
  562. package/simsimd/MANIFEST.in +0 -4
  563. package/simsimd/Package.swift +0 -28
  564. package/simsimd/README.md +0 -1493
  565. package/simsimd/binding.gyp +0 -35
  566. package/simsimd/build.rs +0 -70
  567. package/simsimd/c/lib.c +0 -374
  568. package/simsimd/include/module.modulemap +0 -4
  569. package/simsimd/include/simsimd/binary.h +0 -486
  570. package/simsimd/include/simsimd/curved.h +0 -1541
  571. package/simsimd/include/simsimd/dot.h +0 -1853
  572. package/simsimd/include/simsimd/elementwise.h +0 -2465
  573. package/simsimd/include/simsimd/geospatial.h +0 -43
  574. package/simsimd/include/simsimd/mesh.h +0 -69
  575. package/simsimd/include/simsimd/probability.h +0 -606
  576. package/simsimd/include/simsimd/simsimd.h +0 -2567
  577. package/simsimd/include/simsimd/sparse.h +0 -1384
  578. package/simsimd/include/simsimd/spatial.h +0 -2335
  579. package/simsimd/include/simsimd/types.h +0 -666
  580. package/simsimd/javascript/fallback.ts +0 -233
  581. package/simsimd/javascript/lib.c +0 -125
  582. package/simsimd/javascript/node-gyp-build.d.ts +0 -1
  583. package/simsimd/javascript/simsimd.ts +0 -179
  584. package/simsimd/javascript/tsconfig-cjs.json +0 -8
  585. package/simsimd/package-lock.json +0 -1761
  586. package/simsimd/package.json +0 -61
  587. package/simsimd/rust/lib.rs +0 -1821
  588. package/simsimd/scripts/bench.cxx +0 -1170
  589. package/simsimd/scripts/bench.ipynb +0 -187
  590. package/simsimd/scripts/bench.js +0 -127
  591. package/simsimd/scripts/bench.rs +0 -876
  592. package/simsimd/scripts/bench_vectors.py +0 -734
  593. package/simsimd/scripts/bench_vectors_live.py +0 -213
  594. package/simsimd/scripts/test.c +0 -187
  595. package/simsimd/scripts/test.mjs +0 -220
  596. package/simsimd/scripts/test.py +0 -1505
  597. package/simsimd/swift/SimSIMD.swift +0 -149
  598. package/simsimd/swift/Test.swift +0 -128
  599. /package/{simsimd → numkong}/CODE_OF_CONDUCT.md +0 -0
  600. /package/{simsimd → numkong}/LICENSE +0 -0
  601. /package/{simsimd → numkong}/javascript/dist-package-cjs.json +0 -0
  602. /package/{simsimd → numkong}/javascript/dist-package-esm.json +0 -0
@@ -0,0 +1,3666 @@
1
+ //! Scalar types and conversion trait for mixed-precision computing.
2
+ //!
3
+ //! This module defines **15+ numeric types** spanning 1-bit packed storage to
4
+ //! 128-bit complex. All of them implement [`StorageElement`] (for containers) and
5
+ //! [`NumberLike`] (for conversion to / from `f32` / `f64`), so generic code can
6
+ //! treat them uniformly.
7
+ //!
8
+ //! ## IEEE / Brain Floats
9
+ //!
10
+ //! - [`struct@f16`]: IEEE 754 half-precision (16-bit) floating point
11
+ //! - [`bf16`]: Brain floating point (bfloat16) — truncated single precision
12
+ //! - The Rust primitives [`f32`](prim@f32) and [`f64`](prim@f64) also implement
13
+ //! these traits
14
+ //!
15
+ //! ## 8-bit Floats (OCP / MX)
16
+ //!
17
+ //! - [`e4m3`]: 4-exponent, 3-mantissa FP8 (max ≈ 448)
18
+ //! - [`e5m2`]: 5-exponent, 2-mantissa FP8 (wider range, ~57 344 max)
19
+ //! - [`e2m3`]: 2-exponent, 3-mantissa 6-bit float padded to 8-bit (max 7.5)
20
+ //! - [`e3m2`]: 3-exponent, 2-mantissa 6-bit float padded to 8-bit (max 28)
21
+ //!
22
+ //! ## Signed / Unsigned Integers
23
+ //!
24
+ //! `i8` / `u8`, `i16` / `u16`, `i32` / `u32`, `i64` / `u64` all implement
25
+ //! [`StorageElement`] and [`NumberLike`] via the primitive blanket impls below.
26
+ //!
27
+ //! ## Sub-byte Packed Types
28
+ //!
29
+ //! - [`i4x2`]: Packed pair of signed 4-bit integers (one byte → two values)
30
+ //! - [`u4x2`]: Packed pair of unsigned 4-bit integers
31
+ //! - [`u1x8`]: Packed 8 binary values in a single byte
32
+ //!
33
+ //! ## Complex
34
+ //!
35
+ //! - [`f16c`], [`bf16c`], [`f32c`], [`f64c`] — generic `complex<Scalar>` wrappers
36
+ //! that expose `re`, `im`, `conj`, and arithmetic operators.
37
+ //!
38
+ //! All types support standard arithmetic operations and conversion to / from
39
+ //! `f32` via the [`FloatLike`] trait.
40
+
41
+ #![allow(non_camel_case_types)]
42
+
43
+ #[link(name = "numkong")]
44
+ extern "C" {
45
+ fn nk_f32_to_f16(src: *const f32, dest: *mut u16);
46
+ fn nk_f16_to_f32(src: *const u16, dest: *mut f32);
47
+ fn nk_f32_to_bf16(src: *const f32, dest: *mut u16);
48
+ fn nk_bf16_to_f32(src: *const u16, dest: *mut f32);
49
+ fn nk_f32_to_e4m3(src: *const f32, dest: *mut u8);
50
+ fn nk_e4m3_to_f32(src: *const u8, dest: *mut f32);
51
+ fn nk_f32_to_e5m2(src: *const f32, dest: *mut u8);
52
+ fn nk_e5m2_to_f32(src: *const u8, dest: *mut f32);
53
+ fn nk_f32_to_e2m3(src: *const f32, dest: *mut u8);
54
+ fn nk_e2m3_to_f32(src: *const u8, dest: *mut f32);
55
+ fn nk_f32_to_e3m2(src: *const f32, dest: *mut u8);
56
+ fn nk_e3m2_to_f32(src: *const u8, dest: *mut f32);
57
+ }
58
+
59
+ /// Compatibility function for pre 1.85 Rust versions lacking `f32::abs`.
60
+ #[inline(always)]
61
+ pub(crate) fn f32_abs_compat(x: f32) -> f32 {
62
+ f32::from_bits(x.to_bits() & 0x7FFF_FFFF)
63
+ }
64
+
65
+ /// Compatibility function for pre 1.85 Rust versions lacking `f32::round`.
66
+ #[inline(always)]
67
+ pub(crate) fn f32_round_compat(x: f32) -> f32 {
68
+ let truncated_value = x as i32 as f32;
69
+ let fractional_part = x - truncated_value;
70
+ if fractional_part >= 0.5 {
71
+ truncated_value + 1.0
72
+ } else if fractional_part <= -0.5 {
73
+ truncated_value - 1.0
74
+ } else {
75
+ truncated_value
76
+ }
77
+ }
78
+
79
+ /// Compatibility function for pre 1.85 Rust versions lacking `f64::round`.
80
+ #[inline(always)]
81
+ pub(crate) fn f64_round_compat(x: f64) -> f64 {
82
+ let truncated_value = x as i64 as f64;
83
+ let fractional_part = x - truncated_value;
84
+ if fractional_part >= 0.5 {
85
+ truncated_value + 1.0
86
+ } else if fractional_part <= -0.5 {
87
+ truncated_value - 1.0
88
+ } else {
89
+ truncated_value
90
+ }
91
+ }
92
+
93
+ /// Check whether two values are within tolerance: `|a - b| <= atol + rtol * |b|`.
94
+ #[inline]
95
+ pub fn is_close(a: f64, b: f64, atol: f64, rtol: f64) -> bool {
96
+ let diff = if a > b { a - b } else { b - a };
97
+ diff <= atol + rtol * (if b >= 0.0 { b } else { -b })
98
+ }
99
+
100
+ // region: f16 Type
101
+
102
+ /// Half-precision (16-bit) IEEE 754 floating-point number.
103
+ ///
104
+ /// Layout: sign(1) + exponent(5) + mantissa(10), bias=15.
105
+ /// Range: ±65504, epsilon at 1.0 ≈ 9.77×10⁻⁴, subnormal min ≈ 5.96×10⁻⁸.
106
+ /// 30 722 of 63 488 finite values (48.4%) fall in [−1, +1].
107
+ /// All arithmetic via f32 upcast/downcast.
108
+ ///
109
+ /// # Examples
110
+ ///
111
+ /// ```
112
+ /// use numkong::f16;
113
+ ///
114
+ /// // Create from f32
115
+ /// let half = f16::from_f32(3.14);
116
+ ///
117
+ /// // Convert back to f32
118
+ /// let float = half.to_f32();
119
+ ///
120
+ /// // Direct access to bits
121
+ /// let bits = half.0;
122
+ /// ```
123
+ #[repr(transparent)]
124
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
125
+ pub struct f16(pub u16);
126
+
127
+ impl f16 {
128
+ /// Positive zero.
129
+ pub const ZERO: Self = f16(0);
130
+ /// Positive one.
131
+ pub const ONE: Self = f16(0x3C00);
132
+ /// Negative one.
133
+ pub const NEG_ONE: Self = f16(0xBC00);
134
+ /// Quiet NaN (Not a Number).
135
+ pub const NAN: Self = f16(0x7E00);
136
+
137
+ /// Converts an `f32` value to `f16`. Out-of-range values saturate.
138
+ ///
139
+ /// # Examples
140
+ ///
141
+ /// ```
142
+ /// use numkong::f16;
143
+ /// // Roundtrip a value well inside f16's ±65504 range.
144
+ /// let half = f16::from_f32(3.14);
145
+ /// assert!((half.to_f32() - 3.14).abs() < 1e-2);
146
+ /// ```
147
+ #[inline(always)]
148
+ pub fn from_f32(value: f32) -> Self {
149
+ let mut result: u16 = 0;
150
+ unsafe { nk_f32_to_f16(&value, &mut result) };
151
+ f16(result)
152
+ }
153
+
154
+ /// Converts this value back to `f32`.
155
+ ///
156
+ /// # Examples
157
+ ///
158
+ /// ```
159
+ /// use numkong::f16;
160
+ /// let half = f16::from_f32(-2.5);
161
+ /// assert_eq!(half.to_f32(), -2.5);
162
+ /// ```
163
+ #[inline(always)]
164
+ pub fn to_f32(self) -> f32 {
165
+ let mut result: f32 = 0.0;
166
+ unsafe { nk_f16_to_f32(&self.0, &mut result) };
167
+ result
168
+ }
169
+
170
+ /// Returns true if this value is NaN.
171
+ #[inline(always)]
172
+ pub fn is_nan(self) -> bool {
173
+ self.to_f32().is_nan()
174
+ }
175
+
176
+ /// Returns true if this value is ±∞.
177
+ #[inline(always)]
178
+ pub fn is_infinite(self) -> bool {
179
+ self.to_f32().is_infinite()
180
+ }
181
+
182
+ /// Returns true if this number is neither infinite nor NaN.
183
+ #[inline(always)]
184
+ pub fn is_finite(self) -> bool {
185
+ self.to_f32().is_finite()
186
+ }
187
+
188
+ /// Returns |self|.
189
+ #[inline(always)]
190
+ pub fn abs(self) -> Self {
191
+ Self::from_f32(f32_abs_compat(self.to_f32()))
192
+ }
193
+
194
+ /// Returns ⌊self⌋. Requires `std`.
195
+ #[cfg(feature = "std")]
196
+ #[inline(always)]
197
+ pub fn floor(self) -> Self {
198
+ Self::from_f32(self.to_f32().floor())
199
+ }
200
+
201
+ /// Returns ⌈self⌉. Requires `std`.
202
+ #[cfg(feature = "std")]
203
+ #[inline(always)]
204
+ pub fn ceil(self) -> Self {
205
+ Self::from_f32(self.to_f32().ceil())
206
+ }
207
+
208
+ /// Rounds to the nearest integer; half-way cases go away from zero. Requires `std`.
209
+ #[cfg(feature = "std")]
210
+ #[inline(always)]
211
+ pub fn round(self) -> Self {
212
+ Self::from_f32(self.to_f32().round())
213
+ }
214
+ }
215
+
216
+ impl core::fmt::Debug for f16 {
217
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
218
+ write!(f, "f16({}, 0x{:04x})", self.to_f32(), self.0)
219
+ }
220
+ }
221
+
222
+ impl core::fmt::Display for f16 {
223
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224
+ if f.alternate() {
225
+ core::fmt::Display::fmt(&self.to_f32(), f)?;
226
+ write!(f, " [0x{:04x}]", self.0)
227
+ } else {
228
+ core::fmt::Display::fmt(&self.to_f32(), f)
229
+ }
230
+ }
231
+ }
232
+
233
+ impl core::fmt::LowerHex for f16 {
234
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
235
+ core::fmt::LowerHex::fmt(&self.0, f)
236
+ }
237
+ }
238
+
239
+ impl core::fmt::UpperHex for f16 {
240
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
241
+ core::fmt::UpperHex::fmt(&self.0, f)
242
+ }
243
+ }
244
+
245
+ impl core::fmt::Binary for f16 {
246
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
247
+ core::fmt::Binary::fmt(&self.0, f)
248
+ }
249
+ }
250
+
251
+ impl core::ops::Add for f16 {
252
+ type Output = Self;
253
+ #[inline(always)]
254
+ fn add(self, right: Self) -> Self::Output {
255
+ Self::from_f32(self.to_f32() + right.to_f32())
256
+ }
257
+ }
258
+
259
+ impl core::ops::Sub for f16 {
260
+ type Output = Self;
261
+ #[inline(always)]
262
+ fn sub(self, right: Self) -> Self::Output {
263
+ Self::from_f32(self.to_f32() - right.to_f32())
264
+ }
265
+ }
266
+
267
+ impl core::ops::Mul for f16 {
268
+ type Output = Self;
269
+ #[inline(always)]
270
+ fn mul(self, right: Self) -> Self::Output {
271
+ Self::from_f32(self.to_f32() * right.to_f32())
272
+ }
273
+ }
274
+
275
+ impl core::ops::Div for f16 {
276
+ type Output = Self;
277
+ #[inline(always)]
278
+ fn div(self, right: Self) -> Self::Output {
279
+ Self::from_f32(self.to_f32() / right.to_f32())
280
+ }
281
+ }
282
+
283
+ impl core::ops::Neg for f16 {
284
+ type Output = Self;
285
+ #[inline(always)]
286
+ fn neg(self) -> Self::Output {
287
+ Self::from_f32(-self.to_f32())
288
+ }
289
+ }
290
+
291
+ impl core::cmp::PartialOrd for f16 {
292
+ #[inline(always)]
293
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
294
+ self.to_f32().partial_cmp(&other.to_f32())
295
+ }
296
+ }
297
+
298
+ // endregion: f16 Type
299
+
300
+ // region: bf16 Type
301
+
302
+ /// BFloat16 (16-bit) floating-point number — truncated IEEE 754 single-precision.
303
+ ///
304
+ /// Layout: sign(1) + exponent(8) + mantissa(7), bias=127.
305
+ /// Range: ±3.39×10³⁸ (same dynamic range as f32), epsilon at 1.0 ≈ 7.81×10⁻³.
306
+ /// 32 514 of 65 280 finite values (49.8%) fall in [−1, +1].
307
+ /// Wider dynamic range than f16 but lower precision (7 vs 10 mantissa bits).
308
+ ///
309
+ /// # Examples
310
+ ///
311
+ /// ```
312
+ /// use numkong::bf16;
313
+ ///
314
+ /// let brain = bf16::from_f32(3.14);
315
+ /// let float = brain.to_f32();
316
+ /// ```
317
+ #[repr(transparent)]
318
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
319
+ pub struct bf16(pub u16);
320
+
321
+ impl bf16 {
322
+ /// Positive zero.
323
+ pub const ZERO: Self = bf16(0);
324
+
325
+ /// Positive one.
326
+ pub const ONE: Self = bf16(0x3F80);
327
+
328
+ /// Negative one.
329
+ pub const NEG_ONE: Self = bf16(0xBF80);
330
+ /// Quiet NaN (Not a Number).
331
+ pub const NAN: Self = bf16(0x7FC0);
332
+
333
+ /// Converts an `f32` value to `bf16`. Out-of-range values saturate.
334
+ ///
335
+ /// # Examples
336
+ ///
337
+ /// ```
338
+ /// use numkong::bf16;
339
+ /// // bf16 matches f32's ±3.39e38 range but with only 7 bits of mantissa.
340
+ /// let brain = bf16::from_f32(1.0e30);
341
+ /// assert!((brain.to_f32() - 1.0e30).abs() < 1.0e28);
342
+ /// ```
343
+ #[inline(always)]
344
+ pub fn from_f32(value: f32) -> Self {
345
+ let mut result: u16 = 0;
346
+ unsafe { nk_f32_to_bf16(&value, &mut result) };
347
+ bf16(result)
348
+ }
349
+
350
+ /// Converts this value back to `f32`.
351
+ #[inline(always)]
352
+ pub fn to_f32(self) -> f32 {
353
+ let mut result: f32 = 0.0;
354
+ unsafe { nk_bf16_to_f32(&self.0, &mut result) };
355
+ result
356
+ }
357
+
358
+ /// Returns true if this value is NaN.
359
+ #[inline(always)]
360
+ pub fn is_nan(self) -> bool {
361
+ self.to_f32().is_nan()
362
+ }
363
+
364
+ /// Returns true if this value is positive or negative infinity.
365
+ #[inline(always)]
366
+ pub fn is_infinite(self) -> bool {
367
+ self.to_f32().is_infinite()
368
+ }
369
+
370
+ /// Returns true if this number is neither infinite nor NaN.
371
+ #[inline(always)]
372
+ pub fn is_finite(self) -> bool {
373
+ self.to_f32().is_finite()
374
+ }
375
+
376
+ /// Returns the absolute value of self.
377
+ #[inline(always)]
378
+ pub fn abs(self) -> Self {
379
+ Self::from_f32(f32_abs_compat(self.to_f32()))
380
+ }
381
+
382
+ /// Returns the largest integer less than or equal to a number.
383
+ ///
384
+ /// This method is only available when the `std` feature is enabled.
385
+ #[cfg(feature = "std")]
386
+ #[inline(always)]
387
+ pub fn floor(self) -> Self {
388
+ Self::from_f32(self.to_f32().floor())
389
+ }
390
+
391
+ /// Returns the smallest integer greater than or equal to a number.
392
+ ///
393
+ /// This method is only available when the `std` feature is enabled.
394
+ #[cfg(feature = "std")]
395
+ #[inline(always)]
396
+ pub fn ceil(self) -> Self {
397
+ Self::from_f32(self.to_f32().ceil())
398
+ }
399
+
400
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
401
+ ///
402
+ /// This method is only available when the `std` feature is enabled.
403
+ #[cfg(feature = "std")]
404
+ #[inline(always)]
405
+ pub fn round(self) -> Self {
406
+ Self::from_f32(self.to_f32().round())
407
+ }
408
+ }
409
+
410
+ impl core::fmt::Debug for bf16 {
411
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
412
+ write!(f, "bf16({}, 0x{:04x})", self.to_f32(), self.0)
413
+ }
414
+ }
415
+
416
+ impl core::fmt::Display for bf16 {
417
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
418
+ if f.alternate() {
419
+ core::fmt::Display::fmt(&self.to_f32(), f)?;
420
+ write!(f, " [0x{:04x}]", self.0)
421
+ } else {
422
+ core::fmt::Display::fmt(&self.to_f32(), f)
423
+ }
424
+ }
425
+ }
426
+
427
+ impl core::fmt::LowerHex for bf16 {
428
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
429
+ core::fmt::LowerHex::fmt(&self.0, f)
430
+ }
431
+ }
432
+
433
+ impl core::fmt::UpperHex for bf16 {
434
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
435
+ core::fmt::UpperHex::fmt(&self.0, f)
436
+ }
437
+ }
438
+
439
+ impl core::fmt::Binary for bf16 {
440
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
441
+ core::fmt::Binary::fmt(&self.0, f)
442
+ }
443
+ }
444
+
445
+ impl core::ops::Add for bf16 {
446
+ type Output = Self;
447
+ #[inline(always)]
448
+ fn add(self, right: Self) -> Self::Output {
449
+ Self::from_f32(self.to_f32() + right.to_f32())
450
+ }
451
+ }
452
+
453
+ impl core::ops::Sub for bf16 {
454
+ type Output = Self;
455
+ #[inline(always)]
456
+ fn sub(self, right: Self) -> Self::Output {
457
+ Self::from_f32(self.to_f32() - right.to_f32())
458
+ }
459
+ }
460
+
461
+ impl core::ops::Mul for bf16 {
462
+ type Output = Self;
463
+ #[inline(always)]
464
+ fn mul(self, right: Self) -> Self::Output {
465
+ Self::from_f32(self.to_f32() * right.to_f32())
466
+ }
467
+ }
468
+
469
+ impl core::ops::Div for bf16 {
470
+ type Output = Self;
471
+ #[inline(always)]
472
+ fn div(self, right: Self) -> Self::Output {
473
+ Self::from_f32(self.to_f32() / right.to_f32())
474
+ }
475
+ }
476
+
477
+ impl core::ops::Neg for bf16 {
478
+ type Output = Self;
479
+ #[inline(always)]
480
+ fn neg(self) -> Self::Output {
481
+ Self::from_f32(-self.to_f32())
482
+ }
483
+ }
484
+
485
+ impl core::cmp::PartialOrd for bf16 {
486
+ #[inline(always)]
487
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
488
+ self.to_f32().partial_cmp(&other.to_f32())
489
+ }
490
+ }
491
+
492
+ // endregion: bf16 Type
493
+
494
+ // region: e4m3 Type
495
+
496
+ /// 8-bit E4M3 floating-point number (OCP FP8).
497
+ ///
498
+ /// Layout: sign(1) + exponent(4) + mantissa(3), bias=7.
499
+ /// Range: ±448, no infinities (all-ones exponent → NaN).
500
+ /// 114 of 254 finite values (44.9%) fall in [−1, +1].
501
+ /// Exact integer dot products via exponent-sum binning (29 bins).
502
+ ///
503
+ /// # Examples
504
+ ///
505
+ /// ```
506
+ /// use numkong::e4m3;
507
+ ///
508
+ /// let fp8 = e4m3::from_f32(2.5);
509
+ /// let float = fp8.to_f32();
510
+ /// ```
511
+ #[repr(transparent)]
512
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
513
+ pub struct e4m3(pub u8);
514
+
515
+ impl e4m3 {
516
+ pub const ZERO: Self = e4m3(0x00);
517
+ pub const ONE: Self = e4m3(0x38);
518
+ pub const NEG_ONE: Self = e4m3(0xB8);
519
+ /// Quiet NaN (Not a Number).
520
+ pub const NAN: Self = e4m3(0x7F);
521
+
522
+ /// Converts an `f32` value to `e4m3`. Out-of-range values saturate.
523
+ ///
524
+ /// # Examples
525
+ ///
526
+ /// ```
527
+ /// use numkong::e4m3;
528
+ /// // e4m3 quantises to ~4 effective decimal digits inside ±448.
529
+ /// let fp8 = e4m3::from_f32(3.5);
530
+ /// assert_eq!(fp8.to_f32(), 3.5);
531
+ /// ```
532
+ #[inline(always)]
533
+ pub fn from_f32(value: f32) -> Self {
534
+ let mut result: u8 = 0;
535
+ unsafe { nk_f32_to_e4m3(&value, &mut result) };
536
+ e4m3(result)
537
+ }
538
+
539
+ /// Converts this value back to `f32`.
540
+ #[inline(always)]
541
+ pub fn to_f32(self) -> f32 {
542
+ let mut result: f32 = 0.0;
543
+ unsafe { nk_e4m3_to_f32(&self.0, &mut result) };
544
+ result
545
+ }
546
+
547
+ /// Returns true if this value is NaN.
548
+ #[inline(always)]
549
+ pub fn is_nan(self) -> bool {
550
+ (self.0 & 0x7F) == 0x7F
551
+ }
552
+
553
+ /// Returns true if this value is ±∞. Always false for E4M3 (no infinities).
554
+ #[inline(always)]
555
+ pub fn is_infinite(self) -> bool {
556
+ false
557
+ }
558
+
559
+ /// Returns true if this number is neither infinite nor NaN.
560
+ /// Note: E4M3 format has no infinities.
561
+ #[inline(always)]
562
+ pub fn is_finite(self) -> bool {
563
+ !self.is_nan()
564
+ }
565
+
566
+ /// Returns the absolute value of self.
567
+ #[inline(always)]
568
+ pub fn abs(self) -> Self {
569
+ Self::from_f32(f32_abs_compat(self.to_f32()))
570
+ }
571
+
572
+ /// Returns the largest integer less than or equal to a number.
573
+ ///
574
+ /// This method is only available when the `std` feature is enabled.
575
+ #[cfg(feature = "std")]
576
+ #[inline(always)]
577
+ pub fn floor(self) -> Self {
578
+ Self::from_f32(self.to_f32().floor())
579
+ }
580
+
581
+ /// Returns the smallest integer greater than or equal to a number.
582
+ ///
583
+ /// This method is only available when the `std` feature is enabled.
584
+ #[cfg(feature = "std")]
585
+ #[inline(always)]
586
+ pub fn ceil(self) -> Self {
587
+ Self::from_f32(self.to_f32().ceil())
588
+ }
589
+
590
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
591
+ ///
592
+ /// This method is only available when the `std` feature is enabled.
593
+ #[cfg(feature = "std")]
594
+ #[inline(always)]
595
+ pub fn round(self) -> Self {
596
+ Self::from_f32(self.to_f32().round())
597
+ }
598
+ }
599
+
600
+ impl core::fmt::Debug for e4m3 {
601
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
602
+ write!(f, "e4m3({}, 0x{:02x})", self.to_f32(), self.0)
603
+ }
604
+ }
605
+
606
+ impl core::fmt::Display for e4m3 {
607
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
608
+ if f.alternate() {
609
+ core::fmt::Display::fmt(&self.to_f32(), f)?;
610
+ write!(f, " [0x{:02x}]", self.0)
611
+ } else {
612
+ core::fmt::Display::fmt(&self.to_f32(), f)
613
+ }
614
+ }
615
+ }
616
+
617
+ impl core::fmt::LowerHex for e4m3 {
618
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
619
+ core::fmt::LowerHex::fmt(&self.0, f)
620
+ }
621
+ }
622
+
623
+ impl core::fmt::UpperHex for e4m3 {
624
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
625
+ core::fmt::UpperHex::fmt(&self.0, f)
626
+ }
627
+ }
628
+
629
+ impl core::fmt::Binary for e4m3 {
630
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
631
+ core::fmt::Binary::fmt(&self.0, f)
632
+ }
633
+ }
634
+
635
+ impl core::ops::Add for e4m3 {
636
+ type Output = Self;
637
+ #[inline(always)]
638
+ fn add(self, right: Self) -> Self::Output {
639
+ Self::from_f32(self.to_f32() + right.to_f32())
640
+ }
641
+ }
642
+
643
+ impl core::ops::Sub for e4m3 {
644
+ type Output = Self;
645
+ #[inline(always)]
646
+ fn sub(self, right: Self) -> Self::Output {
647
+ Self::from_f32(self.to_f32() - right.to_f32())
648
+ }
649
+ }
650
+
651
+ impl core::ops::Mul for e4m3 {
652
+ type Output = Self;
653
+ #[inline(always)]
654
+ fn mul(self, right: Self) -> Self::Output {
655
+ Self::from_f32(self.to_f32() * right.to_f32())
656
+ }
657
+ }
658
+
659
+ impl core::ops::Div for e4m3 {
660
+ type Output = Self;
661
+ #[inline(always)]
662
+ fn div(self, right: Self) -> Self::Output {
663
+ Self::from_f32(self.to_f32() / right.to_f32())
664
+ }
665
+ }
666
+
667
+ impl core::ops::Neg for e4m3 {
668
+ type Output = Self;
669
+ #[inline(always)]
670
+ fn neg(self) -> Self::Output {
671
+ Self::from_f32(-self.to_f32())
672
+ }
673
+ }
674
+
675
+ impl core::cmp::PartialOrd for e4m3 {
676
+ #[inline(always)]
677
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
678
+ self.to_f32().partial_cmp(&other.to_f32())
679
+ }
680
+ }
681
+
682
+ // endregion: e4m3 Type
683
+
684
+ // region: e5m2 Type
685
+
686
+ /// 8-bit E5M2 floating-point number (OCP FP8).
687
+ ///
688
+ /// Layout: sign(1) + exponent(5) + mantissa(2), bias=15.
689
+ /// Range: ±57 344, supports infinities. Only 4 mantissa levels per exponent.
690
+ /// 122 of 248 finite values (49.2%) fall in [−1, +1].
691
+ /// High cancellation risk in dot products — consider compensated accumulation.
692
+ ///
693
+ /// # Examples
694
+ ///
695
+ /// ```
696
+ /// use numkong::e5m2;
697
+ ///
698
+ /// let fp8 = e5m2::from_f32(2.5);
699
+ /// let float = fp8.to_f32();
700
+ /// ```
701
+ #[repr(transparent)]
702
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
703
+ pub struct e5m2(pub u8);
704
+
705
+ impl e5m2 {
706
+ /// Positive zero.
707
+ pub const ZERO: Self = e5m2(0x00);
708
+
709
+ /// Positive one.
710
+ pub const ONE: Self = e5m2(0x3C);
711
+
712
+ /// Negative one.
713
+ pub const NEG_ONE: Self = e5m2(0xBC);
714
+ /// Quiet NaN (Not a Number).
715
+ pub const NAN: Self = e5m2(0x7F);
716
+
717
+ /// Convert an `f32` value to `e5m2`. Out-of-range values saturate.
718
+ ///
719
+ /// # Examples
720
+ ///
721
+ /// ```
722
+ /// use numkong::e5m2;
723
+ /// // e5m2 trades mantissa precision for wider range (up to ~57 344).
724
+ /// let fp8 = e5m2::from_f32(1024.0);
725
+ /// assert_eq!(fp8.to_f32(), 1024.0);
726
+ /// ```
727
+ #[inline(always)]
728
+ pub fn from_f32(value: f32) -> Self {
729
+ let mut result: u8 = 0;
730
+ unsafe { nk_f32_to_e5m2(&value, &mut result) };
731
+ e5m2(result)
732
+ }
733
+
734
+ #[inline(always)]
735
+ pub fn to_f32(self) -> f32 {
736
+ let mut result: f32 = 0.0;
737
+ unsafe { nk_e5m2_to_f32(&self.0, &mut result) };
738
+ result
739
+ }
740
+
741
+ /// Returns true if this value is NaN.
742
+ #[inline(always)]
743
+ pub fn is_nan(self) -> bool {
744
+ let exp = (self.0 >> 2) & 0x1F;
745
+ let mant = self.0 & 0x03;
746
+ exp == 0x1F && mant != 0
747
+ }
748
+
749
+ /// Returns true if this value is positive or negative infinity.
750
+ #[inline(always)]
751
+ pub fn is_infinite(self) -> bool {
752
+ let exp = (self.0 >> 2) & 0x1F;
753
+ let mant = self.0 & 0x03;
754
+ exp == 0x1F && mant == 0
755
+ }
756
+
757
+ /// Returns true if this number is neither infinite nor NaN.
758
+ #[inline(always)]
759
+ pub fn is_finite(self) -> bool {
760
+ let exp = (self.0 >> 2) & 0x1F;
761
+ exp != 0x1F
762
+ }
763
+
764
+ /// Returns the absolute value of self.
765
+ #[inline(always)]
766
+ pub fn abs(self) -> Self {
767
+ Self::from_f32(f32_abs_compat(self.to_f32()))
768
+ }
769
+
770
+ /// Returns the largest integer less than or equal to a number.
771
+ ///
772
+ /// This method is only available when the `std` feature is enabled.
773
+ #[cfg(feature = "std")]
774
+ #[inline(always)]
775
+ pub fn floor(self) -> Self {
776
+ Self::from_f32(self.to_f32().floor())
777
+ }
778
+
779
+ /// Returns the smallest integer greater than or equal to a number.
780
+ ///
781
+ /// This method is only available when the `std` feature is enabled.
782
+ #[cfg(feature = "std")]
783
+ #[inline(always)]
784
+ pub fn ceil(self) -> Self {
785
+ Self::from_f32(self.to_f32().ceil())
786
+ }
787
+
788
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
789
+ ///
790
+ /// This method is only available when the `std` feature is enabled.
791
+ #[cfg(feature = "std")]
792
+ #[inline(always)]
793
+ pub fn round(self) -> Self {
794
+ Self::from_f32(self.to_f32().round())
795
+ }
796
+ }
797
+
798
+ impl core::fmt::Debug for e5m2 {
799
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
800
+ write!(f, "e5m2({}, 0x{:02x})", self.to_f32(), self.0)
801
+ }
802
+ }
803
+
804
+ impl core::fmt::Display for e5m2 {
805
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
806
+ if f.alternate() {
807
+ core::fmt::Display::fmt(&self.to_f32(), f)?;
808
+ write!(f, " [0x{:02x}]", self.0)
809
+ } else {
810
+ core::fmt::Display::fmt(&self.to_f32(), f)
811
+ }
812
+ }
813
+ }
814
+
815
+ impl core::fmt::LowerHex for e5m2 {
816
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
817
+ core::fmt::LowerHex::fmt(&self.0, f)
818
+ }
819
+ }
820
+
821
+ impl core::fmt::UpperHex for e5m2 {
822
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
823
+ core::fmt::UpperHex::fmt(&self.0, f)
824
+ }
825
+ }
826
+
827
+ impl core::fmt::Binary for e5m2 {
828
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
829
+ core::fmt::Binary::fmt(&self.0, f)
830
+ }
831
+ }
832
+
833
+ impl core::ops::Add for e5m2 {
834
+ type Output = Self;
835
+ #[inline(always)]
836
+ fn add(self, right: Self) -> Self::Output {
837
+ Self::from_f32(self.to_f32() + right.to_f32())
838
+ }
839
+ }
840
+
841
+ impl core::ops::Sub for e5m2 {
842
+ type Output = Self;
843
+ #[inline(always)]
844
+ fn sub(self, right: Self) -> Self::Output {
845
+ Self::from_f32(self.to_f32() - right.to_f32())
846
+ }
847
+ }
848
+
849
+ impl core::ops::Mul for e5m2 {
850
+ type Output = Self;
851
+ #[inline(always)]
852
+ fn mul(self, right: Self) -> Self::Output {
853
+ Self::from_f32(self.to_f32() * right.to_f32())
854
+ }
855
+ }
856
+
857
+ impl core::ops::Div for e5m2 {
858
+ type Output = Self;
859
+ #[inline(always)]
860
+ fn div(self, right: Self) -> Self::Output {
861
+ Self::from_f32(self.to_f32() / right.to_f32())
862
+ }
863
+ }
864
+
865
+ impl core::ops::Neg for e5m2 {
866
+ type Output = Self;
867
+ #[inline(always)]
868
+ fn neg(self) -> Self::Output {
869
+ Self::from_f32(-self.to_f32())
870
+ }
871
+ }
872
+
873
+ impl core::cmp::PartialOrd for e5m2 {
874
+ #[inline(always)]
875
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
876
+ self.to_f32().partial_cmp(&other.to_f32())
877
+ }
878
+ }
879
+
880
+ // endregion: e5m2 Type
881
+
882
+ // region: e2m3 Type
883
+
884
+ /// 6-bit E2M3 micro-float (padded to 8-bit storage).
885
+ ///
886
+ /// Layout: sign(1) + exponent(2) + mantissa(3), bias=1.
887
+ /// Range: ±7.5, no infinities. Only 64 total codes; 18 (28.1%) fall in [−1, +1].
888
+ /// 72% of codes lie outside [−1, +1] — poor resolution for normalized vectors.
889
+ /// Exact integer dot products via exponent-sum binning (15 bins).
890
+ ///
891
+ /// # Examples
892
+ ///
893
+ /// ```
894
+ /// use numkong::e2m3;
895
+ ///
896
+ /// let fp6 = e2m3::from_f32(2.5);
897
+ /// let float = fp6.to_f32();
898
+ /// ```
899
+ #[repr(transparent)]
900
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
901
+ pub struct e2m3(pub u8);
902
+
903
+ impl e2m3 {
904
+ /// Positive zero.
905
+ pub const ZERO: Self = e2m3(0x00);
906
+
907
+ /// Positive one.
908
+ pub const ONE: Self = e2m3(0x08);
909
+
910
+ /// Negative one.
911
+ pub const NEG_ONE: Self = e2m3(0x28);
912
+
913
+ /// Convert an `f32` value to `e2m3`. Out-of-range values saturate.
914
+ ///
915
+ /// # Examples
916
+ ///
917
+ /// ```
918
+ /// use numkong::e2m3;
919
+ /// // e2m3 covers ±7.5 with 3 mantissa bits — suitable for fine ratios
920
+ /// // in a narrow dynamic range.
921
+ /// let tiny = e2m3::from_f32(1.5);
922
+ /// assert_eq!(tiny.to_f32(), 1.5);
923
+ /// ```
924
+ #[inline(always)]
925
+ pub fn from_f32(value: f32) -> Self {
926
+ let mut result: u8 = 0;
927
+ unsafe { nk_f32_to_e2m3(&value, &mut result) };
928
+ e2m3(result)
929
+ }
930
+
931
+ #[inline(always)]
932
+ pub fn to_f32(self) -> f32 {
933
+ let mut result: f32 = 0.0;
934
+ unsafe { nk_e2m3_to_f32(&self.0, &mut result) };
935
+ result
936
+ }
937
+
938
+ /// Returns true if this value is NaN.
939
+ /// E2M3FN has no infinities - all special values are NaN.
940
+ #[inline(always)]
941
+ pub fn is_nan(self) -> bool {
942
+ false // E2M3FN has no NaN representation
943
+ }
944
+
945
+ /// Returns true if this value is positive or negative infinity.
946
+ /// E2M3FN format has no infinities.
947
+ #[inline(always)]
948
+ pub fn is_infinite(self) -> bool {
949
+ false
950
+ }
951
+
952
+ /// Returns true if this number is neither infinite nor NaN.
953
+ /// Note: E2M3FN format has no infinities or NaN.
954
+ #[inline(always)]
955
+ pub fn is_finite(self) -> bool {
956
+ true
957
+ }
958
+
959
+ /// Returns the absolute value of self.
960
+ #[inline(always)]
961
+ pub fn abs(self) -> Self {
962
+ e2m3(self.0 & 0x1F) // Clear sign bit
963
+ }
964
+
965
+ /// Returns the largest integer less than or equal to a number.
966
+ ///
967
+ /// This method is only available when the `std` feature is enabled.
968
+ #[cfg(feature = "std")]
969
+ #[inline(always)]
970
+ pub fn floor(self) -> Self {
971
+ Self::from_f32(self.to_f32().floor())
972
+ }
973
+
974
+ /// Returns the smallest integer greater than or equal to a number.
975
+ ///
976
+ /// This method is only available when the `std` feature is enabled.
977
+ #[cfg(feature = "std")]
978
+ #[inline(always)]
979
+ pub fn ceil(self) -> Self {
980
+ Self::from_f32(self.to_f32().ceil())
981
+ }
982
+
983
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
984
+ ///
985
+ /// This method is only available when the `std` feature is enabled.
986
+ #[cfg(feature = "std")]
987
+ #[inline(always)]
988
+ pub fn round(self) -> Self {
989
+ Self::from_f32(self.to_f32().round())
990
+ }
991
+ }
992
+
993
+ impl core::fmt::Debug for e2m3 {
994
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
995
+ write!(f, "e2m3({}, 0x{:02x})", self.to_f32(), self.0)
996
+ }
997
+ }
998
+
999
+ impl core::fmt::Display for e2m3 {
1000
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1001
+ if f.alternate() {
1002
+ core::fmt::Display::fmt(&self.to_f32(), f)?;
1003
+ write!(f, " [0x{:02x}]", self.0)
1004
+ } else {
1005
+ core::fmt::Display::fmt(&self.to_f32(), f)
1006
+ }
1007
+ }
1008
+ }
1009
+
1010
+ impl core::fmt::LowerHex for e2m3 {
1011
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1012
+ core::fmt::LowerHex::fmt(&self.0, f)
1013
+ }
1014
+ }
1015
+
1016
+ impl core::fmt::UpperHex for e2m3 {
1017
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1018
+ core::fmt::UpperHex::fmt(&self.0, f)
1019
+ }
1020
+ }
1021
+
1022
+ impl core::fmt::Binary for e2m3 {
1023
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1024
+ core::fmt::Binary::fmt(&self.0, f)
1025
+ }
1026
+ }
1027
+
1028
+ impl core::ops::Add for e2m3 {
1029
+ type Output = Self;
1030
+ #[inline(always)]
1031
+ fn add(self, right: Self) -> Self::Output {
1032
+ Self::from_f32(self.to_f32() + right.to_f32())
1033
+ }
1034
+ }
1035
+
1036
+ impl core::ops::Sub for e2m3 {
1037
+ type Output = Self;
1038
+ #[inline(always)]
1039
+ fn sub(self, right: Self) -> Self::Output {
1040
+ Self::from_f32(self.to_f32() - right.to_f32())
1041
+ }
1042
+ }
1043
+
1044
+ impl core::ops::Mul for e2m3 {
1045
+ type Output = Self;
1046
+ #[inline(always)]
1047
+ fn mul(self, right: Self) -> Self::Output {
1048
+ Self::from_f32(self.to_f32() * right.to_f32())
1049
+ }
1050
+ }
1051
+
1052
+ impl core::ops::Div for e2m3 {
1053
+ type Output = Self;
1054
+ #[inline(always)]
1055
+ fn div(self, right: Self) -> Self::Output {
1056
+ Self::from_f32(self.to_f32() / right.to_f32())
1057
+ }
1058
+ }
1059
+
1060
+ impl core::ops::Neg for e2m3 {
1061
+ type Output = Self;
1062
+ #[inline(always)]
1063
+ fn neg(self) -> Self::Output {
1064
+ Self::from_f32(-self.to_f32())
1065
+ }
1066
+ }
1067
+
1068
+ impl core::cmp::PartialOrd for e2m3 {
1069
+ #[inline(always)]
1070
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1071
+ self.to_f32().partial_cmp(&other.to_f32())
1072
+ }
1073
+ }
1074
+
1075
+ // endregion: e2m3 Type
1076
+
1077
+ // region: e3m2 Type
1078
+
1079
+ /// 6-bit E3M2 micro-float (padded to 8-bit storage).
1080
+ ///
1081
+ /// Layout: sign(1) + exponent(3) + mantissa(2), bias=3.
1082
+ /// Range: ±28, supports infinities. Only 64 total codes; 26 (40.6%) fall in [−1, +1].
1083
+ /// Exact integer dot products via exponent-sum binning (15 bins).
1084
+ ///
1085
+ /// # Examples
1086
+ ///
1087
+ /// ```
1088
+ /// use numkong::e3m2;
1089
+ ///
1090
+ /// let fp6 = e3m2::from_f32(2.5);
1091
+ /// let float = fp6.to_f32();
1092
+ /// ```
1093
+ #[repr(transparent)]
1094
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
1095
+ pub struct e3m2(pub u8);
1096
+
1097
+ impl e3m2 {
1098
+ /// Positive zero.
1099
+ pub const ZERO: Self = e3m2(0x00);
1100
+
1101
+ /// Positive one.
1102
+ pub const ONE: Self = e3m2(0x0C);
1103
+
1104
+ /// Negative one.
1105
+ pub const NEG_ONE: Self = e3m2(0x2C);
1106
+
1107
+ /// Convert an `f32` value to `e3m2`. Out-of-range values saturate.
1108
+ ///
1109
+ /// # Examples
1110
+ ///
1111
+ /// ```
1112
+ /// use numkong::e3m2;
1113
+ /// // e3m2 trades mantissa bits for a larger range (±28).
1114
+ /// let tiny = e3m2::from_f32(16.0);
1115
+ /// assert_eq!(tiny.to_f32(), 16.0);
1116
+ /// ```
1117
+ #[inline(always)]
1118
+ pub fn from_f32(value: f32) -> Self {
1119
+ let mut result: u8 = 0;
1120
+ unsafe { nk_f32_to_e3m2(&value, &mut result) };
1121
+ e3m2(result)
1122
+ }
1123
+
1124
+ #[inline(always)]
1125
+ pub fn to_f32(self) -> f32 {
1126
+ let mut result: f32 = 0.0;
1127
+ unsafe { nk_e3m2_to_f32(&self.0, &mut result) };
1128
+ result
1129
+ }
1130
+
1131
+ /// Returns true if this value is NaN.
1132
+ #[inline(always)]
1133
+ pub fn is_nan(self) -> bool {
1134
+ let exp = (self.0 >> 2) & 0x07;
1135
+ let mant = self.0 & 0x03;
1136
+ exp == 0x07 && mant != 0
1137
+ }
1138
+
1139
+ /// Returns true if this value is positive or negative infinity.
1140
+ #[inline(always)]
1141
+ pub fn is_infinite(self) -> bool {
1142
+ let exp = (self.0 >> 2) & 0x07;
1143
+ let mant = self.0 & 0x03;
1144
+ exp == 0x07 && mant == 0
1145
+ }
1146
+
1147
+ /// Returns true if this number is neither infinite nor NaN.
1148
+ #[inline(always)]
1149
+ pub fn is_finite(self) -> bool {
1150
+ let exp = (self.0 >> 2) & 0x07;
1151
+ exp != 0x07
1152
+ }
1153
+
1154
+ /// Returns the absolute value of self.
1155
+ #[inline(always)]
1156
+ pub fn abs(self) -> Self {
1157
+ e3m2(self.0 & 0x1F) // Clear sign bit
1158
+ }
1159
+
1160
+ /// Returns the largest integer less than or equal to a number.
1161
+ ///
1162
+ /// This method is only available when the `std` feature is enabled.
1163
+ #[cfg(feature = "std")]
1164
+ #[inline(always)]
1165
+ pub fn floor(self) -> Self {
1166
+ Self::from_f32(self.to_f32().floor())
1167
+ }
1168
+
1169
+ /// Returns the smallest integer greater than or equal to a number.
1170
+ ///
1171
+ /// This method is only available when the `std` feature is enabled.
1172
+ #[cfg(feature = "std")]
1173
+ #[inline(always)]
1174
+ pub fn ceil(self) -> Self {
1175
+ Self::from_f32(self.to_f32().ceil())
1176
+ }
1177
+
1178
+ /// Returns the nearest integer to a number. Round half-way cases away from 0.0.
1179
+ ///
1180
+ /// This method is only available when the `std` feature is enabled.
1181
+ #[cfg(feature = "std")]
1182
+ #[inline(always)]
1183
+ pub fn round(self) -> Self {
1184
+ Self::from_f32(self.to_f32().round())
1185
+ }
1186
+ }
1187
+
1188
+ impl core::fmt::Debug for e3m2 {
1189
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1190
+ write!(f, "e3m2({}, 0x{:02x})", self.to_f32(), self.0)
1191
+ }
1192
+ }
1193
+
1194
+ impl core::fmt::Display for e3m2 {
1195
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1196
+ if f.alternate() {
1197
+ core::fmt::Display::fmt(&self.to_f32(), f)?;
1198
+ write!(f, " [0x{:02x}]", self.0)
1199
+ } else {
1200
+ core::fmt::Display::fmt(&self.to_f32(), f)
1201
+ }
1202
+ }
1203
+ }
1204
+
1205
+ impl core::fmt::LowerHex for e3m2 {
1206
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1207
+ core::fmt::LowerHex::fmt(&self.0, f)
1208
+ }
1209
+ }
1210
+
1211
+ impl core::fmt::UpperHex for e3m2 {
1212
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1213
+ core::fmt::UpperHex::fmt(&self.0, f)
1214
+ }
1215
+ }
1216
+
1217
+ impl core::fmt::Binary for e3m2 {
1218
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1219
+ core::fmt::Binary::fmt(&self.0, f)
1220
+ }
1221
+ }
1222
+
1223
+ impl core::ops::Add for e3m2 {
1224
+ type Output = Self;
1225
+ #[inline(always)]
1226
+ fn add(self, right: Self) -> Self::Output {
1227
+ Self::from_f32(self.to_f32() + right.to_f32())
1228
+ }
1229
+ }
1230
+
1231
+ impl core::ops::Sub for e3m2 {
1232
+ type Output = Self;
1233
+ #[inline(always)]
1234
+ fn sub(self, right: Self) -> Self::Output {
1235
+ Self::from_f32(self.to_f32() - right.to_f32())
1236
+ }
1237
+ }
1238
+
1239
+ impl core::ops::Mul for e3m2 {
1240
+ type Output = Self;
1241
+ #[inline(always)]
1242
+ fn mul(self, right: Self) -> Self::Output {
1243
+ Self::from_f32(self.to_f32() * right.to_f32())
1244
+ }
1245
+ }
1246
+
1247
+ impl core::ops::Div for e3m2 {
1248
+ type Output = Self;
1249
+ #[inline(always)]
1250
+ fn div(self, right: Self) -> Self::Output {
1251
+ Self::from_f32(self.to_f32() / right.to_f32())
1252
+ }
1253
+ }
1254
+
1255
+ impl core::ops::Neg for e3m2 {
1256
+ type Output = Self;
1257
+ #[inline(always)]
1258
+ fn neg(self) -> Self::Output {
1259
+ Self::from_f32(-self.to_f32())
1260
+ }
1261
+ }
1262
+
1263
+ impl core::cmp::PartialOrd for e3m2 {
1264
+ #[inline(always)]
1265
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1266
+ self.to_f32().partial_cmp(&other.to_f32())
1267
+ }
1268
+ }
1269
+
1270
+ // endregion: e3m2 Type
1271
+
1272
+ // region: From<f32> Conversions
1273
+
1274
+ impl From<f32> for f16 {
1275
+ #[inline(always)]
1276
+ fn from(value: f32) -> Self {
1277
+ Self::from_f32(value)
1278
+ }
1279
+ }
1280
+ impl From<f16> for f32 {
1281
+ #[inline(always)]
1282
+ fn from(value: f16) -> Self {
1283
+ value.to_f32()
1284
+ }
1285
+ }
1286
+
1287
+ impl From<f32> for bf16 {
1288
+ #[inline(always)]
1289
+ fn from(value: f32) -> Self {
1290
+ Self::from_f32(value)
1291
+ }
1292
+ }
1293
+ impl From<bf16> for f32 {
1294
+ #[inline(always)]
1295
+ fn from(value: bf16) -> Self {
1296
+ value.to_f32()
1297
+ }
1298
+ }
1299
+
1300
+ impl From<f32> for e4m3 {
1301
+ #[inline(always)]
1302
+ fn from(value: f32) -> Self {
1303
+ Self::from_f32(value)
1304
+ }
1305
+ }
1306
+ impl From<e4m3> for f32 {
1307
+ #[inline(always)]
1308
+ fn from(value: e4m3) -> Self {
1309
+ value.to_f32()
1310
+ }
1311
+ }
1312
+
1313
+ impl From<f32> for e5m2 {
1314
+ #[inline(always)]
1315
+ fn from(value: f32) -> Self {
1316
+ Self::from_f32(value)
1317
+ }
1318
+ }
1319
+ impl From<e5m2> for f32 {
1320
+ #[inline(always)]
1321
+ fn from(value: e5m2) -> Self {
1322
+ value.to_f32()
1323
+ }
1324
+ }
1325
+
1326
+ impl From<f32> for e2m3 {
1327
+ #[inline(always)]
1328
+ fn from(value: f32) -> Self {
1329
+ Self::from_f32(value)
1330
+ }
1331
+ }
1332
+ impl From<e2m3> for f32 {
1333
+ #[inline(always)]
1334
+ fn from(value: e2m3) -> Self {
1335
+ value.to_f32()
1336
+ }
1337
+ }
1338
+
1339
+ impl From<f32> for e3m2 {
1340
+ #[inline(always)]
1341
+ fn from(value: f32) -> Self {
1342
+ Self::from_f32(value)
1343
+ }
1344
+ }
1345
+ impl From<e3m2> for f32 {
1346
+ #[inline(always)]
1347
+ fn from(value: e3m2) -> Self {
1348
+ value.to_f32()
1349
+ }
1350
+ }
1351
+
1352
+ // endregion: From<f32> Conversions
1353
+
1354
+ // region: u1x8 Type
1355
+
1356
+ /// Packed 8-bit bit-vector (8 booleans in one byte).
1357
+ ///
1358
+ /// Layout: 8 bits packed into one byte, LSB = dimension 0.
1359
+ /// Used for Hamming distance and Jaccard similarity via popcount.
1360
+ #[repr(transparent)]
1361
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
1362
+ pub struct u1x8(pub u8);
1363
+
1364
+ impl core::fmt::Debug for u1x8 {
1365
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1366
+ write!(f, "u1x8(0b{:08b}, 0x{:02x})", self.0, self.0)
1367
+ }
1368
+ }
1369
+
1370
+ impl core::fmt::Display for u1x8 {
1371
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1372
+ if f.alternate() {
1373
+ write!(f, "0b{:08b} [0x{:02x}]", self.0, self.0)
1374
+ } else {
1375
+ write!(f, "0b{:08b}", self.0)
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ impl core::fmt::LowerHex for u1x8 {
1381
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1382
+ core::fmt::LowerHex::fmt(&self.0, f)
1383
+ }
1384
+ }
1385
+
1386
+ impl core::fmt::UpperHex for u1x8 {
1387
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1388
+ core::fmt::UpperHex::fmt(&self.0, f)
1389
+ }
1390
+ }
1391
+
1392
+ impl core::fmt::Binary for u1x8 {
1393
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1394
+ core::fmt::Binary::fmt(&self.0, f)
1395
+ }
1396
+ }
1397
+
1398
+ impl u1x8 {
1399
+ /// Create from raw packed bits.
1400
+ #[inline(always)]
1401
+ pub const fn new(bits: u8) -> Self {
1402
+ u1x8(bits)
1403
+ }
1404
+
1405
+ /// Get the raw packed bits.
1406
+ #[inline(always)]
1407
+ pub const fn bits(self) -> u8 {
1408
+ self.0
1409
+ }
1410
+
1411
+ /// Construct from 8 booleans (b0 = LSB, b7 = MSB).
1412
+ #[inline(always)]
1413
+ pub const fn from_bools(
1414
+ b0: bool,
1415
+ b1: bool,
1416
+ b2: bool,
1417
+ b3: bool,
1418
+ b4: bool,
1419
+ b5: bool,
1420
+ b6: bool,
1421
+ b7: bool,
1422
+ ) -> Self {
1423
+ u1x8(
1424
+ (b0 as u8)
1425
+ | ((b1 as u8) << 1)
1426
+ | ((b2 as u8) << 2)
1427
+ | ((b3 as u8) << 3)
1428
+ | ((b4 as u8) << 4)
1429
+ | ((b5 as u8) << 5)
1430
+ | ((b6 as u8) << 6)
1431
+ | ((b7 as u8) << 7),
1432
+ )
1433
+ }
1434
+
1435
+ /// Extract to 8 booleans (b0 = LSB, b7 = MSB).
1436
+ #[inline(always)]
1437
+ pub const fn to_bools(self) -> (bool, bool, bool, bool, bool, bool, bool, bool) {
1438
+ (
1439
+ (self.0 & 1) != 0,
1440
+ (self.0 & 2) != 0,
1441
+ (self.0 & 4) != 0,
1442
+ (self.0 & 8) != 0,
1443
+ (self.0 & 16) != 0,
1444
+ (self.0 & 32) != 0,
1445
+ (self.0 & 64) != 0,
1446
+ (self.0 & 128) != 0,
1447
+ )
1448
+ }
1449
+ }
1450
+
1451
+ impl From<(bool, bool, bool, bool, bool, bool, bool, bool)> for u1x8 {
1452
+ #[inline(always)]
1453
+ fn from(b: (bool, bool, bool, bool, bool, bool, bool, bool)) -> Self {
1454
+ u1x8::from_bools(b.0, b.1, b.2, b.3, b.4, b.5, b.6, b.7)
1455
+ }
1456
+ }
1457
+
1458
+ impl From<u1x8> for (bool, bool, bool, bool, bool, bool, bool, bool) {
1459
+ #[inline(always)]
1460
+ fn from(v: u1x8) -> Self {
1461
+ v.to_bools()
1462
+ }
1463
+ }
1464
+
1465
+ // endregion: u1x8 Type
1466
+
1467
+ // region: u4x2 Type
1468
+
1469
+ /// Packed 4-bit unsigned integer pair (2 × u4 in one byte).
1470
+ ///
1471
+ /// Layout: low nibble = first element, high nibble = second element.
1472
+ /// Range per element: [0, 15]. Elements zero-extended to u8 for arithmetic.
1473
+ #[repr(transparent)]
1474
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
1475
+ pub struct u4x2(pub u8);
1476
+
1477
+ impl core::fmt::Debug for u4x2 {
1478
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1479
+ let (low, high) = self.to_u8s();
1480
+ write!(f, "u4x2({}, {}, 0x{:02x})", low, high, self.0)
1481
+ }
1482
+ }
1483
+
1484
+ impl core::fmt::Display for u4x2 {
1485
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1486
+ let (low, high) = self.to_u8s();
1487
+ if f.alternate() {
1488
+ write!(f, "({}, {}) [0x{:02x}]", low, high, self.0)
1489
+ } else {
1490
+ write!(f, "({}, {})", low, high)
1491
+ }
1492
+ }
1493
+ }
1494
+
1495
+ impl core::fmt::LowerHex for u4x2 {
1496
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1497
+ core::fmt::LowerHex::fmt(&self.0, f)
1498
+ }
1499
+ }
1500
+
1501
+ impl core::fmt::UpperHex for u4x2 {
1502
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1503
+ core::fmt::UpperHex::fmt(&self.0, f)
1504
+ }
1505
+ }
1506
+
1507
+ impl core::fmt::Binary for u4x2 {
1508
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1509
+ core::fmt::Binary::fmt(&self.0, f)
1510
+ }
1511
+ }
1512
+
1513
+ impl u4x2 {
1514
+ /// Create from raw packed byte.
1515
+ #[inline(always)]
1516
+ pub const fn new(packed: u8) -> Self {
1517
+ u4x2(packed)
1518
+ }
1519
+
1520
+ /// Get the raw packed byte.
1521
+ #[inline(always)]
1522
+ pub const fn packed(self) -> u8 {
1523
+ self.0
1524
+ }
1525
+
1526
+ /// Construct from two u8 values with saturation to 0..15.
1527
+ #[inline(always)]
1528
+ pub const fn from_u8s(low: u8, high: u8) -> Self {
1529
+ let low_sat = if low > 15 { 15 } else { low };
1530
+ let high_sat = if high > 15 { 15 } else { high };
1531
+ u4x2(low_sat | (high_sat << 4))
1532
+ }
1533
+
1534
+ /// Extract to two u8 values (0..15 each).
1535
+ #[inline(always)]
1536
+ pub const fn to_u8s(self) -> (u8, u8) {
1537
+ (self.0 & 0x0F, self.0 >> 4)
1538
+ }
1539
+ }
1540
+
1541
+ impl From<(u8, u8)> for u4x2 {
1542
+ #[inline(always)]
1543
+ fn from(v: (u8, u8)) -> Self {
1544
+ u4x2::from_u8s(v.0, v.1)
1545
+ }
1546
+ }
1547
+
1548
+ impl From<u4x2> for (u8, u8) {
1549
+ #[inline(always)]
1550
+ fn from(v: u4x2) -> Self {
1551
+ v.to_u8s()
1552
+ }
1553
+ }
1554
+
1555
+ // endregion: u4x2 Type
1556
+
1557
+ // region: i4x2 Type
1558
+
1559
+ /// Packed 4-bit signed integer pair (2 × i4 in one byte).
1560
+ ///
1561
+ /// Layout: low nibble = first element, high nibble = second element (two's complement).
1562
+ /// Range per element: [−8, +7]. Elements sign-extended to i8 for arithmetic.
1563
+ #[repr(transparent)]
1564
+ #[derive(Clone, Copy, PartialEq, Eq, Default)]
1565
+ pub struct i4x2(pub u8);
1566
+
1567
+ impl core::fmt::Debug for i4x2 {
1568
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1569
+ let (low, high) = self.to_i8s();
1570
+ write!(f, "i4x2({}, {}, 0x{:02x})", low, high, self.0)
1571
+ }
1572
+ }
1573
+
1574
+ impl core::fmt::Display for i4x2 {
1575
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1576
+ let (low, high) = self.to_i8s();
1577
+ if f.alternate() {
1578
+ write!(f, "({}, {}) [0x{:02x}]", low, high, self.0)
1579
+ } else {
1580
+ write!(f, "({}, {})", low, high)
1581
+ }
1582
+ }
1583
+ }
1584
+
1585
+ impl core::fmt::LowerHex for i4x2 {
1586
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1587
+ core::fmt::LowerHex::fmt(&self.0, f)
1588
+ }
1589
+ }
1590
+
1591
+ impl core::fmt::UpperHex for i4x2 {
1592
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1593
+ core::fmt::UpperHex::fmt(&self.0, f)
1594
+ }
1595
+ }
1596
+
1597
+ impl core::fmt::Binary for i4x2 {
1598
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1599
+ core::fmt::Binary::fmt(&self.0, f)
1600
+ }
1601
+ }
1602
+
1603
+ impl i4x2 {
1604
+ /// Create from raw packed byte.
1605
+ #[inline(always)]
1606
+ pub const fn new(packed: u8) -> Self {
1607
+ i4x2(packed)
1608
+ }
1609
+
1610
+ /// Get the raw packed byte.
1611
+ #[inline(always)]
1612
+ pub const fn packed(self) -> u8 {
1613
+ self.0
1614
+ }
1615
+
1616
+ /// Construct from two i8 values with saturation to -8..7.
1617
+ #[inline(always)]
1618
+ pub const fn from_i8s(low: i8, high: i8) -> Self {
1619
+ let low_sat = if low < -8 {
1620
+ -8
1621
+ } else if low > 7 {
1622
+ 7
1623
+ } else {
1624
+ low
1625
+ };
1626
+ let high_sat = if high < -8 {
1627
+ -8
1628
+ } else if high > 7 {
1629
+ 7
1630
+ } else {
1631
+ high
1632
+ };
1633
+ i4x2(((low_sat as u8) & 0x0F) | (((high_sat as u8) & 0x0F) << 4))
1634
+ }
1635
+
1636
+ /// Extract to two i8 values (sign-extended from 4 bits).
1637
+ #[inline(always)]
1638
+ pub const fn to_i8s(self) -> (i8, i8) {
1639
+ let low = (self.0 & 0x0F) as i8;
1640
+ let high = ((self.0 >> 4) & 0x0F) as i8;
1641
+ // Sign extend from 4 bits: if bit 3 is set, fill upper bits with 1s
1642
+ let low = if low & 0x08 != 0 {
1643
+ low | (!0x0Fi8)
1644
+ } else {
1645
+ low
1646
+ };
1647
+ let high = if high & 0x08 != 0 {
1648
+ high | (!0x0Fi8)
1649
+ } else {
1650
+ high
1651
+ };
1652
+ (low, high)
1653
+ }
1654
+ }
1655
+
1656
+ impl From<(i8, i8)> for i4x2 {
1657
+ #[inline(always)]
1658
+ fn from(v: (i8, i8)) -> Self {
1659
+ i4x2::from_i8s(v.0, v.1)
1660
+ }
1661
+ }
1662
+
1663
+ impl From<i4x2> for (i8, i8) {
1664
+ #[inline(always)]
1665
+ fn from(v: i4x2) -> Self {
1666
+ v.to_i8s()
1667
+ }
1668
+ }
1669
+
1670
+ // endregion: i4x2 Type
1671
+
1672
+ // region: StorageElement + NumberLike + FloatLike Traits
1673
+
1674
+ /// Minimal trait for types that can be stored in vectors and tensors.
1675
+ ///
1676
+ /// Provides identity elements (`zero`, `one`) and sub-byte packing metadata.
1677
+ /// Does not require numeric conversion — use [`NumberLike`] for that.
1678
+ pub trait StorageElement: Sized + Copy + Clone + Default {
1679
+ /// The additive identity.
1680
+ fn zero() -> Self;
1681
+ /// The multiplicative identity.
1682
+ fn one() -> Self;
1683
+ /// Number of logical dimensions packed into one storage value.
1684
+ /// Default: 1 for all normal types. Override for sub-byte packed types.
1685
+ fn dimensions_per_value() -> usize {
1686
+ 1
1687
+ }
1688
+ }
1689
+
1690
+ /// Trait for types that support conversion to/from f32 with classification and constants.
1691
+ ///
1692
+ /// Provides a unified interface for all numeric types used in NumKong,
1693
+ /// including half-precision floats, mini-floats, integers, and packed types.
1694
+ pub trait NumberLike: StorageElement {
1695
+ /// Convert from f32 to this type.
1696
+ fn from_f32(v: f32) -> Self;
1697
+ /// Convert from this type to f32.
1698
+ fn to_f32(self) -> f32;
1699
+ /// Convert from f64 to this type (default: via f32 roundtrip).
1700
+ fn from_f64(v: f64) -> Self {
1701
+ Self::from_f32(v as f32)
1702
+ }
1703
+ /// Convert from this type to f64 (default: via f32 roundtrip).
1704
+ fn to_f64(self) -> f64 {
1705
+ self.to_f32() as f64
1706
+ }
1707
+
1708
+ fn abs(self) -> Self {
1709
+ Self::from_f32(f32_abs_compat(self.to_f32()))
1710
+ }
1711
+ fn is_nan(self) -> bool {
1712
+ self.to_f32().is_nan()
1713
+ }
1714
+ fn is_finite(self) -> bool {
1715
+ self.to_f32().is_finite()
1716
+ }
1717
+ fn is_infinite(self) -> bool {
1718
+ self.to_f32().is_infinite()
1719
+ }
1720
+
1721
+ fn has_infinity() -> bool {
1722
+ false
1723
+ }
1724
+ fn has_nan() -> bool {
1725
+ false
1726
+ }
1727
+ fn has_subnormals() -> bool {
1728
+ false
1729
+ }
1730
+ fn max_value() -> f32 {
1731
+ f32::MAX
1732
+ }
1733
+ fn min_positive() -> f32 {
1734
+ f32::MIN_POSITIVE
1735
+ }
1736
+ }
1737
+
1738
+ /// Backward-compatible alias: any type implementing [`NumberLike`] also implements `FloatLike`.
1739
+ pub trait FloatLike: NumberLike {}
1740
+ impl<Scalar: NumberLike> FloatLike for Scalar {}
1741
+
1742
+ impl StorageElement for f32 {
1743
+ fn zero() -> Self {
1744
+ 0.0
1745
+ }
1746
+ fn one() -> Self {
1747
+ 1.0
1748
+ }
1749
+ }
1750
+
1751
+ impl NumberLike for f32 {
1752
+ fn from_f32(v: f32) -> Self {
1753
+ v
1754
+ }
1755
+ fn to_f32(self) -> f32 {
1756
+ self
1757
+ }
1758
+ fn from_f64(v: f64) -> Self {
1759
+ v as f32
1760
+ }
1761
+ fn to_f64(self) -> f64 {
1762
+ self as f64
1763
+ }
1764
+ fn abs(self) -> Self {
1765
+ f32_abs_compat(self)
1766
+ }
1767
+ fn is_nan(self) -> bool {
1768
+ f32::is_nan(self)
1769
+ }
1770
+ fn is_finite(self) -> bool {
1771
+ f32::is_finite(self)
1772
+ }
1773
+ fn is_infinite(self) -> bool {
1774
+ f32::is_infinite(self)
1775
+ }
1776
+ fn has_infinity() -> bool {
1777
+ true
1778
+ }
1779
+ fn has_nan() -> bool {
1780
+ true
1781
+ }
1782
+ fn has_subnormals() -> bool {
1783
+ true
1784
+ }
1785
+ fn max_value() -> f32 {
1786
+ f32::MAX
1787
+ }
1788
+ fn min_positive() -> f32 {
1789
+ f32::MIN_POSITIVE
1790
+ }
1791
+ }
1792
+
1793
+ impl StorageElement for f64 {
1794
+ fn zero() -> Self {
1795
+ 0.0
1796
+ }
1797
+ fn one() -> Self {
1798
+ 1.0
1799
+ }
1800
+ }
1801
+
1802
+ impl NumberLike for f64 {
1803
+ fn from_f32(v: f32) -> Self {
1804
+ v as f64
1805
+ }
1806
+ fn to_f32(self) -> f32 {
1807
+ self as f32
1808
+ }
1809
+ fn from_f64(v: f64) -> Self {
1810
+ v
1811
+ }
1812
+ fn to_f64(self) -> f64 {
1813
+ self
1814
+ }
1815
+ fn abs(self) -> Self {
1816
+ f64::from_bits(self.to_bits() & 0x7FFF_FFFF_FFFF_FFFF)
1817
+ }
1818
+ fn is_nan(self) -> bool {
1819
+ f64::is_nan(self)
1820
+ }
1821
+ fn is_finite(self) -> bool {
1822
+ f64::is_finite(self)
1823
+ }
1824
+ fn is_infinite(self) -> bool {
1825
+ f64::is_infinite(self)
1826
+ }
1827
+ fn has_infinity() -> bool {
1828
+ true
1829
+ }
1830
+ fn has_nan() -> bool {
1831
+ true
1832
+ }
1833
+ fn has_subnormals() -> bool {
1834
+ true
1835
+ }
1836
+ fn max_value() -> f32 {
1837
+ f32::MAX
1838
+ }
1839
+ fn min_positive() -> f32 {
1840
+ f32::MIN_POSITIVE
1841
+ }
1842
+ }
1843
+
1844
+ impl StorageElement for f16 {
1845
+ fn zero() -> Self {
1846
+ f16(0)
1847
+ }
1848
+ fn one() -> Self {
1849
+ f16::from_f32(1.0)
1850
+ }
1851
+ }
1852
+
1853
+ impl NumberLike for f16 {
1854
+ fn from_f32(v: f32) -> Self {
1855
+ f16::from_f32(v)
1856
+ }
1857
+ fn to_f32(self) -> f32 {
1858
+ self.to_f32()
1859
+ }
1860
+ fn abs(self) -> Self {
1861
+ self.abs()
1862
+ }
1863
+ fn is_nan(self) -> bool {
1864
+ self.is_nan()
1865
+ }
1866
+ fn is_finite(self) -> bool {
1867
+ self.is_finite()
1868
+ }
1869
+ fn is_infinite(self) -> bool {
1870
+ self.is_infinite()
1871
+ }
1872
+ fn has_infinity() -> bool {
1873
+ true
1874
+ }
1875
+ fn has_nan() -> bool {
1876
+ true
1877
+ }
1878
+ fn has_subnormals() -> bool {
1879
+ true
1880
+ }
1881
+ fn max_value() -> f32 {
1882
+ 65504.0
1883
+ }
1884
+ fn min_positive() -> f32 {
1885
+ 6.1e-5
1886
+ }
1887
+ }
1888
+
1889
+ impl StorageElement for bf16 {
1890
+ fn zero() -> Self {
1891
+ bf16(0)
1892
+ }
1893
+ fn one() -> Self {
1894
+ bf16::from_f32(1.0)
1895
+ }
1896
+ }
1897
+
1898
+ impl NumberLike for bf16 {
1899
+ fn from_f32(v: f32) -> Self {
1900
+ bf16::from_f32(v)
1901
+ }
1902
+ fn to_f32(self) -> f32 {
1903
+ self.to_f32()
1904
+ }
1905
+ fn abs(self) -> Self {
1906
+ self.abs()
1907
+ }
1908
+ fn is_nan(self) -> bool {
1909
+ self.is_nan()
1910
+ }
1911
+ fn is_finite(self) -> bool {
1912
+ self.is_finite()
1913
+ }
1914
+ fn is_infinite(self) -> bool {
1915
+ self.is_infinite()
1916
+ }
1917
+ fn has_infinity() -> bool {
1918
+ true
1919
+ }
1920
+ fn has_nan() -> bool {
1921
+ true
1922
+ }
1923
+ fn has_subnormals() -> bool {
1924
+ true
1925
+ }
1926
+ fn max_value() -> f32 {
1927
+ 3.4e38
1928
+ }
1929
+ fn min_positive() -> f32 {
1930
+ 1.2e-38
1931
+ }
1932
+ }
1933
+
1934
+ impl StorageElement for e4m3 {
1935
+ fn zero() -> Self {
1936
+ e4m3(0)
1937
+ }
1938
+ fn one() -> Self {
1939
+ e4m3::from_f32(1.0)
1940
+ }
1941
+ }
1942
+
1943
+ impl NumberLike for e4m3 {
1944
+ fn from_f32(v: f32) -> Self {
1945
+ e4m3::from_f32(v)
1946
+ }
1947
+ fn to_f32(self) -> f32 {
1948
+ self.to_f32()
1949
+ }
1950
+ fn abs(self) -> Self {
1951
+ self.abs()
1952
+ }
1953
+ fn is_nan(self) -> bool {
1954
+ self.is_nan()
1955
+ }
1956
+ fn is_finite(self) -> bool {
1957
+ self.is_finite()
1958
+ }
1959
+ fn is_infinite(self) -> bool {
1960
+ self.is_infinite()
1961
+ }
1962
+ fn has_nan() -> bool {
1963
+ true
1964
+ }
1965
+ fn has_subnormals() -> bool {
1966
+ true
1967
+ }
1968
+ fn max_value() -> f32 {
1969
+ 448.0
1970
+ }
1971
+ fn min_positive() -> f32 {
1972
+ 0.001953125
1973
+ }
1974
+ }
1975
+
1976
+ impl StorageElement for e5m2 {
1977
+ fn zero() -> Self {
1978
+ e5m2(0)
1979
+ }
1980
+ fn one() -> Self {
1981
+ e5m2::from_f32(1.0)
1982
+ }
1983
+ }
1984
+
1985
+ impl NumberLike for e5m2 {
1986
+ fn from_f32(v: f32) -> Self {
1987
+ e5m2::from_f32(v)
1988
+ }
1989
+ fn to_f32(self) -> f32 {
1990
+ self.to_f32()
1991
+ }
1992
+ fn abs(self) -> Self {
1993
+ self.abs()
1994
+ }
1995
+ fn is_nan(self) -> bool {
1996
+ self.is_nan()
1997
+ }
1998
+ fn is_finite(self) -> bool {
1999
+ self.is_finite()
2000
+ }
2001
+ fn is_infinite(self) -> bool {
2002
+ self.is_infinite()
2003
+ }
2004
+ fn has_infinity() -> bool {
2005
+ true
2006
+ }
2007
+ fn has_nan() -> bool {
2008
+ true
2009
+ }
2010
+ fn has_subnormals() -> bool {
2011
+ true
2012
+ }
2013
+ fn max_value() -> f32 {
2014
+ 57344.0
2015
+ }
2016
+ fn min_positive() -> f32 {
2017
+ 6.103_515_6e-5
2018
+ }
2019
+ }
2020
+
2021
+ impl StorageElement for e2m3 {
2022
+ fn zero() -> Self {
2023
+ e2m3(0)
2024
+ }
2025
+ fn one() -> Self {
2026
+ e2m3::from_f32(1.0)
2027
+ }
2028
+ }
2029
+
2030
+ impl NumberLike for e2m3 {
2031
+ fn from_f32(v: f32) -> Self {
2032
+ e2m3::from_f32(v)
2033
+ }
2034
+ fn to_f32(self) -> f32 {
2035
+ self.to_f32()
2036
+ }
2037
+ fn abs(self) -> Self {
2038
+ self.abs()
2039
+ }
2040
+ fn is_nan(self) -> bool {
2041
+ self.is_nan()
2042
+ }
2043
+ fn is_finite(self) -> bool {
2044
+ self.is_finite()
2045
+ }
2046
+ fn is_infinite(self) -> bool {
2047
+ self.is_infinite()
2048
+ }
2049
+ fn has_subnormals() -> bool {
2050
+ true
2051
+ }
2052
+ fn max_value() -> f32 {
2053
+ 7.5
2054
+ }
2055
+ fn min_positive() -> f32 {
2056
+ 0.0625
2057
+ }
2058
+ }
2059
+
2060
+ impl StorageElement for e3m2 {
2061
+ fn zero() -> Self {
2062
+ e3m2(0)
2063
+ }
2064
+ fn one() -> Self {
2065
+ e3m2::from_f32(1.0)
2066
+ }
2067
+ }
2068
+
2069
+ impl NumberLike for e3m2 {
2070
+ fn from_f32(v: f32) -> Self {
2071
+ e3m2::from_f32(v)
2072
+ }
2073
+ fn to_f32(self) -> f32 {
2074
+ self.to_f32()
2075
+ }
2076
+ fn abs(self) -> Self {
2077
+ self.abs()
2078
+ }
2079
+ fn is_nan(self) -> bool {
2080
+ self.is_nan()
2081
+ }
2082
+ fn is_finite(self) -> bool {
2083
+ self.is_finite()
2084
+ }
2085
+ fn is_infinite(self) -> bool {
2086
+ self.is_infinite()
2087
+ }
2088
+ fn has_subnormals() -> bool {
2089
+ true
2090
+ }
2091
+ fn max_value() -> f32 {
2092
+ 28.0
2093
+ }
2094
+ fn min_positive() -> f32 {
2095
+ 0.125
2096
+ }
2097
+ }
2098
+
2099
+ impl StorageElement for i8 {
2100
+ fn zero() -> Self {
2101
+ 0
2102
+ }
2103
+ fn one() -> Self {
2104
+ 1
2105
+ }
2106
+ }
2107
+
2108
+ impl NumberLike for i8 {
2109
+ fn from_f32(v: f32) -> Self {
2110
+ f32_round_compat(v) as i8
2111
+ }
2112
+ fn to_f32(self) -> f32 {
2113
+ self as f32
2114
+ }
2115
+ fn from_f64(v: f64) -> Self {
2116
+ f64_round_compat(v) as i8
2117
+ }
2118
+ fn to_f64(self) -> f64 {
2119
+ self as f64
2120
+ }
2121
+ }
2122
+
2123
+ impl StorageElement for u8 {
2124
+ fn zero() -> Self {
2125
+ 0
2126
+ }
2127
+ fn one() -> Self {
2128
+ 1
2129
+ }
2130
+ }
2131
+
2132
+ impl NumberLike for u8 {
2133
+ fn from_f32(v: f32) -> Self {
2134
+ let r = f32_round_compat(v);
2135
+ if r < 0.0 {
2136
+ 0
2137
+ } else {
2138
+ r as u8
2139
+ }
2140
+ }
2141
+ fn to_f32(self) -> f32 {
2142
+ self as f32
2143
+ }
2144
+ fn from_f64(v: f64) -> Self {
2145
+ let r = f64_round_compat(v);
2146
+ if r < 0.0 {
2147
+ 0
2148
+ } else {
2149
+ r as u8
2150
+ }
2151
+ }
2152
+ fn to_f64(self) -> f64 {
2153
+ self as f64
2154
+ }
2155
+ }
2156
+
2157
+ impl StorageElement for i32 {
2158
+ fn zero() -> Self {
2159
+ 0
2160
+ }
2161
+ fn one() -> Self {
2162
+ 1
2163
+ }
2164
+ }
2165
+
2166
+ impl NumberLike for i32 {
2167
+ fn from_f32(v: f32) -> Self {
2168
+ f32_round_compat(v) as i32
2169
+ }
2170
+ fn to_f32(self) -> f32 {
2171
+ self as f32
2172
+ }
2173
+ fn from_f64(v: f64) -> Self {
2174
+ f64_round_compat(v) as i32
2175
+ }
2176
+ fn to_f64(self) -> f64 {
2177
+ self as f64
2178
+ }
2179
+ }
2180
+
2181
+ impl StorageElement for u32 {
2182
+ fn zero() -> Self {
2183
+ 0
2184
+ }
2185
+ fn one() -> Self {
2186
+ 1
2187
+ }
2188
+ }
2189
+
2190
+ impl NumberLike for u32 {
2191
+ fn from_f32(v: f32) -> Self {
2192
+ let r = f32_round_compat(v);
2193
+ if r < 0.0 {
2194
+ 0
2195
+ } else {
2196
+ r as u32
2197
+ }
2198
+ }
2199
+ fn to_f32(self) -> f32 {
2200
+ self as f32
2201
+ }
2202
+ fn from_f64(v: f64) -> Self {
2203
+ let r = f64_round_compat(v);
2204
+ if r < 0.0 {
2205
+ 0
2206
+ } else {
2207
+ r as u32
2208
+ }
2209
+ }
2210
+ fn to_f64(self) -> f64 {
2211
+ self as f64
2212
+ }
2213
+ }
2214
+
2215
+ impl StorageElement for i16 {
2216
+ fn zero() -> Self {
2217
+ 0
2218
+ }
2219
+ fn one() -> Self {
2220
+ 1
2221
+ }
2222
+ }
2223
+
2224
+ impl NumberLike for i16 {
2225
+ fn from_f32(v: f32) -> Self {
2226
+ f32_round_compat(v) as i16
2227
+ }
2228
+ fn to_f32(self) -> f32 {
2229
+ self as f32
2230
+ }
2231
+ fn from_f64(v: f64) -> Self {
2232
+ f64_round_compat(v) as i16
2233
+ }
2234
+ fn to_f64(self) -> f64 {
2235
+ self as f64
2236
+ }
2237
+ }
2238
+
2239
+ impl StorageElement for u16 {
2240
+ fn zero() -> Self {
2241
+ 0
2242
+ }
2243
+ fn one() -> Self {
2244
+ 1
2245
+ }
2246
+ }
2247
+
2248
+ impl NumberLike for u16 {
2249
+ fn from_f32(v: f32) -> Self {
2250
+ let r = f32_round_compat(v);
2251
+ if r < 0.0 {
2252
+ 0
2253
+ } else {
2254
+ r as u16
2255
+ }
2256
+ }
2257
+ fn to_f32(self) -> f32 {
2258
+ self as f32
2259
+ }
2260
+ fn from_f64(v: f64) -> Self {
2261
+ let r = f64_round_compat(v);
2262
+ if r < 0.0 {
2263
+ 0
2264
+ } else {
2265
+ r as u16
2266
+ }
2267
+ }
2268
+ fn to_f64(self) -> f64 {
2269
+ self as f64
2270
+ }
2271
+ }
2272
+
2273
+ impl StorageElement for i64 {
2274
+ fn zero() -> Self {
2275
+ 0
2276
+ }
2277
+ fn one() -> Self {
2278
+ 1
2279
+ }
2280
+ }
2281
+
2282
+ impl NumberLike for i64 {
2283
+ fn from_f32(v: f32) -> Self {
2284
+ f32_round_compat(v) as i64
2285
+ }
2286
+ fn to_f32(self) -> f32 {
2287
+ self as f32
2288
+ }
2289
+ fn from_f64(v: f64) -> Self {
2290
+ f64_round_compat(v) as i64
2291
+ }
2292
+ fn to_f64(self) -> f64 {
2293
+ self as f64
2294
+ }
2295
+ }
2296
+
2297
+ impl StorageElement for u64 {
2298
+ fn zero() -> Self {
2299
+ 0
2300
+ }
2301
+ fn one() -> Self {
2302
+ 1
2303
+ }
2304
+ }
2305
+
2306
+ impl NumberLike for u64 {
2307
+ fn from_f32(v: f32) -> Self {
2308
+ let r = f32_round_compat(v);
2309
+ if r < 0.0 {
2310
+ 0
2311
+ } else {
2312
+ r as u64
2313
+ }
2314
+ }
2315
+ fn to_f32(self) -> f32 {
2316
+ self as f32
2317
+ }
2318
+ fn from_f64(v: f64) -> Self {
2319
+ let r = f64_round_compat(v);
2320
+ if r < 0.0 {
2321
+ 0
2322
+ } else {
2323
+ r as u64
2324
+ }
2325
+ }
2326
+ fn to_f64(self) -> f64 {
2327
+ self as f64
2328
+ }
2329
+ }
2330
+
2331
+ impl StorageElement for usize {
2332
+ fn zero() -> Self {
2333
+ 0
2334
+ }
2335
+ fn one() -> Self {
2336
+ 1
2337
+ }
2338
+ }
2339
+
2340
+ impl StorageElement for i4x2 {
2341
+ fn zero() -> Self {
2342
+ i4x2::from((0i8, 0i8))
2343
+ }
2344
+ fn one() -> Self {
2345
+ i4x2::from((1i8, 1i8))
2346
+ }
2347
+ fn dimensions_per_value() -> usize {
2348
+ 2
2349
+ }
2350
+ }
2351
+
2352
+ impl NumberLike for i4x2 {
2353
+ fn from_f32(v: f32) -> Self {
2354
+ let r = f32_round_compat(v) as i8;
2355
+ i4x2::from((r, r))
2356
+ }
2357
+ fn to_f32(self) -> f32 {
2358
+ let (a, _) = self.into();
2359
+ a as f32
2360
+ }
2361
+ }
2362
+
2363
+ impl StorageElement for u4x2 {
2364
+ fn zero() -> Self {
2365
+ u4x2::from((0u8, 0u8))
2366
+ }
2367
+ fn one() -> Self {
2368
+ u4x2::from((1u8, 1u8))
2369
+ }
2370
+ fn dimensions_per_value() -> usize {
2371
+ 2
2372
+ }
2373
+ }
2374
+
2375
+ impl NumberLike for u4x2 {
2376
+ fn from_f32(v: f32) -> Self {
2377
+ let r = f32_round_compat(v);
2378
+ let r = if r < 0.0 { 0u8 } else { r as u8 };
2379
+ u4x2::from((r, r))
2380
+ }
2381
+ fn to_f32(self) -> f32 {
2382
+ let (a, _) = self.into();
2383
+ a as f32
2384
+ }
2385
+ }
2386
+
2387
+ impl StorageElement for u1x8 {
2388
+ fn zero() -> Self {
2389
+ u1x8(0x00)
2390
+ }
2391
+ fn one() -> Self {
2392
+ u1x8(0xFF)
2393
+ }
2394
+ fn dimensions_per_value() -> usize {
2395
+ 8
2396
+ }
2397
+ }
2398
+
2399
+ impl NumberLike for u1x8 {
2400
+ fn from_f32(v: f32) -> Self {
2401
+ if v > 0.0 {
2402
+ u1x8(0xFF)
2403
+ } else {
2404
+ u1x8(0x00)
2405
+ }
2406
+ }
2407
+ fn to_f32(self) -> f32 {
2408
+ self.0.count_ones() as f32
2409
+ }
2410
+ }
2411
+
2412
+ // endregion: StorageElement + NumberLike + FloatLike Traits
2413
+
2414
+ // region: Complex Types
2415
+
2416
+ #[inline(always)]
2417
+ fn complex_mul_components<Scalar>(
2418
+ left_re: Scalar,
2419
+ left_im: Scalar,
2420
+ right_re: Scalar,
2421
+ right_im: Scalar,
2422
+ ) -> (Scalar, Scalar)
2423
+ where
2424
+ Scalar: Copy
2425
+ + core::ops::Add<Output = Scalar>
2426
+ + core::ops::Sub<Output = Scalar>
2427
+ + core::ops::Mul<Output = Scalar>,
2428
+ {
2429
+ (
2430
+ left_re * right_re - left_im * right_im,
2431
+ left_re * right_im + left_im * right_re,
2432
+ )
2433
+ }
2434
+
2435
+ #[inline(always)]
2436
+ fn complex_div_components<Scalar>(
2437
+ left_re: Scalar,
2438
+ left_im: Scalar,
2439
+ right_re: Scalar,
2440
+ right_im: Scalar,
2441
+ ) -> (Scalar, Scalar)
2442
+ where
2443
+ Scalar: Copy
2444
+ + core::ops::Add<Output = Scalar>
2445
+ + core::ops::Sub<Output = Scalar>
2446
+ + core::ops::Mul<Output = Scalar>
2447
+ + core::ops::Div<Output = Scalar>,
2448
+ {
2449
+ let denom = right_re * right_re + right_im * right_im;
2450
+ (
2451
+ (left_re * right_re + left_im * right_im) / denom,
2452
+ (left_im * right_re - left_re * right_im) / denom,
2453
+ )
2454
+ }
2455
+
2456
+ #[doc(hidden)]
2457
+ pub trait ComplexComponent:
2458
+ NumberLike
2459
+ + core::ops::Add<Output = Self>
2460
+ + core::ops::Sub<Output = Self>
2461
+ + core::ops::Mul<Output = Self>
2462
+ + core::ops::Div<Output = Self>
2463
+ + core::ops::Neg<Output = Self>
2464
+ {
2465
+ type Norm: Copy + core::ops::Add<Output = Self::Norm>;
2466
+
2467
+ fn from_norm(value: Self::Norm) -> Self;
2468
+ fn norm_component(self) -> Self::Norm;
2469
+ }
2470
+
2471
+ impl ComplexComponent for f16 {
2472
+ type Norm = f32;
2473
+
2474
+ fn from_norm(value: Self::Norm) -> Self {
2475
+ Self::from_f32(value)
2476
+ }
2477
+
2478
+ fn norm_component(self) -> Self::Norm {
2479
+ self.to_f32() * self.to_f32()
2480
+ }
2481
+ }
2482
+
2483
+ impl ComplexComponent for bf16 {
2484
+ type Norm = f32;
2485
+
2486
+ fn from_norm(value: Self::Norm) -> Self {
2487
+ Self::from_f32(value)
2488
+ }
2489
+
2490
+ fn norm_component(self) -> Self::Norm {
2491
+ self.to_f32() * self.to_f32()
2492
+ }
2493
+ }
2494
+
2495
+ impl ComplexComponent for f32 {
2496
+ type Norm = f32;
2497
+
2498
+ fn from_norm(value: Self::Norm) -> Self {
2499
+ value
2500
+ }
2501
+
2502
+ fn norm_component(self) -> Self::Norm {
2503
+ self * self
2504
+ }
2505
+ }
2506
+
2507
+ impl ComplexComponent for f64 {
2508
+ type Norm = f64;
2509
+
2510
+ fn from_norm(value: Self::Norm) -> Self {
2511
+ value
2512
+ }
2513
+
2514
+ fn norm_component(self) -> Self::Norm {
2515
+ self * self
2516
+ }
2517
+ }
2518
+
2519
+ /// Complex number with adjacent real and imaginary components of type `Scalar`.
2520
+ ///
2521
+ /// Layout: `{re: Scalar, im: Scalar}`. Supports conjugate, norm², and component access.
2522
+ /// Concrete aliases: [`f16c`], [`bf16c`], [`f32c`], [`f64c`].
2523
+ #[repr(C)]
2524
+ #[derive(Debug, Clone, Copy, PartialEq, Default)]
2525
+ pub struct complex<Scalar> {
2526
+ pub re: Scalar,
2527
+ pub im: Scalar,
2528
+ }
2529
+
2530
+ impl<Scalar> complex<Scalar> {
2531
+ pub const fn from_real_imag(re: Scalar, im: Scalar) -> Self {
2532
+ Self { re, im }
2533
+ }
2534
+
2535
+ pub const fn to_real_imag(self) -> (Scalar, Scalar)
2536
+ where
2537
+ Scalar: Copy,
2538
+ {
2539
+ (self.re, self.im)
2540
+ }
2541
+ }
2542
+
2543
+ impl<Scalar: ComplexComponent> complex<Scalar> {
2544
+ pub fn conj(self) -> Self {
2545
+ Self {
2546
+ re: self.re,
2547
+ im: -self.im,
2548
+ }
2549
+ }
2550
+
2551
+ pub fn norm_sqr(self) -> Scalar::Norm {
2552
+ self.re.norm_component() + self.im.norm_component()
2553
+ }
2554
+ }
2555
+
2556
+ /// Half-precision (32-bit) complex number — two [`f16`] components. Kernel outputs widened to f32c.
2557
+ pub type f16c = complex<f16>;
2558
+ /// BFloat16 (32-bit) complex number — two [`bf16`] components. Kernel outputs widened to f32c.
2559
+ pub type bf16c = complex<bf16>;
2560
+ /// Single-precision (64-bit) complex number — two `f32` components.
2561
+ pub type f32c = complex<f32>;
2562
+ /// Double-precision (128-bit) complex number — two `f64` components.
2563
+ pub type f64c = complex<f64>;
2564
+
2565
+ impl<Scalar: ComplexComponent> From<f32> for complex<Scalar> {
2566
+ fn from(value: f32) -> Self {
2567
+ Self {
2568
+ re: Scalar::from_f32(value),
2569
+ im: Scalar::zero(),
2570
+ }
2571
+ }
2572
+ }
2573
+
2574
+ impl<Scalar: ComplexComponent> core::ops::Add for complex<Scalar> {
2575
+ type Output = Self;
2576
+ fn add(self, right: Self) -> Self::Output {
2577
+ Self {
2578
+ re: self.re + right.re,
2579
+ im: self.im + right.im,
2580
+ }
2581
+ }
2582
+ }
2583
+
2584
+ impl<Scalar: ComplexComponent> core::ops::Sub for complex<Scalar> {
2585
+ type Output = Self;
2586
+ fn sub(self, right: Self) -> Self::Output {
2587
+ Self {
2588
+ re: self.re - right.re,
2589
+ im: self.im - right.im,
2590
+ }
2591
+ }
2592
+ }
2593
+
2594
+ impl<Scalar: ComplexComponent> core::ops::Mul for complex<Scalar> {
2595
+ type Output = Self;
2596
+ fn mul(self, right: Self) -> Self::Output {
2597
+ let (re, im) = complex_mul_components(self.re, self.im, right.re, right.im);
2598
+ Self { re, im }
2599
+ }
2600
+ }
2601
+
2602
+ impl<Scalar: ComplexComponent> core::ops::Div for complex<Scalar> {
2603
+ type Output = Self;
2604
+ fn div(self, right: Self) -> Self::Output {
2605
+ let (re, im) = complex_div_components(self.re, self.im, right.re, right.im);
2606
+ Self { re, im }
2607
+ }
2608
+ }
2609
+
2610
+ impl<Scalar: ComplexComponent> core::ops::Neg for complex<Scalar> {
2611
+ type Output = Self;
2612
+ fn neg(self) -> Self::Output {
2613
+ Self {
2614
+ re: -self.re,
2615
+ im: -self.im,
2616
+ }
2617
+ }
2618
+ }
2619
+
2620
+ impl<Scalar: NumberLike> core::fmt::Display for complex<Scalar> {
2621
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2622
+ let re = self.re.to_f32();
2623
+ let im = self.im.to_f32();
2624
+ if let Some(prec) = f.precision() {
2625
+ if im < 0.0 {
2626
+ write!(f, "{:.prec$} - {:.prec$}i", re, -im)
2627
+ } else {
2628
+ write!(f, "{:.prec$} + {:.prec$}i", re, im)
2629
+ }
2630
+ } else if im < 0.0 {
2631
+ write!(f, "{} - {}i", re, -im)
2632
+ } else {
2633
+ write!(f, "{} + {}i", re, im)
2634
+ }
2635
+ }
2636
+ }
2637
+
2638
+ impl<Scalar: ComplexComponent> StorageElement for complex<Scalar> {
2639
+ fn zero() -> Self {
2640
+ Self {
2641
+ re: Scalar::zero(),
2642
+ im: Scalar::zero(),
2643
+ }
2644
+ }
2645
+ fn one() -> Self {
2646
+ Self {
2647
+ re: Scalar::one(),
2648
+ im: Scalar::zero(),
2649
+ }
2650
+ }
2651
+ }
2652
+
2653
+ impl<Scalar: ComplexComponent> NumberLike for complex<Scalar> {
2654
+ fn from_f32(v: f32) -> Self {
2655
+ Self::from(v)
2656
+ }
2657
+ fn to_f32(self) -> f32 {
2658
+ self.re.to_f32()
2659
+ }
2660
+ fn from_f64(v: f64) -> Self {
2661
+ Self {
2662
+ re: Scalar::from_f64(v),
2663
+ im: Scalar::zero(),
2664
+ }
2665
+ }
2666
+ fn to_f64(self) -> f64 {
2667
+ self.re.to_f64()
2668
+ }
2669
+ fn abs(self) -> Self {
2670
+ Self {
2671
+ re: Scalar::from_norm(self.norm_sqr()),
2672
+ im: Scalar::zero(),
2673
+ }
2674
+ }
2675
+ fn is_nan(self) -> bool {
2676
+ self.re.is_nan() || self.im.is_nan()
2677
+ }
2678
+ fn is_finite(self) -> bool {
2679
+ self.re.is_finite() && self.im.is_finite()
2680
+ }
2681
+ fn is_infinite(self) -> bool {
2682
+ self.re.is_infinite() || self.im.is_infinite()
2683
+ }
2684
+ fn has_infinity() -> bool {
2685
+ Scalar::has_infinity()
2686
+ }
2687
+ fn has_nan() -> bool {
2688
+ Scalar::has_nan()
2689
+ }
2690
+ fn has_subnormals() -> bool {
2691
+ Scalar::has_subnormals()
2692
+ }
2693
+ fn max_value() -> f32 {
2694
+ Scalar::max_value()
2695
+ }
2696
+ fn min_positive() -> f32 {
2697
+ Scalar::min_positive()
2698
+ }
2699
+ }
2700
+
2701
+ impl<Scalar: ComplexComponent> FloatConvertible for complex<Scalar> {
2702
+ type DimScalar = complex<Scalar>;
2703
+ type Unpacked = [complex<Scalar>; 1];
2704
+ #[inline(always)]
2705
+ fn unpack(self) -> [complex<Scalar>; 1] {
2706
+ [self]
2707
+ }
2708
+ #[inline(always)]
2709
+ fn pack(dims: [complex<Scalar>; 1]) -> Self {
2710
+ dims[0]
2711
+ }
2712
+ }
2713
+
2714
+ // endregion: Complex Types
2715
+
2716
+ // region: FloatConvertible Trait
2717
+
2718
+ /// Trait for types that can unpack/pack logical sub-dimensions.
2719
+ ///
2720
+ /// For normal scalar types (`dimensions_per_value() == 1`), `DimScalar = Self` and
2721
+ /// `Unpacked = [Self; 1]`. For packed sub-byte types, `DimScalar` is the natural
2722
+ /// scalar type for individual sub-dimensions (e.g., `i8` for `i4x2`).
2723
+ pub trait FloatConvertible: NumberLike {
2724
+ /// Scalar type for individual sub-dimensions.
2725
+ type DimScalar: Copy + Default + NumberLike;
2726
+
2727
+ /// Fixed-size array holding all unpacked sub-dimensions.
2728
+ type Unpacked: AsRef<[Self::DimScalar]> + AsMut<[Self::DimScalar]> + Copy + Default;
2729
+
2730
+ /// Unpack all logical sub-dimensions from this packed value.
2731
+ fn unpack(self) -> Self::Unpacked;
2732
+
2733
+ /// Pack sub-dimension scalars into a single storage value.
2734
+ fn pack(dims: Self::Unpacked) -> Self;
2735
+ }
2736
+
2737
+ impl FloatConvertible for f32 {
2738
+ type DimScalar = f32;
2739
+ type Unpacked = [f32; 1];
2740
+ #[inline(always)]
2741
+ fn unpack(self) -> [f32; 1] {
2742
+ [self]
2743
+ }
2744
+ #[inline(always)]
2745
+ fn pack(dims: [f32; 1]) -> Self {
2746
+ dims[0]
2747
+ }
2748
+ }
2749
+
2750
+ impl FloatConvertible for f64 {
2751
+ type DimScalar = f64;
2752
+ type Unpacked = [f64; 1];
2753
+ #[inline(always)]
2754
+ fn unpack(self) -> [f64; 1] {
2755
+ [self]
2756
+ }
2757
+ #[inline(always)]
2758
+ fn pack(dims: [f64; 1]) -> Self {
2759
+ dims[0]
2760
+ }
2761
+ }
2762
+
2763
+ impl FloatConvertible for f16 {
2764
+ type DimScalar = f16;
2765
+ type Unpacked = [f16; 1];
2766
+ #[inline(always)]
2767
+ fn unpack(self) -> [f16; 1] {
2768
+ [self]
2769
+ }
2770
+ #[inline(always)]
2771
+ fn pack(dims: [f16; 1]) -> Self {
2772
+ dims[0]
2773
+ }
2774
+ }
2775
+
2776
+ impl FloatConvertible for bf16 {
2777
+ type DimScalar = bf16;
2778
+ type Unpacked = [bf16; 1];
2779
+ #[inline(always)]
2780
+ fn unpack(self) -> [bf16; 1] {
2781
+ [self]
2782
+ }
2783
+ #[inline(always)]
2784
+ fn pack(dims: [bf16; 1]) -> Self {
2785
+ dims[0]
2786
+ }
2787
+ }
2788
+
2789
+ impl FloatConvertible for e4m3 {
2790
+ type DimScalar = e4m3;
2791
+ type Unpacked = [e4m3; 1];
2792
+ #[inline(always)]
2793
+ fn unpack(self) -> [e4m3; 1] {
2794
+ [self]
2795
+ }
2796
+ #[inline(always)]
2797
+ fn pack(dims: [e4m3; 1]) -> Self {
2798
+ dims[0]
2799
+ }
2800
+ }
2801
+
2802
+ impl FloatConvertible for e5m2 {
2803
+ type DimScalar = e5m2;
2804
+ type Unpacked = [e5m2; 1];
2805
+ #[inline(always)]
2806
+ fn unpack(self) -> [e5m2; 1] {
2807
+ [self]
2808
+ }
2809
+ #[inline(always)]
2810
+ fn pack(dims: [e5m2; 1]) -> Self {
2811
+ dims[0]
2812
+ }
2813
+ }
2814
+
2815
+ impl FloatConvertible for e2m3 {
2816
+ type DimScalar = e2m3;
2817
+ type Unpacked = [e2m3; 1];
2818
+ #[inline(always)]
2819
+ fn unpack(self) -> [e2m3; 1] {
2820
+ [self]
2821
+ }
2822
+ #[inline(always)]
2823
+ fn pack(dims: [e2m3; 1]) -> Self {
2824
+ dims[0]
2825
+ }
2826
+ }
2827
+
2828
+ impl FloatConvertible for e3m2 {
2829
+ type DimScalar = e3m2;
2830
+ type Unpacked = [e3m2; 1];
2831
+ #[inline(always)]
2832
+ fn unpack(self) -> [e3m2; 1] {
2833
+ [self]
2834
+ }
2835
+ #[inline(always)]
2836
+ fn pack(dims: [e3m2; 1]) -> Self {
2837
+ dims[0]
2838
+ }
2839
+ }
2840
+
2841
+ impl FloatConvertible for i8 {
2842
+ type DimScalar = i8;
2843
+ type Unpacked = [i8; 1];
2844
+ #[inline(always)]
2845
+ fn unpack(self) -> [i8; 1] {
2846
+ [self]
2847
+ }
2848
+ #[inline(always)]
2849
+ fn pack(dims: [i8; 1]) -> Self {
2850
+ dims[0]
2851
+ }
2852
+ }
2853
+
2854
+ impl FloatConvertible for u8 {
2855
+ type DimScalar = u8;
2856
+ type Unpacked = [u8; 1];
2857
+ #[inline(always)]
2858
+ fn unpack(self) -> [u8; 1] {
2859
+ [self]
2860
+ }
2861
+ #[inline(always)]
2862
+ fn pack(dims: [u8; 1]) -> Self {
2863
+ dims[0]
2864
+ }
2865
+ }
2866
+
2867
+ impl FloatConvertible for i16 {
2868
+ type DimScalar = i16;
2869
+ type Unpacked = [i16; 1];
2870
+ #[inline(always)]
2871
+ fn unpack(self) -> [i16; 1] {
2872
+ [self]
2873
+ }
2874
+ #[inline(always)]
2875
+ fn pack(dims: [i16; 1]) -> Self {
2876
+ dims[0]
2877
+ }
2878
+ }
2879
+
2880
+ impl FloatConvertible for u16 {
2881
+ type DimScalar = u16;
2882
+ type Unpacked = [u16; 1];
2883
+ #[inline(always)]
2884
+ fn unpack(self) -> [u16; 1] {
2885
+ [self]
2886
+ }
2887
+ #[inline(always)]
2888
+ fn pack(dims: [u16; 1]) -> Self {
2889
+ dims[0]
2890
+ }
2891
+ }
2892
+
2893
+ impl FloatConvertible for i32 {
2894
+ type DimScalar = i32;
2895
+ type Unpacked = [i32; 1];
2896
+ #[inline(always)]
2897
+ fn unpack(self) -> [i32; 1] {
2898
+ [self]
2899
+ }
2900
+ #[inline(always)]
2901
+ fn pack(dims: [i32; 1]) -> Self {
2902
+ dims[0]
2903
+ }
2904
+ }
2905
+
2906
+ impl FloatConvertible for u32 {
2907
+ type DimScalar = u32;
2908
+ type Unpacked = [u32; 1];
2909
+ #[inline(always)]
2910
+ fn unpack(self) -> [u32; 1] {
2911
+ [self]
2912
+ }
2913
+ #[inline(always)]
2914
+ fn pack(dims: [u32; 1]) -> Self {
2915
+ dims[0]
2916
+ }
2917
+ }
2918
+
2919
+ impl FloatConvertible for i64 {
2920
+ type DimScalar = i64;
2921
+ type Unpacked = [i64; 1];
2922
+ #[inline(always)]
2923
+ fn unpack(self) -> [i64; 1] {
2924
+ [self]
2925
+ }
2926
+ #[inline(always)]
2927
+ fn pack(dims: [i64; 1]) -> Self {
2928
+ dims[0]
2929
+ }
2930
+ }
2931
+
2932
+ impl FloatConvertible for u64 {
2933
+ type DimScalar = u64;
2934
+ type Unpacked = [u64; 1];
2935
+ #[inline(always)]
2936
+ fn unpack(self) -> [u64; 1] {
2937
+ [self]
2938
+ }
2939
+ #[inline(always)]
2940
+ fn pack(dims: [u64; 1]) -> Self {
2941
+ dims[0]
2942
+ }
2943
+ }
2944
+
2945
+ impl FloatConvertible for i4x2 {
2946
+ type DimScalar = i8;
2947
+ type Unpacked = [i8; 2];
2948
+ #[inline(always)]
2949
+ fn unpack(self) -> [i8; 2] {
2950
+ let (low, high) = self.to_i8s();
2951
+ [low, high]
2952
+ }
2953
+ #[inline(always)]
2954
+ fn pack(dims: [i8; 2]) -> Self {
2955
+ i4x2::from_i8s(dims[0], dims[1])
2956
+ }
2957
+ }
2958
+
2959
+ impl FloatConvertible for u4x2 {
2960
+ type DimScalar = u8;
2961
+ type Unpacked = [u8; 2];
2962
+ #[inline(always)]
2963
+ fn unpack(self) -> [u8; 2] {
2964
+ let (low, high) = self.to_u8s();
2965
+ [low, high]
2966
+ }
2967
+ #[inline(always)]
2968
+ fn pack(dims: [u8; 2]) -> Self {
2969
+ u4x2::from_u8s(dims[0], dims[1])
2970
+ }
2971
+ }
2972
+
2973
+ impl FloatConvertible for u1x8 {
2974
+ type DimScalar = u8;
2975
+ type Unpacked = [u8; 8];
2976
+ #[inline(always)]
2977
+ fn unpack(self) -> [u8; 8] {
2978
+ let mut out = [0u8; 8];
2979
+ for (i, slot) in out.iter_mut().enumerate() {
2980
+ *slot = (self.0 >> i) & 1;
2981
+ }
2982
+ out
2983
+ }
2984
+ #[inline(always)]
2985
+ fn pack(dims: [u8; 8]) -> Self {
2986
+ let mut byte = 0u8;
2987
+ for (i, &dim) in dims.iter().enumerate() {
2988
+ if dim != 0 {
2989
+ byte |= 1 << i;
2990
+ }
2991
+ }
2992
+ u1x8(byte)
2993
+ }
2994
+ }
2995
+
2996
+ // endregion: FloatConvertible Trait
2997
+
2998
+ // region: Dimension Proxies
2999
+
3000
+ /// Immutable proxy for a single logical dimension within a packed storage value.
3001
+ ///
3002
+ /// For normal types this is equivalent to a `Scalar::DimScalar` copy.
3003
+ /// For sub-byte types it represents one unpacked dimension from a packed value.
3004
+ /// The lifetime ensures the proxy does not outlive the container it came from.
3005
+ pub struct DimRef<'a, Scalar: FloatConvertible> {
3006
+ value: Scalar::DimScalar,
3007
+ _marker: core::marker::PhantomData<&'a Scalar>,
3008
+ }
3009
+
3010
+ impl<'a, Scalar: FloatConvertible> DimRef<'a, Scalar> {
3011
+ /// Create a new immutable dimension proxy.
3012
+ #[inline]
3013
+ pub fn new(value: Scalar::DimScalar) -> Self {
3014
+ Self {
3015
+ value,
3016
+ _marker: core::marker::PhantomData,
3017
+ }
3018
+ }
3019
+ }
3020
+
3021
+ impl<Scalar: FloatConvertible> Copy for DimRef<'_, Scalar> {}
3022
+
3023
+ impl<Scalar: FloatConvertible> Clone for DimRef<'_, Scalar> {
3024
+ #[inline]
3025
+ fn clone(&self) -> Self {
3026
+ *self
3027
+ }
3028
+ }
3029
+
3030
+ impl<Scalar: FloatConvertible> core::ops::Deref for DimRef<'_, Scalar> {
3031
+ type Target = Scalar::DimScalar;
3032
+ #[inline]
3033
+ fn deref(&self) -> &Scalar::DimScalar {
3034
+ &self.value
3035
+ }
3036
+ }
3037
+
3038
+ impl<Scalar: FloatConvertible> PartialEq for DimRef<'_, Scalar>
3039
+ where
3040
+ Scalar::DimScalar: PartialEq,
3041
+ {
3042
+ #[inline]
3043
+ fn eq(&self, other: &Self) -> bool {
3044
+ self.value == other.value
3045
+ }
3046
+ }
3047
+
3048
+ impl<Scalar: FloatConvertible> PartialOrd for DimRef<'_, Scalar>
3049
+ where
3050
+ Scalar::DimScalar: PartialOrd,
3051
+ {
3052
+ #[inline]
3053
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
3054
+ self.value.partial_cmp(&other.value)
3055
+ }
3056
+ }
3057
+
3058
+ impl<Scalar: FloatConvertible> core::fmt::Debug for DimRef<'_, Scalar>
3059
+ where
3060
+ Scalar::DimScalar: core::fmt::Debug,
3061
+ {
3062
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3063
+ self.value.fmt(f)
3064
+ }
3065
+ }
3066
+
3067
+ impl<Scalar: FloatConvertible> core::fmt::Display for DimRef<'_, Scalar>
3068
+ where
3069
+ Scalar::DimScalar: core::fmt::Display,
3070
+ {
3071
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3072
+ self.value.fmt(f)
3073
+ }
3074
+ }
3075
+
3076
+ /// Mutable proxy for a single logical dimension within a packed storage value.
3077
+ ///
3078
+ /// On [`Drop`], performs a read-modify-write: reads the current storage value,
3079
+ /// overwrites the sub-dimension at `sub_index`, and writes back.
3080
+ ///
3081
+ /// For normal types (`dimensions_per_value() == 1`), this is equivalent to `&mut Scalar`.
3082
+ /// For sub-byte types, this enables modifying individual nibbles/bits.
3083
+ ///
3084
+ /// # Aliasing
3085
+ ///
3086
+ /// In a standard `for` loop each proxy is dropped before the next is created.
3087
+ /// Holding multiple proxies to sub-dimensions of the same storage value
3088
+ /// simultaneously is safe but uses last-writer-wins semantics.
3089
+ pub struct DimMut<'a, Scalar: FloatConvertible> {
3090
+ ptr: *mut Scalar,
3091
+ sub_index: usize,
3092
+ value: Scalar::DimScalar,
3093
+ _marker: core::marker::PhantomData<&'a mut Scalar>,
3094
+ }
3095
+
3096
+ impl<'a, Scalar: FloatConvertible> DimMut<'a, Scalar> {
3097
+ /// Create a new mutable dimension proxy.
3098
+ ///
3099
+ /// # Safety
3100
+ ///
3101
+ /// `ptr` must be valid for reads and writes for the duration of `'a`.
3102
+ #[inline]
3103
+ pub unsafe fn new(ptr: *mut Scalar, sub_index: usize, value: Scalar::DimScalar) -> Self {
3104
+ Self {
3105
+ ptr,
3106
+ sub_index,
3107
+ value,
3108
+ _marker: core::marker::PhantomData,
3109
+ }
3110
+ }
3111
+ }
3112
+
3113
+ impl<Scalar: FloatConvertible> core::ops::Deref for DimMut<'_, Scalar> {
3114
+ type Target = Scalar::DimScalar;
3115
+ #[inline]
3116
+ fn deref(&self) -> &Scalar::DimScalar {
3117
+ &self.value
3118
+ }
3119
+ }
3120
+
3121
+ impl<Scalar: FloatConvertible> core::ops::DerefMut for DimMut<'_, Scalar> {
3122
+ #[inline]
3123
+ fn deref_mut(&mut self) -> &mut Scalar::DimScalar {
3124
+ &mut self.value
3125
+ }
3126
+ }
3127
+
3128
+ impl<Scalar: FloatConvertible> Drop for DimMut<'_, Scalar> {
3129
+ fn drop(&mut self) {
3130
+ let mut unpacked = unsafe { *self.ptr }.unpack();
3131
+ unpacked.as_mut()[self.sub_index] = self.value;
3132
+ unsafe { self.ptr.write(Scalar::pack(unpacked)) };
3133
+ }
3134
+ }
3135
+
3136
+ impl<Scalar: FloatConvertible> core::fmt::Debug for DimMut<'_, Scalar>
3137
+ where
3138
+ Scalar::DimScalar: core::fmt::Debug,
3139
+ {
3140
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3141
+ self.value.fmt(f)
3142
+ }
3143
+ }
3144
+
3145
+ impl<Scalar: FloatConvertible> core::fmt::Display for DimMut<'_, Scalar>
3146
+ where
3147
+ Scalar::DimScalar: core::fmt::Display,
3148
+ {
3149
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3150
+ self.value.fmt(f)
3151
+ }
3152
+ }
3153
+
3154
+ // endregion: Dimension Proxies
3155
+
3156
+ // region: TestableType Trait (test-only)
3157
+
3158
+ #[cfg(test)]
3159
+ pub(crate) trait TestableType: FloatLike {
3160
+ /// Base absolute tolerance for this type.
3161
+ fn atol() -> f64;
3162
+ /// Base relative tolerance for this type.
3163
+ fn rtol() -> f64;
3164
+ }
3165
+
3166
+ #[cfg(test)]
3167
+ pub(crate) fn assert_close(actual: f64, expected: f64, atol: f64, rtol: f64, msg: &str) {
3168
+ let tol = atol + rtol * expected.abs();
3169
+ assert!(
3170
+ (actual - expected).abs() <= tol,
3171
+ "{}: expected {} but got {} (atol={}, rtol={}, tol={})",
3172
+ msg,
3173
+ expected,
3174
+ actual,
3175
+ atol,
3176
+ rtol,
3177
+ tol
3178
+ );
3179
+ }
3180
+
3181
+ #[cfg(test)]
3182
+ impl TestableType for f32 {
3183
+ fn atol() -> f64 {
3184
+ 1e-4
3185
+ }
3186
+ fn rtol() -> f64 {
3187
+ 1e-4
3188
+ }
3189
+ }
3190
+ #[cfg(test)]
3191
+ impl TestableType for f64 {
3192
+ fn atol() -> f64 {
3193
+ 1e-9
3194
+ }
3195
+ fn rtol() -> f64 {
3196
+ 1e-9
3197
+ }
3198
+ }
3199
+ #[cfg(test)]
3200
+ impl TestableType for f16 {
3201
+ fn atol() -> f64 {
3202
+ 0.05
3203
+ }
3204
+ fn rtol() -> f64 {
3205
+ 0.05
3206
+ }
3207
+ }
3208
+ #[cfg(test)]
3209
+ impl TestableType for bf16 {
3210
+ fn atol() -> f64 {
3211
+ 0.1
3212
+ }
3213
+ fn rtol() -> f64 {
3214
+ 0.1
3215
+ }
3216
+ }
3217
+ #[cfg(test)]
3218
+ impl TestableType for e4m3 {
3219
+ fn atol() -> f64 {
3220
+ 0.5
3221
+ }
3222
+ fn rtol() -> f64 {
3223
+ 0.1
3224
+ }
3225
+ }
3226
+ #[cfg(test)]
3227
+ impl TestableType for e5m2 {
3228
+ fn atol() -> f64 {
3229
+ 1.0
3230
+ }
3231
+ fn rtol() -> f64 {
3232
+ 0.1
3233
+ }
3234
+ }
3235
+ #[cfg(test)]
3236
+ impl TestableType for e2m3 {
3237
+ fn atol() -> f64 {
3238
+ 0.5
3239
+ }
3240
+ fn rtol() -> f64 {
3241
+ 0.1
3242
+ }
3243
+ }
3244
+ #[cfg(test)]
3245
+ impl TestableType for e3m2 {
3246
+ fn atol() -> f64 {
3247
+ 0.5
3248
+ }
3249
+ fn rtol() -> f64 {
3250
+ 0.1
3251
+ }
3252
+ }
3253
+ #[cfg(test)]
3254
+ impl TestableType for i8 {
3255
+ fn atol() -> f64 {
3256
+ 1.0
3257
+ }
3258
+ fn rtol() -> f64 {
3259
+ 0.0
3260
+ }
3261
+ }
3262
+ #[cfg(test)]
3263
+ impl TestableType for u8 {
3264
+ fn atol() -> f64 {
3265
+ 1.0
3266
+ }
3267
+ fn rtol() -> f64 {
3268
+ 0.0
3269
+ }
3270
+ }
3271
+ #[cfg(test)]
3272
+ impl TestableType for i32 {
3273
+ fn atol() -> f64 {
3274
+ 1.0
3275
+ }
3276
+ fn rtol() -> f64 {
3277
+ 0.0
3278
+ }
3279
+ }
3280
+ #[cfg(test)]
3281
+ impl TestableType for u32 {
3282
+ fn atol() -> f64 {
3283
+ 1.0
3284
+ }
3285
+ fn rtol() -> f64 {
3286
+ 0.0
3287
+ }
3288
+ }
3289
+ #[cfg(test)]
3290
+ impl TestableType for i16 {
3291
+ fn atol() -> f64 {
3292
+ 1.0
3293
+ }
3294
+ fn rtol() -> f64 {
3295
+ 0.0
3296
+ }
3297
+ }
3298
+ #[cfg(test)]
3299
+ impl TestableType for u16 {
3300
+ fn atol() -> f64 {
3301
+ 1.0
3302
+ }
3303
+ fn rtol() -> f64 {
3304
+ 0.0
3305
+ }
3306
+ }
3307
+ #[cfg(test)]
3308
+ impl TestableType for i64 {
3309
+ fn atol() -> f64 {
3310
+ 1.0
3311
+ }
3312
+ fn rtol() -> f64 {
3313
+ 0.0
3314
+ }
3315
+ }
3316
+ #[cfg(test)]
3317
+ impl TestableType for u64 {
3318
+ fn atol() -> f64 {
3319
+ 1.0
3320
+ }
3321
+ fn rtol() -> f64 {
3322
+ 0.0
3323
+ }
3324
+ }
3325
+ #[cfg(test)]
3326
+ impl TestableType for i4x2 {
3327
+ fn atol() -> f64 {
3328
+ 1.0
3329
+ }
3330
+ fn rtol() -> f64 {
3331
+ 0.0
3332
+ }
3333
+ }
3334
+ #[cfg(test)]
3335
+ impl TestableType for u4x2 {
3336
+ fn atol() -> f64 {
3337
+ 1.0
3338
+ }
3339
+ fn rtol() -> f64 {
3340
+ 0.0
3341
+ }
3342
+ }
3343
+ #[cfg(test)]
3344
+ impl TestableType for u1x8 {
3345
+ fn atol() -> f64 {
3346
+ 0.0
3347
+ }
3348
+ fn rtol() -> f64 {
3349
+ 0.0
3350
+ }
3351
+ }
3352
+
3353
+ // endregion: TestableType Trait
3354
+
3355
+ #[cfg(test)]
3356
+ mod tests {
3357
+ use super::*;
3358
+
3359
+ fn assert_scalar_roundtrip<Scalar: FloatLike>(original: f32, abs_tol: f32, rel_tol: f32) {
3360
+ let converted = Scalar::from_f32(original);
3361
+ let roundtrip = NumberLike::to_f32(converted);
3362
+ if original == 0.0 {
3363
+ assert_eq!(roundtrip, 0.0, "Zero should roundtrip exactly");
3364
+ return;
3365
+ }
3366
+ let abs_error = f32_abs_compat(roundtrip - original);
3367
+ let rel_error = abs_error / f32_abs_compat(original);
3368
+ assert!(
3369
+ abs_error <= abs_tol || rel_error <= rel_tol,
3370
+ "Roundtrip failed for {}: got {} (abs_err={:.6}, rel_err={:.6})",
3371
+ original,
3372
+ roundtrip,
3373
+ abs_error,
3374
+ rel_error
3375
+ );
3376
+ }
3377
+
3378
+ fn assert_scalar_almost_equal<Scalar: FloatLike>(
3379
+ actual: f32,
3380
+ expected: f32,
3381
+ abs_tol: f32,
3382
+ rel_tol: f32,
3383
+ context: &str,
3384
+ ) {
3385
+ let abs_error = f32_abs_compat(actual - expected);
3386
+ let rel_error = if expected != 0.0 {
3387
+ abs_error / f32_abs_compat(expected)
3388
+ } else {
3389
+ abs_error
3390
+ };
3391
+ assert!(
3392
+ abs_error <= abs_tol || rel_error <= rel_tol,
3393
+ "{}: expected {} but got {} (abs_err={:.6}, rel_err={:.6})",
3394
+ context,
3395
+ expected,
3396
+ actual,
3397
+ abs_error,
3398
+ rel_error
3399
+ );
3400
+ }
3401
+
3402
+ fn check_arithmetic<Scalar>(a_val: f32, b_val: f32, abs_tol: f32, rel_tol: f32)
3403
+ where
3404
+ Scalar: FloatLike
3405
+ + PartialOrd
3406
+ + PartialEq
3407
+ + core::ops::Add<Output = Scalar>
3408
+ + core::ops::Sub<Output = Scalar>
3409
+ + core::ops::Mul<Output = Scalar>
3410
+ + core::ops::Div<Output = Scalar>
3411
+ + core::ops::Neg<Output = Scalar>,
3412
+ {
3413
+ let a = Scalar::from_f32(a_val);
3414
+ let b = Scalar::from_f32(b_val);
3415
+ assert_scalar_almost_equal::<Scalar>(
3416
+ NumberLike::to_f32(a + b),
3417
+ a_val + b_val,
3418
+ abs_tol,
3419
+ rel_tol,
3420
+ "add",
3421
+ );
3422
+ assert_scalar_almost_equal::<Scalar>(
3423
+ NumberLike::to_f32(a - b),
3424
+ a_val - b_val,
3425
+ abs_tol,
3426
+ rel_tol,
3427
+ "sub",
3428
+ );
3429
+ assert_scalar_almost_equal::<Scalar>(
3430
+ NumberLike::to_f32(a * b),
3431
+ a_val * b_val,
3432
+ abs_tol,
3433
+ rel_tol,
3434
+ "mul",
3435
+ );
3436
+ assert_scalar_almost_equal::<Scalar>(
3437
+ NumberLike::to_f32(a / b),
3438
+ a_val / b_val,
3439
+ abs_tol,
3440
+ rel_tol,
3441
+ "div",
3442
+ );
3443
+ assert_scalar_almost_equal::<Scalar>(
3444
+ NumberLike::to_f32(-a),
3445
+ -a_val,
3446
+ abs_tol,
3447
+ rel_tol,
3448
+ "neg",
3449
+ );
3450
+ assert_eq!(NumberLike::to_f32(Scalar::zero()), 0.0);
3451
+ assert_scalar_almost_equal::<Scalar>(
3452
+ NumberLike::to_f32(Scalar::one()),
3453
+ 1.0,
3454
+ abs_tol,
3455
+ rel_tol,
3456
+ "ONE",
3457
+ );
3458
+ assert_scalar_almost_equal::<Scalar>(
3459
+ NumberLike::to_f32(Scalar::from_f32(-1.0)),
3460
+ -1.0,
3461
+ abs_tol,
3462
+ rel_tol,
3463
+ "NEG_ONE",
3464
+ );
3465
+ assert!(a > b);
3466
+ assert!(a == a);
3467
+ assert_scalar_almost_equal::<Scalar>(
3468
+ NumberLike::to_f32(NumberLike::abs(-a)),
3469
+ a_val,
3470
+ abs_tol,
3471
+ rel_tol,
3472
+ "abs",
3473
+ );
3474
+ assert!(NumberLike::is_finite(a));
3475
+ assert!(!NumberLike::is_nan(a));
3476
+ if Scalar::has_infinity() {
3477
+ assert!(!NumberLike::is_infinite(a));
3478
+ }
3479
+ }
3480
+
3481
+ fn check_roundtrip<Scalar: FloatLike>(values: &[f32], abs_tol: f32, rel_tol: f32) {
3482
+ for &v in values {
3483
+ assert_scalar_roundtrip::<Scalar>(v, abs_tol, rel_tol);
3484
+ }
3485
+ }
3486
+
3487
+ fn check_edge_cases<Scalar: FloatLike>() {
3488
+ assert_eq!(NumberLike::to_f32(Scalar::from_f32(0.0)), 0.0);
3489
+ assert_eq!(NumberLike::to_f32(Scalar::from_f32(-0.0)), 0.0);
3490
+ if Scalar::has_infinity() {
3491
+ assert!(NumberLike::to_f32(Scalar::from_f32(f32::INFINITY)).is_infinite());
3492
+ assert!(NumberLike::to_f32(Scalar::from_f32(f32::NEG_INFINITY)).is_infinite());
3493
+ } else {
3494
+ assert!(!NumberLike::to_f32(Scalar::from_f32(f32::INFINITY)).is_infinite());
3495
+ }
3496
+ if Scalar::has_nan() {
3497
+ assert!(NumberLike::to_f32(Scalar::from_f32(f32::NAN)).is_nan());
3498
+ } else {
3499
+ assert!(!NumberLike::to_f32(Scalar::from_f32(f32::NAN)).is_nan());
3500
+ }
3501
+ let big = Scalar::max_value() * 10.0;
3502
+ let overflow = Scalar::from_f32(big);
3503
+ if Scalar::has_infinity() {
3504
+ assert!(
3505
+ NumberLike::to_f32(overflow).is_infinite()
3506
+ || NumberLike::to_f32(overflow) >= Scalar::max_value()
3507
+ );
3508
+ } else {
3509
+ let v = NumberLike::to_f32(overflow);
3510
+ assert!(!v.is_infinite() && !v.is_nan());
3511
+ assert!(v <= Scalar::max_value());
3512
+ let neg = NumberLike::to_f32(Scalar::from_f32(-big));
3513
+ assert!(!neg.is_infinite() && !neg.is_nan());
3514
+ assert!(neg >= -Scalar::max_value());
3515
+ }
3516
+ }
3517
+
3518
+ fn check_subnormals<Scalar: FloatLike>(values: &[f32], upper_bound: f32) {
3519
+ for &val in values {
3520
+ let roundtrip = NumberLike::to_f32(Scalar::from_f32(val));
3521
+ assert!(
3522
+ roundtrip >= 0.0 && roundtrip < upper_bound,
3523
+ "{} subnormal test failed for {}: got {}",
3524
+ core::any::type_name::<Scalar>(),
3525
+ val,
3526
+ roundtrip
3527
+ );
3528
+ }
3529
+ }
3530
+
3531
+ #[test]
3532
+ fn arithmetic_ieee_halfs() {
3533
+ check_arithmetic::<f16>(3.5, 2.0, 0.002, 0.001);
3534
+ check_arithmetic::<bf16>(3.5, 2.0, 0.016, 0.008);
3535
+ }
3536
+
3537
+ #[test]
3538
+ fn arithmetic_minifloats() {
3539
+ check_arithmetic::<e4m3>(2.0, 1.5, 0.25, 0.125);
3540
+ check_arithmetic::<e5m2>(2.0, 1.5, 0.5, 0.25);
3541
+ check_arithmetic::<e2m3>(2.0, 1.5, 0.25, 0.125);
3542
+ check_arithmetic::<e3m2>(4.0, 2.0, 0.5, 0.25);
3543
+ }
3544
+
3545
+ #[test]
3546
+ fn roundtrip() {
3547
+ check_roundtrip::<f16>(
3548
+ &[
3549
+ 0.0, 1.0, -1.0, 0.5, 2.0, 4.0, 8.0, 16.0, 100.0, 1000.0, 10000.0, 0.001, 0.0001,
3550
+ 0.00001, -100.0, -1000.0,
3551
+ ],
3552
+ 0.002,
3553
+ 0.001,
3554
+ );
3555
+ check_roundtrip::<bf16>(
3556
+ &[
3557
+ 0.0, 1.0, -1.0, 0.5, 2.0, 10.0, 100.0, 1000.0, 1e6, 0.001, 1e-6, -100.0, -1000.0,
3558
+ ],
3559
+ 0.016,
3560
+ 0.008,
3561
+ );
3562
+ check_roundtrip::<e4m3>(
3563
+ &[0.0, 1.0, -1.0, 0.5, 2.0, 4.0, 8.0, 16.0, 64.0, 128.0, 224.0],
3564
+ 0.25,
3565
+ 0.125,
3566
+ );
3567
+ check_roundtrip::<e5m2>(
3568
+ &[
3569
+ 0.0, 1.0, -1.0, 0.5, 2.0, 4.0, 8.0, 16.0, 64.0, 256.0, 1024.0,
3570
+ ],
3571
+ 0.5,
3572
+ 0.25,
3573
+ );
3574
+ check_roundtrip::<e2m3>(
3575
+ &[
3576
+ 0.0, 1.0, -1.0, 0.5, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 7.5, 0.25, -0.25, 0.125, -0.125,
3577
+ -4.0, -6.0, -7.5,
3578
+ ],
3579
+ 0.25,
3580
+ 0.125,
3581
+ );
3582
+ check_roundtrip::<e3m2>(
3583
+ &[
3584
+ 0.0, 1.0, -1.0, 0.5, 2.0, 4.0, 8.0, 16.0, 20.0, 24.0, 28.0, 0.25, -0.25, -20.0,
3585
+ -28.0,
3586
+ ],
3587
+ 0.5,
3588
+ 0.25,
3589
+ );
3590
+ }
3591
+
3592
+ #[test]
3593
+ fn edge_cases() {
3594
+ check_edge_cases::<f16>();
3595
+ check_edge_cases::<bf16>();
3596
+ check_edge_cases::<e4m3>();
3597
+ check_edge_cases::<e5m2>();
3598
+ check_edge_cases::<e2m3>();
3599
+ check_edge_cases::<e3m2>();
3600
+ }
3601
+
3602
+ #[test]
3603
+ fn subnormals() {
3604
+ check_subnormals::<f16>(&[1e-5, 1e-6, 1e-7, 5e-6, 5e-7], 1e-4);
3605
+ check_subnormals::<bf16>(&[1e-39, 1e-40, 1e-42], 1e-37);
3606
+ check_subnormals::<e4m3>(&[0.001, 0.0005], 0.002);
3607
+ check_subnormals::<e5m2>(&[0.00005, 0.00003, 0.00001], 0.0001);
3608
+ check_subnormals::<e2m3>(&[0.03, 0.015], 0.07);
3609
+ check_subnormals::<e3m2>(&[0.0625, 0.03], 0.15);
3610
+ }
3611
+
3612
+ #[test]
3613
+ fn half_crate_interop() {
3614
+ use half::bf16 as HalfBF16;
3615
+ use half::f16 as HalfF16;
3616
+
3617
+ // f16: all 65536 bit patterns
3618
+ for bits in 0u16..=u16::MAX {
3619
+ let half_val = HalfF16::from_bits(bits).to_f32();
3620
+ let nk_val = f16(bits).to_f32();
3621
+ assert!(
3622
+ half_val.to_bits() == nk_val.to_bits() || (half_val.is_nan() && nk_val.is_nan()),
3623
+ "f16 mismatch at bits 0x{bits:04X}: half={half_val}, numkong={nk_val}"
3624
+ );
3625
+ }
3626
+
3627
+ // bf16: all 65536 bit patterns
3628
+ for bits in 0u16..=u16::MAX {
3629
+ let half_val = HalfBF16::from_bits(bits).to_f32();
3630
+ let nk_val = bf16(bits).to_f32();
3631
+ assert!(
3632
+ half_val.to_bits() == nk_val.to_bits() || (half_val.is_nan() && nk_val.is_nan()),
3633
+ "bf16 mismatch at bits 0x{bits:04X}: half={half_val}, numkong={nk_val}"
3634
+ );
3635
+ }
3636
+ }
3637
+
3638
+ #[test]
3639
+ fn is_close_exact() {
3640
+ assert!(is_close(1.0, 1.0, 0.0, 0.0));
3641
+ assert!(is_close(0.0, 0.0, 0.0, 0.0));
3642
+ assert!(is_close(-5.0, -5.0, 0.0, 0.0));
3643
+ }
3644
+
3645
+ #[test]
3646
+ fn is_close_within_atol() {
3647
+ assert!(is_close(1.0, 1.0 + 1e-7, 1e-6, 0.0));
3648
+ assert!(!is_close(1.0, 1.0 + 1e-5, 1e-6, 0.0));
3649
+ }
3650
+
3651
+ #[test]
3652
+ fn is_close_within_rtol() {
3653
+ assert!(is_close(100.0, 100.01, 0.0, 1e-3));
3654
+ assert!(!is_close(100.0, 100.2, 0.0, 1e-3));
3655
+ }
3656
+
3657
+ #[test]
3658
+ fn is_close_nan_inf() {
3659
+ // NaN is never close to anything (including itself).
3660
+ assert!(!is_close(f64::NAN, f64::NAN, 1e-6, 1e-6));
3661
+ assert!(!is_close(f64::NAN, 0.0, 1e-6, 1e-6));
3662
+ // INF - INF = NaN, so same-sign infinities are not "close" either.
3663
+ assert!(!is_close(f64::INFINITY, f64::INFINITY, 0.0, 0.0));
3664
+ assert!(!is_close(f64::INFINITY, f64::NEG_INFINITY, 0.0, 0.0));
3665
+ }
3666
+ }