usearch 2.8.13 → 2.8.14
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.
- package/CITATION.cff +1 -1
- package/Cargo.toml +1 -1
- package/README.md +1 -1
- package/VERSION +1 -1
- package/conanfile.py +1 -1
- package/include/usearch/index.hpp +1 -1
- package/package.json +1 -1
- package/simsimd/README.md +1 -1
- package/simsimd/include/simsimd/probability.h +5 -2
- package/simsimd/include/simsimd/simsimd.h +24 -1
- package/simsimd/include/simsimd/spatial.h +172 -2
- package/simsimd/package.json +20 -3
- package/wasmer.toml +1 -1
package/CITATION.cff
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.8.
|
|
1
|
+
2.8.14
|
package/conanfile.py
CHANGED
|
@@ -7,7 +7,7 @@ required_conan_version = ">=1.53.0"
|
|
|
7
7
|
class USearchConan(ConanFile):
|
|
8
8
|
|
|
9
9
|
name = "usearch"
|
|
10
|
-
version = '2.8.
|
|
10
|
+
version = '2.8.14'
|
|
11
11
|
license = "Apache-2.0"
|
|
12
12
|
description = "Smaller & Faster Single-File Vector Search Engine from Unum"
|
|
13
13
|
homepage = "https://github.com/unum-cloud/usearch"
|
package/package.json
CHANGED
package/simsimd/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SimSIMD 📏
|
|
2
2
|
|
|
3
3
|
<div>
|
|
4
|
-
<a href="https://
|
|
4
|
+
<a href="https://pepy.tech/project/simsimd"> <img alt="PyPI" src="https://static.pepy.tech/personalized-badge/simsimd?period=month&units=abbreviation&left_color=black&right_color=blue&left_text=Python%20monthly%20installs"> </a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/simsimd"> <img alt="npm" src="https://img.shields.io/npm/dy/simsimd?label=npm%20dowloads"> </a>
|
|
6
6
|
<img alt="GitHub code size in bytes" src="https://img.shields.io/github/languages/code-size/ashvardanian/simsimd">
|
|
7
7
|
</div>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
simsimd_##accumulator_type##_t bi = converter(b[i]); \
|
|
33
33
|
d += ai * SIMSIMD_LOG((ai + epsilon) / (bi + epsilon)); \
|
|
34
34
|
} \
|
|
35
|
-
return d;
|
|
35
|
+
return (simsimd_f32_t)d; \
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
#define SIMSIMD_MAKE_JS(name, input_type, accumulator_type, converter, epsilon) \
|
|
@@ -46,13 +46,16 @@
|
|
|
46
46
|
d += ai * SIMSIMD_LOG((ai + epsilon) / (mi + epsilon)); \
|
|
47
47
|
d += bi * SIMSIMD_LOG((bi + epsilon) / (mi + epsilon)); \
|
|
48
48
|
} \
|
|
49
|
-
return d / 2;
|
|
49
|
+
return (simsimd_f32_t)d / 2; \
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
#ifdef __cplusplus
|
|
53
53
|
extern "C" {
|
|
54
54
|
#endif
|
|
55
55
|
|
|
56
|
+
SIMSIMD_MAKE_KL(serial, f64, f64, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f64_kl
|
|
57
|
+
SIMSIMD_MAKE_JS(serial, f64, f64, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f64_js
|
|
58
|
+
|
|
56
59
|
SIMSIMD_MAKE_KL(serial, f32, f32, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f32_kl
|
|
57
60
|
SIMSIMD_MAKE_JS(serial, f32, f32, SIMSIMD_IDENTIFY, SIMSIMD_F32_DIVISION_EPSILON) // simsimd_serial_f32_js
|
|
58
61
|
|
|
@@ -221,7 +221,30 @@ inline static void simsimd_find_metric_punned( //
|
|
|
221
221
|
switch (datatype) {
|
|
222
222
|
|
|
223
223
|
case simsimd_datatype_unknown_k: break;
|
|
224
|
-
|
|
224
|
+
|
|
225
|
+
// Double-precision floating-point vectors
|
|
226
|
+
case simsimd_datatype_f64_k:
|
|
227
|
+
|
|
228
|
+
#if SIMSIMD_TARGET_X86_AVX512
|
|
229
|
+
if (viable & simsimd_cap_x86_avx512_k)
|
|
230
|
+
switch (kind) {
|
|
231
|
+
case simsimd_metric_ip_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_f64_ip, *c = simsimd_cap_x86_avx512_k; return;
|
|
232
|
+
case simsimd_metric_cos_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_f64_cos, *c = simsimd_cap_x86_avx512_k; return;
|
|
233
|
+
case simsimd_metric_l2sq_k: *m = (simsimd_metric_punned_t)&simsimd_avx512_f64_l2sq, *c = simsimd_cap_x86_avx512_k; return;
|
|
234
|
+
default: break;
|
|
235
|
+
}
|
|
236
|
+
#endif
|
|
237
|
+
if (viable & simsimd_cap_serial_k)
|
|
238
|
+
switch (kind) {
|
|
239
|
+
case simsimd_metric_ip_k: *m = (simsimd_metric_punned_t)&simsimd_serial_f64_ip, *c = simsimd_cap_serial_k; return;
|
|
240
|
+
case simsimd_metric_cos_k: *m = (simsimd_metric_punned_t)&simsimd_serial_f64_cos, *c = simsimd_cap_serial_k; return;
|
|
241
|
+
case simsimd_metric_l2sq_k: *m = (simsimd_metric_punned_t)&simsimd_serial_f64_l2sq, *c = simsimd_cap_serial_k; return;
|
|
242
|
+
case simsimd_metric_js_k: *m = (simsimd_metric_punned_t)&simsimd_serial_f64_js, *c = simsimd_cap_serial_k; return;
|
|
243
|
+
case simsimd_metric_kl_k: *m = (simsimd_metric_punned_t)&simsimd_serial_f64_kl, *c = simsimd_cap_serial_k; return;
|
|
244
|
+
default: break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
break;
|
|
225
248
|
|
|
226
249
|
// Single-precision floating-point vectors
|
|
227
250
|
case simsimd_datatype_f32_k:
|
|
@@ -67,6 +67,10 @@
|
|
|
67
67
|
extern "C" {
|
|
68
68
|
#endif
|
|
69
69
|
|
|
70
|
+
SIMSIMD_MAKE_L2SQ(serial, f64, f64, SIMSIMD_IDENTIFY) // simsimd_serial_f64_l2sq
|
|
71
|
+
SIMSIMD_MAKE_IP(serial, f64, f64, SIMSIMD_IDENTIFY) // simsimd_serial_f64_ip
|
|
72
|
+
SIMSIMD_MAKE_COS(serial, f64, f64, SIMSIMD_IDENTIFY) // simsimd_serial_f64_cos
|
|
73
|
+
|
|
70
74
|
SIMSIMD_MAKE_L2SQ(serial, f32, f32, SIMSIMD_IDENTIFY) // simsimd_serial_f32_l2sq
|
|
71
75
|
SIMSIMD_MAKE_IP(serial, f32, f32, SIMSIMD_IDENTIFY) // simsimd_serial_f32_ip
|
|
72
76
|
SIMSIMD_MAKE_COS(serial, f32, f32, SIMSIMD_IDENTIFY) // simsimd_serial_f32_cos
|
|
@@ -338,7 +342,7 @@ simsimd_neon_i8_ip(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_t
|
|
|
338
342
|
* @author Ash Vardanian
|
|
339
343
|
*
|
|
340
344
|
* - Implements: L2 squared, inner product, cosine similarity.
|
|
341
|
-
* - Uses `
|
|
345
|
+
* - Uses `f32` for both storage and accumulation.
|
|
342
346
|
* - Requires compiler capabilities: +sve.
|
|
343
347
|
*/
|
|
344
348
|
|
|
@@ -408,7 +412,7 @@ simsimd_sve_f32_cos(simsimd_f32_t const* a, simsimd_f32_t const* b, simsimd_size
|
|
|
408
412
|
* @author Ash Vardanian
|
|
409
413
|
*
|
|
410
414
|
* - Implements: L2 squared, inner product, cosine similarity.
|
|
411
|
-
* - Uses `f16` for
|
|
415
|
+
* - Uses `f16` for storage and `f32` for accumulation.
|
|
412
416
|
* - Requires compiler capabilities: +sve+fp16.
|
|
413
417
|
*/
|
|
414
418
|
|
|
@@ -479,6 +483,77 @@ simsimd_sve_f16_cos(simsimd_f16_t const* a_enum, simsimd_f16_t const* b_enum, si
|
|
|
479
483
|
return ab != 0 ? 1 - ab * a2_b2_arr[0] * a2_b2_arr[1] : 1;
|
|
480
484
|
}
|
|
481
485
|
|
|
486
|
+
/*
|
|
487
|
+
* @file arm_sve_f64.h
|
|
488
|
+
* @brief Arm SVE implementation of the most common similarity metrics for 64-bit floating point numbers.
|
|
489
|
+
* @author Ash Vardanian
|
|
490
|
+
*
|
|
491
|
+
* - Implements: L2 squared, inner product, cosine similarity.
|
|
492
|
+
* - Uses `f64` for both storage and for accumulation.
|
|
493
|
+
* - Requires compiler capabilities: +sve.
|
|
494
|
+
*/
|
|
495
|
+
|
|
496
|
+
__attribute__((target("+sve"))) //
|
|
497
|
+
inline static simsimd_f32_t
|
|
498
|
+
simsimd_sve_f64_l2sq(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n) {
|
|
499
|
+
simsimd_size_t i = 0;
|
|
500
|
+
svfloat64_t d2_vec = svdupq_n_f64(0.0, 0.0);
|
|
501
|
+
do {
|
|
502
|
+
svbool_t pg_vec = svwhilelt_b64((unsigned int)i, (unsigned int)n);
|
|
503
|
+
svfloat64_t a_vec = svld1_f64(pg_vec, a + i);
|
|
504
|
+
svfloat64_t b_vec = svld1_f64(pg_vec, b + i);
|
|
505
|
+
svfloat64_t a_minus_b_vec = svsub_f64_x(pg_vec, a_vec, b_vec);
|
|
506
|
+
d2_vec = svmla_f64_x(pg_vec, d2_vec, a_minus_b_vec, a_minus_b_vec);
|
|
507
|
+
i += svcntd();
|
|
508
|
+
} while (i < n);
|
|
509
|
+
simsimd_f64_t d2 = svaddv_f64(svptrue_b32(), d2_vec);
|
|
510
|
+
return d2;
|
|
511
|
+
}
|
|
512
|
+
__attribute__((target("+sve"))) //
|
|
513
|
+
inline static simsimd_f32_t
|
|
514
|
+
simsimd_sve_f64_ip(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n) {
|
|
515
|
+
simsimd_size_t i = 0;
|
|
516
|
+
svfloat64_t ab_vec = svdupq_n_f64(0.0, 0.0);
|
|
517
|
+
do {
|
|
518
|
+
svbool_t pg_vec = svwhilelt_b64((unsigned int)i, (unsigned int)n);
|
|
519
|
+
svfloat64_t a_vec = svld1_f64(pg_vec, a + i);
|
|
520
|
+
svfloat64_t b_vec = svld1_f64(pg_vec, b + i);
|
|
521
|
+
ab_vec = svmla_f64_x(pg_vec, ab_vec, a_vec, b_vec);
|
|
522
|
+
i += svcntd();
|
|
523
|
+
} while (i < n);
|
|
524
|
+
return 1 - svaddv_f64(svptrue_b32(), ab_vec);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
__attribute__((target("+sve"))) //
|
|
528
|
+
inline static simsimd_f32_t
|
|
529
|
+
simsimd_sve_f64_cos(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n) {
|
|
530
|
+
simsimd_size_t i = 0;
|
|
531
|
+
svfloat64_t ab_vec = svdupq_n_f64(0.0, 0.0);
|
|
532
|
+
svfloat64_t a2_vec = svdupq_n_f64(0.0, 0.0);
|
|
533
|
+
svfloat64_t b2_vec = svdupq_n_f64(0.0, 0.0);
|
|
534
|
+
do {
|
|
535
|
+
svbool_t pg_vec = svwhilelt_b64((unsigned int)i, (unsigned int)n);
|
|
536
|
+
svfloat64_t a_vec = svld1_f64(pg_vec, a + i);
|
|
537
|
+
svfloat64_t b_vec = svld1_f64(pg_vec, b + i);
|
|
538
|
+
ab_vec = svmla_f64_x(pg_vec, ab_vec, a_vec, b_vec);
|
|
539
|
+
a2_vec = svmla_f64_x(pg_vec, a2_vec, a_vec, a_vec);
|
|
540
|
+
b2_vec = svmla_f64_x(pg_vec, b2_vec, b_vec, b_vec);
|
|
541
|
+
i += svcntd();
|
|
542
|
+
} while (i < n);
|
|
543
|
+
|
|
544
|
+
simsimd_f64_t ab = svaddv_f64(svptrue_b32(), ab_vec);
|
|
545
|
+
simsimd_f64_t a2 = svaddv_f64(svptrue_b32(), a2_vec);
|
|
546
|
+
simsimd_f64_t b2 = svaddv_f64(svptrue_b32(), b2_vec);
|
|
547
|
+
|
|
548
|
+
// Avoid `simsimd_approximate_inverse_square_root` on Arm NEON
|
|
549
|
+
simsimd_f64_t a2_b2_arr[2] = {a2, b2};
|
|
550
|
+
float64x2_t a2_b2 = vld1q_f64(a2_b2_arr);
|
|
551
|
+
a2_b2 = vrsqrteq_f64(a2_b2);
|
|
552
|
+
vst1q_f64(a2_b2_arr, a2_b2);
|
|
553
|
+
return ab != 0 ? 1 - ab * a2_b2_arr[0] * a2_b2_arr[1] : 1;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
482
557
|
#endif // SIMSIMD_TARGET_ARM_SVE
|
|
483
558
|
#endif // SIMSIMD_TARGET_ARM
|
|
484
559
|
|
|
@@ -996,6 +1071,101 @@ simsimd_avx512_i8_ip(simsimd_i8_t const* a, simsimd_i8_t const* b, simsimd_size_
|
|
|
996
1071
|
return simsimd_avx512_i8_cos(a, b, n);
|
|
997
1072
|
}
|
|
998
1073
|
|
|
1074
|
+
/*
|
|
1075
|
+
* @file x86_avx512_f64.h
|
|
1076
|
+
* @brief x86 AVX-512 implementation of the most common similarity metrics for 64-bit floating point numbers.
|
|
1077
|
+
* @author Ash Vardanian
|
|
1078
|
+
*
|
|
1079
|
+
* - Implements: L2 squared, inner product, cosine similarity.
|
|
1080
|
+
* - Uses `f64` for both storage and accumulation.
|
|
1081
|
+
* - Requires compiler capabilities: avx512f, avx512vl, bmi2.
|
|
1082
|
+
*/
|
|
1083
|
+
|
|
1084
|
+
__attribute__((target("avx512f,avx512vl,bmi2"))) //
|
|
1085
|
+
inline static simsimd_f32_t
|
|
1086
|
+
simsimd_avx512_f64_l2sq(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n) {
|
|
1087
|
+
__m512d d2_vec = _mm512_set1_pd(0);
|
|
1088
|
+
__m512d a_vec, b_vec;
|
|
1089
|
+
|
|
1090
|
+
simsimd_avx512_f64_l2sq_cycle:
|
|
1091
|
+
if (n < 8) {
|
|
1092
|
+
__mmask8 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
1093
|
+
a_vec = _mm512_maskz_loadu_pd(mask, a);
|
|
1094
|
+
b_vec = _mm512_maskz_loadu_pd(mask, b);
|
|
1095
|
+
n = 0;
|
|
1096
|
+
} else {
|
|
1097
|
+
a_vec = _mm512_loadu_pd(a);
|
|
1098
|
+
b_vec = _mm512_loadu_pd(b);
|
|
1099
|
+
a += 8, b += 8, n -= 8;
|
|
1100
|
+
}
|
|
1101
|
+
__m512d d_vec = _mm512_sub_pd(a_vec, b_vec);
|
|
1102
|
+
d2_vec = _mm512_fmadd_pd(d_vec, d_vec, d2_vec);
|
|
1103
|
+
if (n)
|
|
1104
|
+
goto simsimd_avx512_f64_l2sq_cycle;
|
|
1105
|
+
|
|
1106
|
+
return (simsimd_f32_t)_mm512_reduce_add_pd(d2_vec);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
__attribute__((target("avx512f,avx512vl,bmi2"))) //
|
|
1110
|
+
inline static simsimd_f32_t
|
|
1111
|
+
simsimd_avx512_f64_ip(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n) {
|
|
1112
|
+
__m512d ab_vec = _mm512_set1_pd(0);
|
|
1113
|
+
__m512d a_vec, b_vec;
|
|
1114
|
+
|
|
1115
|
+
simsimd_avx512_f64_ip_cycle:
|
|
1116
|
+
if (n < 8) {
|
|
1117
|
+
__mmask8 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
1118
|
+
a_vec = _mm512_maskz_loadu_pd(mask, a);
|
|
1119
|
+
b_vec = _mm512_maskz_loadu_pd(mask, b);
|
|
1120
|
+
n = 0;
|
|
1121
|
+
} else {
|
|
1122
|
+
a_vec = _mm512_loadu_pd(a);
|
|
1123
|
+
b_vec = _mm512_loadu_pd(b);
|
|
1124
|
+
a += 8, b += 8, n -= 8;
|
|
1125
|
+
}
|
|
1126
|
+
ab_vec = _mm512_fmadd_pd(a_vec, b_vec, ab_vec);
|
|
1127
|
+
if (n)
|
|
1128
|
+
goto simsimd_avx512_f64_ip_cycle;
|
|
1129
|
+
|
|
1130
|
+
return 1 - (simsimd_f32_t)_mm512_reduce_add_pd(ab_vec);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
__attribute__((target("avx512f,avx512vl,bmi2"))) //
|
|
1134
|
+
inline static simsimd_f32_t
|
|
1135
|
+
simsimd_avx512_f64_cos(simsimd_f64_t const* a, simsimd_f64_t const* b, simsimd_size_t n) {
|
|
1136
|
+
__m512d ab_vec = _mm512_set1_pd(0);
|
|
1137
|
+
__m512d a2_vec = _mm512_set1_pd(0);
|
|
1138
|
+
__m512d b2_vec = _mm512_set1_pd(0);
|
|
1139
|
+
__m512d a_vec, b_vec;
|
|
1140
|
+
|
|
1141
|
+
simsimd_avx512_f64_cos_cycle:
|
|
1142
|
+
if (n < 8) {
|
|
1143
|
+
__mmask8 mask = _bzhi_u32(0xFFFFFFFF, n);
|
|
1144
|
+
a_vec = _mm512_maskz_loadu_pd(mask, a);
|
|
1145
|
+
b_vec = _mm512_maskz_loadu_pd(mask, b);
|
|
1146
|
+
n = 0;
|
|
1147
|
+
} else {
|
|
1148
|
+
a_vec = _mm512_loadu_pd(a);
|
|
1149
|
+
b_vec = _mm512_loadu_pd(b);
|
|
1150
|
+
a += 8, b += 8, n -= 8;
|
|
1151
|
+
}
|
|
1152
|
+
ab_vec = _mm512_fmadd_pd(a_vec, b_vec, ab_vec);
|
|
1153
|
+
a2_vec = _mm512_fmadd_pd(a_vec, a_vec, a2_vec);
|
|
1154
|
+
b2_vec = _mm512_fmadd_pd(b_vec, b_vec, b2_vec);
|
|
1155
|
+
if (n)
|
|
1156
|
+
goto simsimd_avx512_f64_cos_cycle;
|
|
1157
|
+
|
|
1158
|
+
simsimd_f32_t ab = (simsimd_f32_t)_mm512_reduce_add_pd(ab_vec);
|
|
1159
|
+
simsimd_f32_t a2 = (simsimd_f32_t)_mm512_reduce_add_pd(a2_vec);
|
|
1160
|
+
simsimd_f32_t b2 = (simsimd_f32_t)_mm512_reduce_add_pd(b2_vec);
|
|
1161
|
+
|
|
1162
|
+
// Compute the reciprocal square roots of a2 and b2
|
|
1163
|
+
__m128 rsqrts = _mm_rsqrt14_ps(_mm_set_ps(0.f, 0.f, a2 + 1.e-9f, b2 + 1.e-9f));
|
|
1164
|
+
simsimd_f32_t rsqrt_a2 = _mm_cvtss_f32(rsqrts);
|
|
1165
|
+
simsimd_f32_t rsqrt_b2 = _mm_cvtss_f32(_mm_shuffle_ps(rsqrts, rsqrts, _MM_SHUFFLE(0, 0, 0, 1)));
|
|
1166
|
+
return 1 - ab * rsqrt_a2 * rsqrt_b2;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
999
1169
|
#endif // SIMSIMD_TARGET_X86_AVX512
|
|
1000
1170
|
#endif // SIMSIMD_TARGET_X86
|
|
1001
1171
|
|
package/simsimd/package.json
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simsimd",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "Vector Similarity Functions
|
|
3
|
+
"version": "3.6.0",
|
|
4
|
+
"description": "Fastest SIMD-Accelerated Vector Similarity Functions for x86 and Arm",
|
|
5
|
+
"homepage": "https://github.com/ashvardanian/simsimd",
|
|
5
6
|
"author": "Ash Vardanian",
|
|
6
7
|
"license": "Apache 2.0",
|
|
7
8
|
"main": "javascript/simsimd.js",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"vector",
|
|
11
|
+
"distance",
|
|
12
|
+
"similarity",
|
|
13
|
+
"search",
|
|
14
|
+
"ai",
|
|
15
|
+
"math",
|
|
16
|
+
"avx2",
|
|
17
|
+
"avx512",
|
|
18
|
+
"neon",
|
|
19
|
+
"sve",
|
|
20
|
+
"arm",
|
|
21
|
+
"x86",
|
|
22
|
+
"simd",
|
|
23
|
+
"f16"
|
|
24
|
+
],
|
|
8
25
|
"type": "module",
|
|
9
26
|
"scripts": {
|
|
10
27
|
"test": "node --test ./javascript/test",
|
|
@@ -26,4 +43,4 @@
|
|
|
26
43
|
"typescript": "^5.1.6",
|
|
27
44
|
"usearch": "^2.7.2"
|
|
28
45
|
}
|
|
29
|
-
}
|
|
46
|
+
}
|