carray 3.0.1 → 3.0.3

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 (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +824 -0
  3. data/README.md +5 -6
  4. data/carray.gemspec +1 -1
  5. data/ext/ca_axis_dispatch.c +258 -4
  6. data/ext/ca_axis_group.c +368 -184
  7. data/ext/ca_binop_dispatch.c +54 -32
  8. data/ext/ca_binop_dispatch.h +1 -0
  9. data/ext/ca_categorical_iterator.c +90 -41
  10. data/ext/ca_compare.c +8 -2
  11. data/ext/ca_for_buffer.h +21 -68
  12. data/ext/ca_group_iter.c +27 -11
  13. data/ext/ca_kernel_iterator.c +960 -245
  14. data/ext/ca_kernel_iterator.h +352 -181
  15. data/ext/ca_monop_dispatch.c +4 -4
  16. data/ext/ca_monop_dispatch.h +4 -0
  17. data/ext/ca_obj_array.c +194 -42
  18. data/ext/ca_obj_bincmp.c +152 -121
  19. data/ext/ca_obj_binop.c +107 -210
  20. data/ext/ca_obj_bitarray.c +37 -24
  21. data/ext/ca_obj_bitfield.c +95 -62
  22. data/ext/ca_obj_block.c +5 -4
  23. data/ext/ca_obj_byte_swap.c +40 -40
  24. data/ext/ca_obj_const_string.c +180 -50
  25. data/ext/ca_obj_face.c +428 -62
  26. data/ext/ca_obj_face.h +107 -47
  27. data/ext/ca_obj_fake.c +21 -50
  28. data/ext/ca_obj_farray.c +1 -0
  29. data/ext/ca_obj_field.c +16 -3
  30. data/ext/ca_obj_fixlen_string.c +39 -26
  31. data/ext/ca_obj_grid.c +181 -124
  32. data/ext/ca_obj_meld.c +235 -55
  33. data/ext/ca_obj_moncmp.c +61 -61
  34. data/ext/ca_obj_monop.c +125 -107
  35. data/ext/ca_obj_object.c +551 -233
  36. data/ext/ca_obj_record.c +51 -16
  37. data/ext/ca_obj_reduce.c +5 -8
  38. data/ext/ca_obj_refer.c +117 -33
  39. data/ext/ca_obj_remap.c +57 -17
  40. data/ext/ca_obj_repeat.c +21 -5
  41. data/ext/ca_obj_roll.c +115 -11
  42. data/ext/ca_obj_select.c +88 -75
  43. data/ext/ca_obj_select_axis.c +58 -60
  44. data/ext/ca_obj_shift.c +22 -102
  45. data/ext/ca_obj_stack.c +223 -123
  46. data/ext/ca_obj_stride.c +267 -35
  47. data/ext/ca_obj_string.c +25 -22
  48. data/ext/ca_obj_tile.c +32 -16
  49. data/ext/ca_obj_time.c +23 -25
  50. data/ext/ca_obj_timedelta.c +21 -23
  51. data/ext/ca_obj_transpose.c +12 -3
  52. data/ext/ca_obj_triop.c +110 -117
  53. data/ext/ca_obj_window.c +157 -29
  54. data/ext/ca_op_ipower.c +25 -13
  55. data/ext/ca_op_powi.h +57 -30
  56. data/ext/ca_pow5_table.h +656 -0
  57. data/ext/ca_rng_normal.h +42 -0
  58. data/ext/ca_rng_xoshiro256pp.h +105 -0
  59. data/ext/ca_sort_kernels.h +70 -0
  60. data/ext/ca_sweep_engine.c +385 -198
  61. data/ext/ca_sweep_engine.h +31 -23
  62. data/ext/ca_triop_dispatch.c +4 -1
  63. data/ext/ca_triop_dispatch.h +5 -2
  64. data/ext/caframe_csv_reader.c +463 -0
  65. data/ext/carray.h +90 -28
  66. data/ext/carray_access.c +487 -231
  67. data/ext/carray_address_basis.c +731 -0
  68. data/ext/carray_attribute.c +45 -3
  69. data/ext/carray_bincount.c +32 -2
  70. data/ext/carray_broadcast.c +49 -17
  71. data/ext/carray_call_cfunc.c +667 -511
  72. data/ext/carray_cast.c +1556 -247
  73. data/ext/carray_cast_func.rb +34 -21
  74. data/ext/carray_conversion.c +197 -73
  75. data/ext/carray_copy.c +84 -38
  76. data/ext/carray_core.c +591 -119
  77. data/ext/carray_count.c +148 -96
  78. data/ext/carray_element.c +18 -27
  79. data/ext/carray_factorize.c +604 -291
  80. data/ext/carray_generate.c +76 -40
  81. data/ext/carray_histogram.c +152 -94
  82. data/ext/carray_hold.c +2 -1
  83. data/ext/carray_index_classifier.c +63 -96
  84. data/ext/carray_internal.h +400 -1
  85. data/ext/carray_lazy.c +834 -79
  86. data/ext/carray_loop.c +88 -66
  87. data/ext/carray_mask.c +705 -324
  88. data/ext/carray_math_kernel.h +1 -0
  89. data/ext/carray_median_percentile.c +263 -63
  90. data/ext/carray_memory_view.c +254 -248
  91. data/ext/carray_operator.c +1093 -363
  92. data/ext/carray_order.c +133 -69
  93. data/ext/carray_partition.c +102 -56
  94. data/ext/carray_random.c +582 -204
  95. data/ext/carray_scatter.c +308 -65
  96. data/ext/carray_segment.c +257 -0
  97. data/ext/carray_slab.c +183 -70
  98. data/ext/carray_sort.c +235 -240
  99. data/ext/carray_sort_kernel.c +8 -1
  100. data/ext/carray_struct.c +99 -21
  101. data/ext/carray_test.c +167 -64
  102. data/ext/carray_time_text.c +1182 -0
  103. data/ext/carray_utils.c +363 -98
  104. data/ext/extconf.rb +27 -2
  105. data/ext/mk_call_cfunc.rb +103 -118
  106. data/ext/mk_pow5_table.rb +35 -0
  107. data/ext/mkkernel.rb +1349 -851
  108. data/ext/ruby_carray.c +17 -2
  109. data/ext/version.h +4 -4
  110. data/ext/version.rb +4 -2
  111. data/lib/carray/attribute.rb +39 -0
  112. data/lib/carray/autoload_carray.rb +24 -3
  113. data/lib/carray/autoload_method_extension.rb +18 -0
  114. data/lib/carray/axis_group.rb +245 -20
  115. data/lib/carray/basics.rb +95 -25
  116. data/lib/carray/bincount_nd.rb +6 -0
  117. data/lib/carray/block_iterator.rb +200 -54
  118. data/lib/carray/boolean_reduce.rb +21 -29
  119. data/lib/carray/categorical.rb +244 -59
  120. data/lib/carray/categorical_iterator.rb +391 -757
  121. data/lib/carray/complex.rb +36 -29
  122. data/lib/carray/conditional.rb +94 -7
  123. data/lib/carray/const_string.rb +177 -27
  124. data/lib/carray/construct.rb +40 -0
  125. data/lib/carray/data_type_extension.rb +3 -0
  126. data/lib/carray/data_type_limits.rb +91 -0
  127. data/lib/carray/fixlen_string.rb +1 -1
  128. data/lib/carray/frame/concat.rb +157 -6
  129. data/lib/carray/frame/convert.rb +13 -7
  130. data/lib/carray/frame/csv_parser.rb +590 -76
  131. data/lib/carray/frame/describe.rb +79 -0
  132. data/lib/carray/frame/frame.rb +205 -35
  133. data/lib/carray/frame/group.rb +199 -40
  134. data/lib/carray/frame/io.rb +452 -60
  135. data/lib/carray/frame/join.rb +134 -46
  136. data/lib/carray/frame/records.rb +54 -19
  137. data/lib/carray/frame/reshape.rb +448 -0
  138. data/lib/carray/frame/sort.rb +26 -2
  139. data/lib/carray/frame/verbs.rb +726 -56
  140. data/lib/carray/frame.rb +2 -0
  141. data/lib/carray/fuse_source.rb +111 -2
  142. data/lib/carray/fusion.rb +229 -38
  143. data/lib/carray/inspect.rb +71 -20
  144. data/lib/carray/iterator.rb +157 -2
  145. data/lib/carray/lazy.rb +54 -67
  146. data/lib/carray/mask_gap_fill.rb +66 -50
  147. data/lib/carray/math.rb +32 -19
  148. data/lib/carray/meld_reduce.rb +40 -7
  149. data/lib/carray/methods/align_addr.rb +8 -4
  150. data/lib/carray/methods/bincount.rb +2 -2
  151. data/lib/carray/methods/bit_string.rb +14 -3
  152. data/lib/carray/methods/composition.rb +16 -10
  153. data/lib/carray/methods/discovery_along.rb +74 -0
  154. data/lib/carray/methods/factorize.rb +50 -0
  155. data/lib/carray/methods/gather_nd.rb +13 -2
  156. data/lib/carray/methods/insert_block.rb +9 -14
  157. data/lib/carray/methods/is_in.rb +130 -6
  158. data/lib/carray/methods/join.rb +1 -1
  159. data/lib/carray/methods/locate_addr.rb +235 -21
  160. data/lib/carray/methods/mask_duplicates.rb +35 -1
  161. data/lib/carray/methods/mode.rb +18 -1
  162. data/lib/carray/methods/nunique.rb +22 -1
  163. data/lib/carray/methods/repeat.rb +91 -0
  164. data/lib/carray/methods/resize.rb +21 -23
  165. data/lib/carray/methods/snap.rb +34 -12
  166. data/lib/carray/methods/unique.rb +52 -3
  167. data/lib/carray/record_order.rb +208 -0
  168. data/lib/carray/rng.rb +86 -0
  169. data/lib/carray/runtime.rb +14 -11
  170. data/lib/carray/segment_iterator.rb +990 -0
  171. data/lib/carray/serialize.rb +197 -27
  172. data/lib/carray/slab_iterator.rb +109 -40
  173. data/lib/carray/stack.rb +10 -4
  174. data/lib/carray/string_operation_extension.rb +9 -4
  175. data/lib/carray/struct.rb +142 -51
  176. data/lib/carray/struct_builder.rb +110 -14
  177. data/lib/carray/table.rb +2 -2
  178. data/lib/carray/time.rb +535 -105
  179. data/lib/carray/window_iterator.rb +259 -82
  180. data/lib/carray.rb +4 -2
  181. data/yard-stubs/ca_obj_array.rb +4 -4
  182. data/yard-stubs/ca_obj_bitarray.rb +7 -0
  183. data/yard-stubs/ca_obj_bitfield.rb +23 -12
  184. data/yard-stubs/ca_obj_block.rb +2 -7
  185. data/yard-stubs/ca_obj_fake.rb +1 -1
  186. data/yard-stubs/ca_obj_field.rb +1 -1
  187. data/yard-stubs/ca_obj_refer.rb +3 -3
  188. data/yard-stubs/ca_obj_roll.rb +0 -1
  189. data/yard-stubs/ca_obj_stride.rb +10 -7
  190. data/yard-stubs/ca_obj_transpose.rb +1 -1
  191. data/yard-stubs/ca_obj_window.rb +11 -4
  192. data/yard-stubs/carray_access.rb +6 -5
  193. data/yard-stubs/carray_broadcast.rb +2 -2
  194. data/yard-stubs/carray_cast.rb +78 -43
  195. data/yard-stubs/carray_conversion.rb +13 -1
  196. data/yard-stubs/carray_copy.rb +8 -3
  197. data/yard-stubs/carray_core.rb +3 -83
  198. data/yard-stubs/carray_count.rb +16 -2
  199. data/yard-stubs/carray_lazy.rb +210 -1
  200. data/yard-stubs/carray_loop.rb +4 -1
  201. data/yard-stubs/carray_mask.rb +34 -10
  202. data/yard-stubs/carray_math.rb +1499 -4
  203. data/yard-stubs/carray_median_percentile.rb +26 -8
  204. data/yard-stubs/carray_order.rb +43 -82
  205. data/yard-stubs/carray_scatter.rb +10 -3
  206. data/yard-stubs/carray_segment.rb +72 -0
  207. data/yard-stubs/carray_slab.rb +11 -8
  208. data/yard-stubs/carray_sort.rb +50 -8
  209. data/yard-stubs/carray_test.rb +10 -0
  210. data/yard-stubs/carray_utils.rb +0 -26
  211. metadata +19 -19
  212. data/ext/ca_for_each_element.h +0 -239
  213. data/ext/carray_build_flags.h +0 -3
  214. data/ext/carray_config.h +0 -28
  215. data/ext/carray_kernels_bincmp.c +0 -4446
  216. data/ext/carray_kernels_binop.c +0 -11001
  217. data/ext/carray_kernels_init.c +0 -1131
  218. data/ext/carray_kernels_map.c +0 -3467
  219. data/ext/carray_kernels_moncmp.c +0 -2097
  220. data/ext/carray_kernels_monop.c +0 -18313
  221. data/ext/carray_kernels_reduce_aggregate.c +0 -25837
  222. data/ext/carray_kernels_reduce_boolean.c +0 -330
  223. data/ext/carray_kernels_reduce_cumulative.c +0 -14593
  224. data/ext/carray_kernels_reduce_extreme.c +0 -16948
  225. data/ext/carray_kernels_reduce_variance.c +0 -3910
  226. data/ext/carray_kernels_scan.c +0 -3693
  227. data/ext/carray_kernels_search.c +0 -32138
  228. data/ext/carray_kernels_sort.c +0 -10626
  229. data/ext/carray_kernels_triop.c +0 -1392
@@ -107,8 +107,8 @@ FLOAT = [
107
107
  CA_FLOAT64 ,
108
108
  ]
109
109
 
110
- # object -> int/float cast: a parse failure masks the output cell (UNDEF)
111
- # instead of a silent 0.0 (float) or a raise (int). These parsers report
110
+ # object -> int/float/complex cast: a parse failure masks the output cell
111
+ # (UNDEF) instead of raising. These parsers report
112
112
  # failure through an out-param whose C type is `oktype`.
113
113
  obj2cval_ok = {
114
114
  CA_INT8 => "rb_obj2long_ok",
@@ -121,6 +121,8 @@ obj2cval_ok = {
121
121
  CA_UINT64 => "rb_obj2ull_ok",
122
122
  CA_FLOAT32 => "ca_obj2dbl_ok",
123
123
  CA_FLOAT64 => "ca_obj2dbl_ok",
124
+ CA_CMPLX64 => "ca_obj2cmplx_ok",
125
+ CA_CMPLX128 => "ca_obj2cmplx_ok",
124
126
  }
125
127
 
126
128
  oktype = {
@@ -134,6 +136,8 @@ oktype = {
134
136
  CA_UINT64 => "unsigned long long",
135
137
  CA_FLOAT32 => "double",
136
138
  CA_FLOAT64 => "double",
139
+ CA_CMPLX64 => "double complex",
140
+ CA_CMPLX128 => "double complex",
137
141
  }
138
142
 
139
143
  BOOLEAN = [
@@ -172,6 +176,7 @@ puts %{
172
176
  ---------------------------------------------------------------------------- */
173
177
 
174
178
  #include "carray.h"
179
+ #include "carray_internal.h" /* ca_decimal_string_to / _store */
175
180
 
176
181
  static void __attribute__((noreturn))
177
182
  ca_cast_not_implemented(ca_size_t n, CArray *a1, void *ptr1, CArray *a2, void *ptr2, boolean8_t *m)
@@ -295,7 +300,7 @@ puts
295
300
  *p2 = q;
296
301
  }
297
302
  else {
298
- rb_raise(rb_eRuntimeError, "out of range to cast to boolean (0 or 1)");
303
+ rb_raise(rb_eRangeError, "out of range to cast to boolean (0 or 1)");
299
304
  }
300
305
  }
301
306
  p1++; p2++; m++;
@@ -308,7 +313,7 @@ puts
308
313
  *p2 = q;
309
314
  }
310
315
  else {
311
- rb_raise(rb_eRuntimeError, "out of range to cast to boolean (0 or 1)");
316
+ rb_raise(rb_eRangeError, "out of range to cast to boolean (0 or 1)");
312
317
  }
313
318
  p1++; p2++;
314
319
  }
@@ -456,17 +461,30 @@ OBJECT.each do |type1|
456
461
  ctype1 = ctype[type1]
457
462
  ctype2 = ctype[type2]
458
463
  CA_CAST_TABLE[type1][type2] = "ca_cast_#{ctype1}_#{ctype2}"
459
- if (INTEGER+FLOAT).include?(type2)
460
- # object -> int/float: parse failure -> masked (UNDEF) output cell.
461
- # The mask-aware loop runs only when a mask buffer is supplied (m);
462
- # #to_type always supplies one for these targets so the whole
463
- # user-facing object->numeric surface gets the masking semantics.
464
- # With no mask buffer (internal single-cell / view paths) the legacy
465
- # value parser keeps its old behaviour (lenient 0.0 float / raising
466
- # int), leaving those paths byte-identical.
464
+ if (INTEGER+FLOAT+[CA_CMPLX64, CA_CMPLX128]).include?(type2)
465
+ # object -> int/float/complex: parse failure -> masked (UNDEF) output
466
+ # cell. The mask-aware loop runs only when a mask buffer is supplied
467
+ # (m); #to_type always supplies one for these targets. With no mask
468
+ # buffer (a store, single-cell and view paths) the value parser
469
+ # raises for a cell it cannot read.
467
470
  conv_ok = obj2cval_ok[type2]
468
471
  conv = obj2cval[type2]
469
472
  oktype2 = oktype[type2]
473
+ # A String cell of an integer or float target is read as a decimal
474
+ # number (ca_decimal_string_to / _store in carray_cast.c), not as a
475
+ # Ruby literal. Complex keeps its own reader.
476
+ if (INTEGER+FLOAT).include?(type2)
477
+ type_name = "CA_" + ctype2.sub(/_t\z/, "").upcase
478
+ read_ok = "( RB_TYPE_P(*p1, T_STRING) ? " \
479
+ "ca_decimal_string_to(*p1, #{type_name}, p2) : " \
480
+ "( #{conv_ok}(*p1, &tmp) ? ( *p2 = (#{ctype2}) tmp, 1 ) : 0 ) )"
481
+ read = "if ( RB_TYPE_P(*p1, T_STRING) ) { " \
482
+ "ca_decimal_string_store(*p1, #{type_name}, p2); } " \
483
+ "else { *p2 = (#{ctype2}) #{conv}(*p1); }"
484
+ else
485
+ read_ok = "( #{conv_ok}(*p1, &tmp) ? ( *p2 = (#{ctype2}) tmp, 1 ) : 0 )"
486
+ read = "*p2 = (#{ctype2}) #{conv}(*p1);"
487
+ end
470
488
  puts <<-END_DEF .gsub(/^ {6}/, '')
471
489
  static void
472
490
  ca_cast_#{ctype1}_#{ctype2}(ca_size_t n, CArray *a1, void *ptr1, CArray *a2, void *ptr2, boolean8_t *m)
@@ -476,20 +494,15 @@ OBJECT.each do |type1|
476
494
  #{oktype2} tmp;
477
495
  if ( m ) {
478
496
  while ( n-- ) {
479
- if ( !*m ) {
480
- if ( #{conv_ok}(*p1, &tmp) ) {
481
- *p2 = (#{ctype2}) tmp;
482
- }
483
- else {
484
- *m = 1;
485
- *p2 = 0;
486
- }
497
+ if ( !*m && ! #{read_ok} ) {
498
+ *m = 1;
499
+ *p2 = 0;
487
500
  }
488
501
  p1++; p2++; m++;
489
502
  }
490
503
  }
491
504
  else {
492
- while ( n-- ) { *p2 = (#{ctype2}) #{conv}(*p1); p1++; p2++; }
505
+ while ( n-- ) { #{read} p1++; p2++; }
493
506
  }
494
507
  return;
495
508
  }
@@ -12,8 +12,11 @@
12
12
  ---------------------------------------------------------------------------- */
13
13
 
14
14
  #include "carray.h"
15
+ #include "carray_internal.h" /* ca_attach_window */
15
16
 
16
17
  #include "ruby/io.h"
18
+ #include "ruby/util.h" /* ruby_strtod */
19
+ #include <math.h>
17
20
 
18
21
  /* to_a dispatch:
19
22
  numeric / boolean data type + entity or attach-able view -> fast path
@@ -175,19 +178,34 @@ rb_ca_to_a_loop_universal (VALUE self, int32_t level, ca_size_t *idx, VALUE ary)
175
178
  * fast path, so all view kinds land in the data type-direct leaf loop when
176
179
  * the data type is numeric or boolean; CA_OBJECT / CA_FIXLEN fall through
177
180
  * to the universal fetch_index walk. */
181
+ /* The universal walk reads each cell through the fetch path, which
182
+ decodes -- an object cell, a Face's element -- and can raise, so it runs
183
+ inside an attach window. The fast path only copies numbers out. */
184
+ typedef struct {
185
+ VALUE self;
186
+ VALUE ary;
187
+ } ca_to_a_ctx_t;
188
+
189
+ static VALUE
190
+ ca_to_a_universal (VALUE arg)
191
+ {
192
+ ca_to_a_ctx_t *c = (ca_to_a_ctx_t *) arg;
193
+ ca_size_t idx[CA_RANK_MAX];
194
+ rb_ca_to_a_loop_universal(c->self, 0, idx, c->ary);
195
+ return Qnil;
196
+ }
197
+
178
198
  VALUE
179
199
  rb_ca_to_a (VALUE self)
180
200
  {
181
201
  volatile VALUE ary;
182
202
  CArray *ca;
183
- ca_size_t idx[CA_RANK_MAX];
184
203
  int data_type, fast_eligible;
185
204
 
186
205
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
187
206
  ary = rb_ca_is_empty(self) ? rb_ary_new() : rb_ary_new2(ca->dim[0]);
188
- ca_attach(ca);
189
207
 
190
- /* After ca_attach, ca->ptr is a contig row-major buffer (alias for
208
+ /* Once attached, ca->ptr is a contig row-major buffer (alias for
191
209
  contig views, materialised otherwise) and ca->mask matches, so the
192
210
  fast path handles every view kind. CA_OBJECT / CA_FIXLEN stay on
193
211
  the universal path because their ptr is VALUE / byte string and
@@ -210,16 +228,21 @@ rb_ca_to_a (VALUE self)
210
228
  || data_type == CA_BOOLEAN);
211
229
 
212
230
  if ( fast_eligible ) {
213
- int has_mask = (ca->mask != NULL);
214
- const boolean8_t *mask_base = has_mask
215
- ? (const boolean8_t *) ca->mask->ptr : NULL;
231
+ int has_mask;
232
+ const boolean8_t *mask_base;
233
+ ca_attach(ca); /* window: nothing raises inside */
234
+ has_mask = (ca->mask != NULL);
235
+ mask_base = has_mask ? (const boolean8_t *) ca->mask->ptr : NULL;
216
236
  to_a_fast_recurse(ca, 0, ca->ptr, mask_base, ary,
217
237
  data_type, ca->bytes, has_mask);
238
+ ca_detach(ca);
218
239
  }
219
240
  else if ( ca->elements > 0 ) {
220
- rb_ca_to_a_loop_universal(self, 0, idx, ary);
241
+ ca_to_a_ctx_t c;
242
+ c.self = self;
243
+ c.ary = ary;
244
+ ca_attach_window(1, &ca, "r", ca_to_a_universal, (VALUE) &c);
221
245
  }
222
- ca_detach(ca);
223
246
  return ary;
224
247
  }
225
248
 
@@ -228,65 +251,44 @@ rb_ca_to_a (VALUE self)
228
251
  #undef TO_A_DISPATCH_LEAF
229
252
  #undef TO_A_DISPATCH_LEAF_MASKED
230
253
 
231
- /* CArray#convert(data_type=nil, bytes: nil) { |elem| ... } — map every cell
232
- * through the block, into a new array whose data type is chosen by argv via
233
- * CArray#template (shape is inherited from self).
234
- *
235
- * Masked cells skip the block; if the block returns CA_UNDEF, the
236
- * corresponding output cell is masked instead of stored. Both self
237
- * and the template result are attached symmetrically (R3-compliant) so
238
- * an overridden #template returning a view stays on the in-ptr path. */
254
+ /* convert yields inside the window over self and the output, so the loop
255
+ runs through ca_attach_window. An output mask made part way (the first
256
+ UNDEF from the block) is attached inside the loop, and closed by an
257
+ ensure of its own. */
258
+
259
+ typedef struct {
260
+ VALUE self;
261
+ VALUE obj;
262
+ CArray *ca;
263
+ CArray *co;
264
+ int has_mask;
265
+ int co_mask_attached;
266
+ } ca_convert_ctx_t;
267
+
239
268
  static VALUE
240
- rb_ca_convert (int argc, VALUE *argv, VALUE self)
269
+ ca_convert_loop (VALUE arg)
241
270
  {
242
- volatile VALUE obj;
243
- CArray *ca, *co;
244
- ca_size_t i, n, sbytes, dbytes;
245
- int has_mask;
246
- const boolean8_t *src_mask = NULL;
271
+ ca_convert_ctx_t *c = (ca_convert_ctx_t *) arg;
272
+ CArray *ca = c->ca, *co = c->co;
273
+ ca_size_t i, n = ca->elements;
274
+ ca_size_t sbytes = ca->bytes, dbytes = co->bytes;
275
+ const char *src_ptr = ca->ptr;
276
+ char *dst_ptr = co->ptr;
247
277
  boolean8_t *dst_mask = NULL;
248
- const char *src_ptr;
249
- char *dst_ptr;
250
278
 
251
- rb_need_block();
252
-
253
- obj = rb_apply(self, rb_intern("template"), rb_ary_new4(argc, argv));
254
-
255
- TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
256
- TypedData_Get_Struct(obj, CArray, &carray_data_type, co);
257
-
258
- n = ca->elements;
259
- has_mask = ca_has_mask(ca);
260
- sbytes = ca->bytes;
261
- dbytes = co->bytes;
262
-
263
- /* Attach every storage touched by ptr (self, obj, and either side's
264
- mask) — parent → child order per the R4 attach protocol. */
265
- ca_attach(ca);
266
- ca_attach(co);
267
- if ( has_mask ) {
268
- ca_attach(ca->mask);
269
- if ( ! co->mask ) {
270
- ca_create_mask(co);
271
- }
272
- ca_attach(co->mask);
273
- src_mask = (const boolean8_t *) ca->mask->ptr;
279
+ if ( c->has_mask ) {
280
+ const boolean8_t *src_mask = (const boolean8_t *) ca->mask->ptr;
274
281
  dst_mask = (boolean8_t *) co->mask->ptr;
275
282
  memset(dst_mask, 0, (size_t) n);
276
- }
277
- src_ptr = ca->ptr;
278
- dst_ptr = co->ptr;
279
-
280
- if ( has_mask ) {
281
283
  for (i = 0; i < n; i++) {
282
284
  if ( ! src_mask[i] ) {
283
- VALUE val = rb_ca_ptr2obj(self, (void *) (src_ptr + i * sbytes));
285
+ VALUE val = rb_ca_ptr2obj(c->self, (void *) (src_ptr + i * sbytes));
284
286
  VALUE ret = rb_yield(val);
285
287
  if ( ret == CA_UNDEF ) {
286
288
  dst_mask[i] = 1;
287
289
  }
288
290
  else {
289
- rb_ca_obj2ptr(obj, ret, dst_ptr + i * dbytes);
291
+ rb_ca_obj2ptr(c->obj, ret, dst_ptr + i * dbytes);
290
292
  }
291
293
  }
292
294
  else {
@@ -296,33 +298,78 @@ rb_ca_convert (int argc, VALUE *argv, VALUE self)
296
298
  }
297
299
  else {
298
300
  for (i = 0; i < n; i++) {
299
- VALUE val = rb_ca_ptr2obj(self, (void *) (src_ptr + i * sbytes));
301
+ VALUE val = rb_ca_ptr2obj(c->self, (void *) (src_ptr + i * sbytes));
300
302
  VALUE ret = rb_yield(val);
301
303
  if ( ret == CA_UNDEF ) {
302
304
  if ( ! co->mask ) {
303
305
  ca_create_mask(co);
304
- ca_attach(co->mask);
306
+ ca_attach(co->mask); /* window: closed by the ensure in ca_convert_body */
307
+ c->co_mask_attached = 1;
305
308
  dst_mask = (boolean8_t *) co->mask->ptr;
306
309
  memset(dst_mask, 0, (size_t) n);
307
310
  }
308
311
  dst_mask[i] = 1;
309
312
  }
310
313
  else {
311
- rb_ca_obj2ptr(obj, ret, dst_ptr + i * dbytes);
314
+ rb_ca_obj2ptr(c->obj, ret, dst_ptr + i * dbytes);
312
315
  }
313
316
  }
314
317
  }
318
+ return Qnil;
319
+ }
315
320
 
316
- if ( co->mask ) {
317
- ca_sync(co->mask);
318
- ca_detach(co->mask);
321
+ static VALUE
322
+ ca_convert_close_mask (VALUE arg)
323
+ {
324
+ ca_convert_ctx_t *c = (ca_convert_ctx_t *) arg;
325
+ if ( c->co_mask_attached ) {
326
+ c->co_mask_attached = 0;
327
+ ca_sync_detach(c->co->mask);
319
328
  }
320
- if ( has_mask ) {
321
- ca_detach(ca->mask);
329
+ return Qnil;
330
+ }
331
+
332
+ static VALUE
333
+ ca_convert_body (VALUE arg)
334
+ {
335
+ return rb_ensure(ca_convert_loop, arg, ca_convert_close_mask, arg);
336
+ }
337
+
338
+ /* CArray#convert(data_type=nil, bytes: nil) { |elem| ... } — map every cell
339
+ * through the block, into a new array whose data type is chosen by argv via
340
+ * CArray#template (shape is inherited from self).
341
+ *
342
+ * Masked cells skip the block; if the block returns CA_UNDEF, the
343
+ * corresponding output cell is masked instead of stored. Both self
344
+ * and the template result are attached symmetrically (R3-compliant) so
345
+ * an overridden #template returning a view stays on the in-ptr path. */
346
+ static VALUE
347
+ rb_ca_convert (int argc, VALUE *argv, VALUE self)
348
+ {
349
+ volatile VALUE obj;
350
+ ca_convert_ctx_t c;
351
+ CArray *list[2];
352
+
353
+ rb_need_block();
354
+
355
+ obj = rb_apply(self, rb_intern("template"), rb_ary_new4(argc, argv));
356
+
357
+ TypedData_Get_Struct(self, CArray, &carray_data_type, c.ca);
358
+ TypedData_Get_Struct(obj, CArray, &carray_data_type, c.co);
359
+ c.self = self;
360
+ c.obj = obj;
361
+ c.has_mask = ca_has_mask(c.ca);
362
+ c.co_mask_attached = 0;
363
+
364
+ /* Attaching an array attaches its mask, so the output's mask is made
365
+ before the window opens when the source already has one. */
366
+ if ( c.has_mask && ! c.co->mask ) {
367
+ ca_create_mask(c.co);
322
368
  }
323
- ca_sync(co);
324
- ca_detach(co);
325
- ca_detach(ca);
369
+
370
+ list[0] = c.ca;
371
+ list[1] = c.co;
372
+ ca_attach_window(2, list, "rw", ca_convert_body, (VALUE) &c);
326
373
 
327
374
  return obj;
328
375
  }
@@ -395,7 +442,7 @@ rb_ca_dump_binary (int argc, VALUE *argv, VALUE self)
395
442
  rb_funcall(io, rb_intern("write"), 1, buf);
396
443
  }
397
444
  else {
398
- rb_raise(rb_eRuntimeError, "IO like object should have 'write' method");
445
+ rb_raise(rb_eTypeError, "IO like object should have 'write' method");
399
446
  }
400
447
  }
401
448
 
@@ -422,6 +469,7 @@ rb_ca_load_binary (VALUE self, VALUE io)
422
469
  {
423
470
  CArray *ca;
424
471
 
472
+ rb_ca_modify(self);
425
473
  TypedData_Get_Struct(self, CArray, &carray_data_type, ca);
426
474
 
427
475
  if ( ca_is_object_type(ca) ) {
@@ -431,7 +479,7 @@ rb_ca_load_binary (VALUE self, VALUE io)
431
479
  switch ( TYPE(io) ) {
432
480
  case T_STRING:
433
481
  if ( ca_length(ca) != RSTRING_LEN(io) ) {
434
- rb_raise(rb_eRuntimeError,
482
+ rb_raise(rb_eArgError,
435
483
  "data size mismatch (got %" PRId64 " bytes, expected %" PRId64 ")",
436
484
  (ca_size_t) RSTRING_LEN(io), (ca_size_t) ca_length(ca));
437
485
  }
@@ -447,13 +495,13 @@ rb_ca_load_binary (VALUE self, VALUE io)
447
495
  const ca_size_t CHUNK = 65536;
448
496
  volatile VALUE scratch = rb_str_buf_new(CHUNK < total ? CHUNK : total);
449
497
  ca_size_t off = 0;
450
- ca_allocate(ca);
498
+ ca_allocate(ca); /* window: an entity, whose allocate holds nothing */
451
499
  while ( off < total ) {
452
500
  ca_size_t want = (total - off < CHUNK) ? (total - off) : CHUNK;
453
501
  VALUE r = rb_funcall(io, id_read, 2, SIZE2NUM(want), scratch);
454
502
  if ( NIL_P(r) || RSTRING_LEN(r) != want ) {
455
503
  ca_detach(ca);
456
- rb_raise(rb_eRuntimeError,
504
+ rb_raise(rb_eArgError,
457
505
  "short read from IO at offset %" PRId64 " "
458
506
  "(got %" PRId64 " bytes, expected %" PRId64 ")",
459
507
  (ca_size_t) off,
@@ -463,15 +511,14 @@ rb_ca_load_binary (VALUE self, VALUE io)
463
511
  memcpy(ca->ptr + off, RSTRING_PTR(r), want);
464
512
  off += want;
465
513
  }
466
- ca_sync(ca);
467
- ca_detach(ca);
514
+ ca_sync_detach(ca);
468
515
  }
469
516
  else {
470
517
  /* ca_sync_data scatters in one pass, so buffer the full read
471
518
  and recurse via T_STRING. */
472
519
  volatile VALUE buf = rb_funcall(io, id_read, 1, SIZE2NUM(total));
473
520
  if ( NIL_P(buf) || RSTRING_LEN(buf) != total ) {
474
- rb_raise(rb_eRuntimeError,
521
+ rb_raise(rb_eArgError,
475
522
  "short read from IO (got %" PRId64 " bytes, expected %" PRId64 ")",
476
523
  (ca_size_t) (NIL_P(buf) ? 0 : RSTRING_LEN(buf)),
477
524
  (ca_size_t) total);
@@ -481,13 +528,89 @@ rb_ca_load_binary (VALUE self, VALUE io)
481
528
  return self;
482
529
  }
483
530
  else {
484
- rb_raise(rb_eRuntimeError, "IO like object should have 'read' method");
531
+ rb_raise(rb_eTypeError, "IO like object should have 'read' method");
485
532
  }
486
533
  }
487
534
 
488
535
  return self;
489
536
  }
490
537
 
538
+ /* The double nearest to the shortest decimal that reads back as x, the
539
+ float32 value, as from_csv reads text into float32: strtod, then the cast
540
+ to float. Its digits are those of the decimal (at most 9, which a double
541
+ holds), so Float#to_s writes them. */
542
+ static double
543
+ ca_float32_shortest (float x)
544
+ {
545
+ char buf[32];
546
+ int lo = 1, hi = 9;
547
+ double best;
548
+ if ( isnan(x) || isinf(x) ) {
549
+ return (double) x;
550
+ }
551
+ /* Nine significant digits always read back; search for the fewest. More
552
+ digits round closer to x, so the digit counts that read back are those
553
+ from some count up. */
554
+ best = 0.0;
555
+ while ( lo < hi ) {
556
+ int mid = (lo + hi) / 2;
557
+ double d;
558
+ snprintf(buf, sizeof(buf), "%.*e", mid - 1, (double) x);
559
+ d = ruby_strtod(buf, NULL);
560
+ if ( (float) d == x ) {
561
+ best = d;
562
+ hi = mid;
563
+ }
564
+ else {
565
+ lo = mid + 1;
566
+ }
567
+ }
568
+ if ( lo == 9 ) {
569
+ snprintf(buf, sizeof(buf), "%.8e", (double) x);
570
+ best = ruby_strtod(buf, NULL);
571
+ }
572
+ return best;
573
+ }
574
+
575
+ /* float32 -> float64 and cmplx64 -> cmplx128, each value replaced by the
576
+ double its shortest decimal reads as, for to_csv to write that decimal.
577
+ The mask is kept. */
578
+ static VALUE
579
+ rb_ca_shortest_float64 (VALUE self)
580
+ {
581
+ volatile VALUE out, holder = 0;
582
+ CArray *ca, *co;
583
+ ca_size_t i, n;
584
+ int8_t type;
585
+
586
+ GetCArray(self, ca);
587
+ if ( ca->data_type == CA_FLOAT32 ) {
588
+ type = CA_FLOAT64;
589
+ }
590
+ else if ( ca->data_type == CA_CMPLX64 ) {
591
+ type = CA_CMPLX128;
592
+ }
593
+ else {
594
+ rb_raise(rb_eCADataTypeError, "__shortest_float64__ takes float32 or cmplx64");
595
+ }
596
+ n = ca->elements * (ca->data_type == CA_CMPLX64 ? 2 : 1);
597
+ out = rb_carray_new_safe(type, ca->ndim, ca->dim, 0, NULL);
598
+ GetCArray(out, co);
599
+ {
600
+ float *src = ALLOCV_N(float, holder, n > 0 ? n : 1);
601
+ double *dst = (double *) co->ptr;
602
+ ca_copy_data(ca, (char *) src);
603
+ for (i = 0; i < n; i++) {
604
+ dst[i] = ca_float32_shortest(src[i]);
605
+ }
606
+ ALLOCV_END(holder);
607
+ }
608
+ if ( ca_has_mask(ca) ) {
609
+ ca_copy_mask_overlay(co, co->elements, 1, ca);
610
+ }
611
+ return out;
612
+ }
613
+
491
614
  /* [MOVED] str_format (2.0-era per-element formatter) is replaced by the Ruby
492
615
  CArray#format / CArray.format surface in lib/carray/methods/string_format.rb,
493
616
  which yields a CAString. */
@@ -504,6 +627,7 @@ Init_carray_conversion (void)
504
627
  rb_define_method(rb_cCArray, "dump_binary", rb_ca_dump_binary, -1);
505
628
  rb_define_method(rb_cCArray, "to_s", rb_ca_to_s, 0);
506
629
  rb_define_method(rb_cCArray, "load_binary", rb_ca_load_binary, 1);
630
+ rb_define_method(rb_cCArray, "__shortest_float64__", rb_ca_shortest_float64, 0);
507
631
 
508
632
  /* CAREFUL: do not bind #to_ary — implicit array coercion in Ruby core
509
633
  (splat / multiple assignment / Array()) then triggers on any CArray