carray 3.0.5 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +233 -0
  3. data/README.md +1 -1
  4. data/carray.gemspec +3 -2
  5. data/ext/ca_axis_dispatch.c +6 -0
  6. data/ext/ca_axis_group.c +1 -1
  7. data/ext/ca_categorical_iterator.c +7 -7
  8. data/ext/ca_kernel_iterator.c +116 -231
  9. data/ext/ca_kernel_iterator.h +410 -254
  10. data/ext/ca_obj_bincmp.c +9 -1
  11. data/ext/ca_obj_binop.c +10 -1
  12. data/ext/ca_obj_bitfield.c +2 -0
  13. data/ext/ca_obj_byte_swap.c +2 -0
  14. data/ext/ca_obj_face.c +2 -0
  15. data/ext/ca_obj_fake.c +3 -1
  16. data/ext/ca_obj_grid.c +167 -12
  17. data/ext/ca_obj_meld.c +69 -113
  18. data/ext/ca_obj_moncmp.c +9 -1
  19. data/ext/ca_obj_monop.c +40 -2
  20. data/ext/ca_obj_object.c +54 -20
  21. data/ext/ca_obj_reduce.c +15 -1
  22. data/ext/ca_obj_refer.c +65 -20
  23. data/ext/ca_obj_remap.c +69 -10
  24. data/ext/ca_obj_roll.c +1 -0
  25. data/ext/ca_obj_select.c +4 -1
  26. data/ext/ca_obj_select_axis.c +1 -0
  27. data/ext/ca_obj_shift.c +1 -0
  28. data/ext/ca_obj_stack.c +310 -111
  29. data/ext/ca_obj_stride.c +131 -45
  30. data/ext/ca_obj_string.c +18 -2
  31. data/ext/ca_obj_tile.c +1 -0
  32. data/ext/ca_obj_triop.c +9 -1
  33. data/ext/ca_obj_window.c +1 -0
  34. data/ext/ca_op_ipower.c +8 -8
  35. data/ext/caframe_csv_reader.c +1 -1
  36. data/ext/carray.h +2 -1
  37. data/ext/carray_access.c +27 -64
  38. data/ext/carray_address_basis.c +134 -0
  39. data/ext/carray_attribute.c +8 -0
  40. data/ext/carray_broadcast.c +3 -4
  41. data/ext/carray_build_flags.h +1 -1
  42. data/ext/carray_cast.c +8 -11
  43. data/ext/carray_conversion.c +9 -5
  44. data/ext/carray_core.c +811 -72
  45. data/ext/carray_element.c +13 -9
  46. data/ext/carray_histogram.c +25 -3
  47. data/ext/carray_internal.h +147 -12
  48. data/ext/carray_kernels_bincmp.c +2007 -3
  49. data/ext/carray_kernels_binop.c +25365 -5199
  50. data/ext/carray_kernels_init.c +8 -8
  51. data/ext/carray_kernels_monop.c +10696 -3678
  52. data/ext/carray_kernels_reduce_aggregate.c +4429 -702
  53. data/ext/carray_kernels_reduce_boolean.c +1533 -15
  54. data/ext/carray_kernels_reduce_cumulative.c +1808 -248
  55. data/ext/carray_kernels_reduce_extreme.c +4508 -511
  56. data/ext/carray_kernels_reduce_variance.c +2284 -152
  57. data/ext/carray_kernels_scan.c +240 -240
  58. data/ext/carray_kernels_search.c +4211 -264
  59. data/ext/carray_kernels_sort.c +367 -450
  60. data/ext/carray_lazy.c +47 -0
  61. data/ext/carray_loop.c +1 -1
  62. data/ext/carray_mask.c +130 -139
  63. data/ext/carray_median_percentile.c +4 -4
  64. data/ext/carray_memory_view.c +13 -7
  65. data/ext/carray_operator.c +147 -44
  66. data/ext/carray_order.c +3 -2
  67. data/ext/carray_random.c +4 -1
  68. data/ext/carray_slab.c +97 -21
  69. data/ext/carray_sort.c +1 -1
  70. data/ext/carray_test.c +74 -19
  71. data/ext/carray_time_text.c +2 -2
  72. data/ext/extconf.rb +11 -0
  73. data/ext/mkkernel.rb +346 -94
  74. data/ext/ruby_carray.c +4 -1
  75. data/ext/version.h +4 -4
  76. data/lib/carray/attribute.rb +25 -1
  77. data/lib/carray/autoload_carray.rb +3 -3
  78. data/lib/carray/autoload_method_extension.rb +5 -3
  79. data/lib/carray/axis_group.rb +13 -7
  80. data/lib/carray/basics.rb +1 -1
  81. data/lib/carray/block_iterator.rb +17 -3
  82. data/lib/carray/categorical.rb +6 -8
  83. data/lib/carray/categorical_iterator.rb +2 -2
  84. data/lib/carray/complex.rb +2 -2
  85. data/lib/carray/conditional.rb +3 -2
  86. data/lib/carray/frame/column_verbs.rb +45 -14
  87. data/lib/carray/frame/convert.rb +19 -0
  88. data/lib/carray/frame/csv_parser.rb +32 -32
  89. data/lib/carray/frame/describe.rb +4 -2
  90. data/lib/carray/frame/frame.rb +399 -78
  91. data/lib/carray/frame/group.rb +679 -53
  92. data/lib/carray/frame/io.rb +209 -71
  93. data/lib/carray/frame/join.rb +146 -37
  94. data/lib/carray/frame/records.rb +3 -3
  95. data/lib/carray/frame/reshape.rb +89 -11
  96. data/lib/carray/frame/sort.rb +57 -9
  97. data/lib/carray/frame/verbs.rb +384 -49
  98. data/lib/carray/frame/windows.rb +44 -6
  99. data/lib/carray/inspect.rb +7 -2
  100. data/lib/carray/iterator.rb +32 -0
  101. data/lib/carray/lazy.rb +17 -8
  102. data/lib/carray/mask_gap_fill.rb +37 -17
  103. data/lib/carray/meld_reduce.rb +1 -1
  104. data/lib/carray/methods/choose.rb +3 -2
  105. data/lib/carray/methods/meshgrid.rb +3 -1
  106. data/lib/carray/runtime.rb +47 -8
  107. data/lib/carray/segment_iterator.rb +23 -8
  108. data/lib/carray/serialize.rb +34 -17
  109. data/lib/carray/stack.rb +18 -6
  110. data/lib/carray/time.rb +5 -0
  111. data/lib/carray/window_iterator.rb +16 -4
  112. data/yard-stubs/ca_obj_array.rb +26 -1
  113. data/yard-stubs/carray_cast.rb +8 -5
  114. data/yard-stubs/carray_conversion.rb +8 -2
  115. data/yard-stubs/carray_mask.rb +20 -1
  116. data/yard-stubs/carray_math.rb +31 -16
  117. data/yard-stubs/carray_memory_view.rb +7 -0
  118. data/yard-stubs/carray_test.rb +7 -4
  119. metadata +18 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ccd30e8ea5b53c3a2654a86a0ab7473b3073f7feffa7db7be0e5c9aef5bc17c8
4
- data.tar.gz: ffcd91fe93ca10b4b12dbff035ffc25533699472b364482a59bf2aa635296032
3
+ metadata.gz: 7012a2c73c33e52e9b5dd18d5fd0e7c02b4c741bb650a41cefbfea117449da2f
4
+ data.tar.gz: 5731ca96689f1d87d9c4e1e6a4764893eac0e39f8126f91695b2042581ae198f
5
5
  SHA512:
6
- metadata.gz: 53c08f4ccf6a4c25219cef4e8f27e7de9740dc8c69ee493ff8e3631c87499cd15ed121d5f7707bd1017f77e0309dc1be2628fa3611b9b3c979e0e075008842d6
7
- data.tar.gz: 2200b047a456603d6274d9dfaf8f93c011ce1cc1eb457c454fa7f9cdc9c75ea4b96c9bbe1dab0b37710f8b872ac5e8b630db5c0dacae12a9012b1b34d4c7f1ec
6
+ metadata.gz: 1e05efbfc29639882cc08753c9394400abb707df063025a7ae5bfed6c006979721d8b0030ae17a4eab1397c89c7c3803bf9fa2ec62fa7aa9d8c56e1adb38bd79
7
+ data.tar.gz: 21baa2d1fac6d2c45a1ed47b4235e9f1832beb4063e93491743470ac77805c266210139d7af834ca1d014ec410ae43191228679a72004c14af6c2fa91eb88eba
data/CHANGELOG.md CHANGED
@@ -34,6 +34,239 @@ and a newer one. The 1.x history, up to the 2.0.0 release, is in
34
34
  neighbour ("as well", "the producer above") or leave unnamed the
35
35
  method, class or keyword it is about. -->
36
36
 
37
+ ## 3.0.6
38
+
39
+ - New: `CAFrame` answers the column reductions itself, as a frame of one
40
+ row (`df.mean`, `df.sum("rain", "temp")`), and the running values down
41
+ all its rows (`df.cumsum("rain")`, `order`), as a grouped frame does
42
+ down each group. `percentile(p, *names)` and `quantile` (a column per
43
+ quartile, `"rain_p0"` ... `"rain_p100"`) are added to the frame, groups
44
+ and windows.
45
+
46
+ - New: `CAFrame` row picks: `nlargest` / `nsmallest` (also on
47
+ `GroupedFrame`), `drop_masked` for the rows with no masked cell,
48
+ `GroupedFrame#head` / `#tail` for the first and last rows of each group,
49
+ and `filter` given a boolean selector directly (`df.filter(df["n"] > 3)`).
50
+
51
+ - New: `CAFrame#transform` computes columns with a block, keeping their
52
+ names and the index. `select`, `drop`, `transform` and `describe` pick
53
+ columns by a Range of names, a Regexp, a kind (`:float`, `:integer`,
54
+ `:complex`, `:timedelta`, ...) or a data type.
55
+
56
+ - New: `CAFrame#value_counts` (how many rows hold each value, as a
57
+ frame), `#strip_mask` (the masked cells filled, in a new frame), `#to_a`
58
+ (the rows as Arrays, masked cells `UNDEF`), `#shape`, `#empty?`, and
59
+ `GroupedFrame#each` and `#size`. `CAFrame#==` compares two frames by
60
+ their table: names, columns, index and row axis name.
61
+
62
+ - New: `CAFrame#lag` and `GroupedFrame#lag` take a distance of the key,
63
+ `before: "1 hour"` or `after:`, giving each row the value of the row at
64
+ exactly that distance (`UNDEF` where there is none). The key is the
65
+ index or the column `on:` names: a time, a duration or a number.
66
+
67
+ - New: `join` and `join_asof` take a key named differently on the two
68
+ sides, and `view: :full`, which gives every column of the result as a
69
+ view, with holes for the unmatched rows, so a write reaches either
70
+ frame.
71
+
72
+ - New: `df[name] = value` with a single value repeats it on every row.
73
+ `fill` takes several columns, an Array of names or a Hash of column =>
74
+ value; `rename` takes a block, can exchange two names, and renames the
75
+ index under its name. `sort_by_key` and `sort_by` take `descending:`.
76
+
77
+ - New: `CAFrame#set_column_attrs` gives columns their names and common
78
+ attributes; `CArray#attrs_symkey` and `CAFrame#attrs_symkey` return the
79
+ attributes with Symbol keys. `CAFrame#to_csv` takes `precision:`.
80
+
81
+ - New: `GroupedFrame#fill(name, :linear)` interpolates within each group.
82
+ On a frame, `:linear` reads an index that is not in order as one series
83
+ in index order.
84
+
85
+ - New: `melt` brings number columns of different types (an `int64` and a
86
+ `float64` column) to their common type, still as a view. A mixture no
87
+ type holds without changing a value (`int64` beside `uint64`), or one
88
+ with text or time, is refused; cast the columns first.
89
+
90
+ - New: `wsum` and `wmean` take weights shaped as the reduced axes, matched
91
+ in ascending axis order, besides a scalar and weights of the receiver's
92
+ shape.
93
+
94
+ - New (C extensions and carray-jit): `CArray::AddressBasis.sweep(function,
95
+ fsync, arrays, *parts)` runs a chunked element-wise sweep
96
+ (`ca_call_cslab_N_r`, arities 1 to 7) from Ruby, handing the slab
97
+ pointers to the parts' own String bytes. An `fsync` character other than
98
+ `0` or `1` is refused.
99
+
100
+ - Change: CArray requires Ruby 3.2 or later, up from 3.0. On an older
101
+ Ruby, `gem install carray` does not offer this release; install a
102
+ newer Ruby with a version manager such as rbenv or mise, or stay on
103
+ 3.0.5.
104
+
105
+ - Change: CSV reading and writing are split by what they take.
106
+ `CAFrame.read_csv` and `#write_csv` take a path or an IO;
107
+ `CAFrame.from_csv` takes CSV text as a String and `#to_csv` returns it.
108
+ Code that gave `from_csv` a path or an IO calls `read_csv`, and
109
+ `to_csv(path)` becomes `write_csv(path)`. No aliases are kept.
110
+
111
+ - Change: `read_csv` and `from_csv` infer the column types by default
112
+ (`int64`, `float64`, `boolean`, `time`, otherwise text); `types:` names
113
+ some columns and the rest are still inferred. Pass `types: :string` to
114
+ read every column as text, as before. A column with a blank but present
115
+ value (a quoted `""`) stays text.
116
+
117
+ - Change: `CAFrame#set_index`, `#reset_index`, `#cast`, `#promote`,
118
+ `#parse_to_time` and `#to_time` return a new frame and leave the
119
+ receiver as it is (the two share their columns). Assign the result:
120
+ `df = df.set_index("time")`.
121
+
122
+ - Change: a `GroupedFrame` reduction given column names returns a frame of
123
+ those columns, one row per group, labelled as without names; it
124
+ returned the `CArray` (one name) or an `Array` of them. Take the column
125
+ (`grp.mean("temp")["temp"]`) or reduce the group iterator
126
+ (`grp["temp"].mean`) for the values alone.
127
+
128
+ - Change: a `GroupedFrame` or window value on the frame's rows given
129
+ several column names (`keep_rows: true`, the running values, `lag`,
130
+ `w.mean("a", "b")`) returns a frame of them on the frame's rows and
131
+ index, instead of an `Array`. One name still gives the `CArray`.
132
+
133
+ - Change: `CAFrame#filter` no longer takes `keep_masked:`, so every row
134
+ selection is a view. To keep the rows whose condition is undetermined,
135
+ say so in the selector: `df.filter(cond.is_masked)` or
136
+ `df.filter(cond.strip_mask(true))`.
137
+
138
+ - Change: `df[sel] = ...` refuses a Ruby Array as the row selector, as
139
+ `df[]` does; the column indexer would read `[0, 2]` as two rows from
140
+ row 0. Write `df[CA_INT64([0, 2])]` for rows 0 and 2.
141
+
142
+ - Change: a group key named in a `GroupedFrame` reduction, and the index's
143
+ name given to a verb that reads or changes columns (`df[...]`,
144
+ `select`, `fill`, `cast`, the reductions), raise and point at `size`
145
+ or `df.index`. A key argument (`group_by`, `sort_by_key`, `join(on:)`)
146
+ still names the index.
147
+
148
+ - Change: an `:inner` or `:right` `join` gives the other frame's columns
149
+ as views picked by row, not copies, so a write to them reaches that
150
+ frame. Call `copy` on the result to keep it apart.
151
+
152
+ - Change: a `CScalar` takes only `s[]` and `s[0]`, reading or writing its
153
+ one cell; any other index (`s[3]`, a range, `nil`, an index array)
154
+ raises. A view such as `s.refer(s.data_type, [1])` takes the usual
155
+ indexes.
156
+
157
+ - Change: `CArray.stack` and `CArray.meld` refuse a value array
158
+ (`a.value`) or a mask array (`a.mask`) as a part. Pass a copy
159
+ (`a.value.copy`), or `a.is_masked` for the mask's values.
160
+
161
+ - Change: `CArray#==` finds a NaN equal to a NaN, so an array holding NaN
162
+ equals its copy and the array read back from a file. The cell-by-cell
163
+ IEEE comparison is `eq`; `eql?` still compares bits.
164
+
165
+ - Change: `CARemap#mapper` returns a read-only array; writing it raises.
166
+ The index array given to `a[idx]` is not affected.
167
+
168
+ - Change: `Marshal.dump` of a `CAWrap` (from `CArray.wrap_memory_view`)
169
+ writes its values, and the load gives a plain array owning a copy of
170
+ them, as for a view of the wrap; it raised `TypeError`.
171
+
172
+ - Change: `as_type` returns a view also when the data type is the
173
+ receiver's own, and `CATime#to_unit` / `CATimedelta#to_unit` return a
174
+ new array when the unit does not change, so an attribute set on, or a
175
+ freeze of, the result does not reach the receiver.
176
+
177
+ - Change: `wmean` gives `UNDEF` where the weights sum to zero, on every
178
+ path; numeric arrays gave NaN or an infinity. A weight array with one
179
+ element is no longer read as a scalar: give a Ruby number or a
180
+ `CScalar`.
181
+
182
+ - Change: a computation over an object array that comes back into the
183
+ array (a cell holding the array, or a view of it) raises, as reading
184
+ such a cell does; it recursed until the process died. A lazy
185
+ expression over such an array (a cell holding `x + 1` for the array `x`)
186
+ raises the same way instead of overflowing the stack or not ending, and
187
+ so does `Marshal.dump` of an object view whose cells lead back to its
188
+ root.
189
+
190
+ - Change (CAObject authors): a `CAObject` without `create_mask` raises
191
+ `TypeError` when its `copy_data` writes `UNDEF`, as it does when a
192
+ fetcher answers `UNDEF`; the `UNDEF` was dropped and read as a value.
193
+
194
+ - Change (C extensions): `ca_install_obj_type` refuses an operation table
195
+ without `allocate`, `attach`, `sync` and `detach` slots;
196
+ `ca_iter_register_source_kind` refuses carray's own types; and the
197
+ kernel iterator's `FIBER_PAIR` forms refuse `CA_KERNEL_WRITE`.
198
+
199
+ - Fix: `require "carray"` works on Ruby 4.0; it raised
200
+ `Ractor::IsolationError` since 3.0.4.
201
+
202
+ - Fix: `CArray.fuse` works on Ruby 3.2 and 3.3. The gem now depends on
203
+ prism 1.0 or later, which `gem install` and Bundler bring in on Ruby
204
+ 3.2, which ships none, and 3.3, whose own prism is too old; Ruby 3.4
205
+ and later already ship it.
206
+
207
+ - Fix: a block of a reshape over a strided view
208
+ (`a[1..2, 1..3].reshape(3, 2)[0..1, nil]`) read and wrote past the end
209
+ of a row, outside the buffer at the edges.
210
+
211
+ - Fix: a frozen object array shared between Ractors can be computed over
212
+ in several at once; it was refused as cyclic. A cell holding a view of
213
+ other cells of the same array is no longer refused.
214
+
215
+ - Fix: a record array (`CARecord`) exports through MemoryView from a
216
+ Ractor other than the main one; it raised `Ractor::IsolationError`.
217
+
218
+ - Fix: `Marshal.dump` of a view no longer writes its whole parent (a
219
+ one-cell view of a 100,000-cell array dumped about 400 KB), and an
220
+ array's mask marshals as a plain boolean array.
221
+
222
+ - Fix: an `each_slab` Enumerator stopped part way with `next` leaves no
223
+ array attached once its Fiber is collected.
224
+
225
+ - Fix: a window or block iterator reads its source afresh on each call;
226
+ after the source changed, a later reduction mixed new sums with old
227
+ counts.
228
+
229
+ - Fix: a frozen or read-only `CArray.meld` refuses writes through a meld
230
+ of it (`CArray.meld(m, m)` wrote into the frozen root).
231
+
232
+ - Fix: a write to a `CArray.stack` or `CArray.meld` whose parts share
233
+ cells (`CArray.meld(a, a)`) gives those cells the value, or the mask, of
234
+ the last part; `add!` could leave an earlier part's result, and a mask
235
+ write masked the cells of every part.
236
+
237
+ - Fix: a write to a `CArray.stack` or `CArray.meld` that one part refuses
238
+ (a value outside a part's data type) writes nothing; the parts before
239
+ it were written.
240
+
241
+ - Fix: `blocks(...).sort_addr` works when the source holds more than one
242
+ tile; it failed with `IndexError`.
243
+
244
+ - Fix: `elem_fetch` answers a boolean cell with `true` or `false` when given
245
+ an index array over a view, as `[]` does; it answered `0` or `1`.
246
+
247
+ - Fix: `CAFrame#fill(:linear)` leaves a row with no coordinate out of the
248
+ interpolation; it was filled with 0.
249
+
250
+ - Fix: `bin_to` and `histogram1d` put a value next to an edge in the bin
251
+ that edge bounds when the edges are nearly but not exactly equally
252
+ spaced.
253
+
254
+ - Fix: `float ** Integer` takes any exponent that fits 64 bits, eagerly
255
+ as lazily, and `x.lazy.pow(k)` answers as `x.pow(k)`. A lazy array
256
+ compared with `UNDEF` (`eq`, `ne`) answers as the eager one does.
257
+
258
+ - Fix: writing `UNDEF` through a widening `refer` (`r.refer(:float64,
259
+ ...)`) no longer masks parent cells outside the written cells, and a
260
+ mask written onto a `CAObject` without mask hooks survives the next
261
+ read of its values.
262
+
263
+ - Fix: a masked cell that is not a String reads as `""` when a `CAString`
264
+ wraps a view, as it does over an entity.
265
+
266
+ - Fix (CASource authors): a source that leaves its `ptr` NULL at rest no
267
+ longer crashes `elem_*`, `dump_binary` to a File or `nlargest` over a
268
+ whole axis.
269
+
37
270
  ## 3.0.5
38
271
 
39
272
  - New: `GroupedFrame#aggregate` takes a block instead of a spec. The block
data/README.md CHANGED
@@ -30,7 +30,7 @@ Or add it to your `Gemfile`:
30
30
  gem "carray"
31
31
  ```
32
32
 
33
- Requires Ruby 3.0 or later.
33
+ Requires Ruby 3.2 or later.
34
34
 
35
35
  On a multi-core machine, parallel `make` cuts install time noticeably:
36
36
 
data/carray.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "carray"
3
- s.version = "3.0.5"
3
+ s.version = "3.0.6"
4
4
  s.authors = ["himotoyoshi"]
5
5
  s.email = ["himotoyoshi@users.noreply.github.com"]
6
6
  s.summary = "Multi-dimensional numerical array class for Ruby"
@@ -17,7 +17,8 @@ Gem::Specification.new do |s|
17
17
  s.homepage = "https://github.com/himotoyoshi/carray"
18
18
  s.license = "MIT"
19
19
  s.platform = Gem::Platform::RUBY
20
- s.required_ruby_version = ">= 3.0"
20
+ s.required_ruby_version = ">= 3.2"
21
+ s.add_dependency "prism", ">= 1.0"
21
22
  s.files = [
22
23
  *Dir.glob("lib/**/*.rb"),
23
24
  *Dir.glob("yard-stubs/**/*.rb"),
@@ -1101,6 +1101,12 @@ ca_axis_view_xfer_all (void *ap, ca_axis_view_fast_t fast, int region_ok,
1101
1101
  if ( ca_attach_is_alias(parent) ) {
1102
1102
  ca_attach(parent); /* window: an alias attach, cannot raise */
1103
1103
  fast(ca, (char *) data, dir);
1104
+ /* An entity that keeps no buffer at rest (the mask of a CAObject, a
1105
+ CASource) attaches a copy, which only its sync writes back; for
1106
+ memory it lends, the sync does nothing. */
1107
+ if ( dir == CA_XFER_PUT ) {
1108
+ ca_sync(parent);
1109
+ }
1104
1110
  ca_detach(parent);
1105
1111
  return;
1106
1112
  }
data/ext/ca_axis_group.c CHANGED
@@ -25,7 +25,7 @@
25
25
  (see the section comment above `rb_ca_axis_group_scan`).
26
26
 
27
27
  Group codes outside [0, k) at any bundle mark the slab element unassigned
28
- (skipped, contributing to no group) — mirrors digitize OOB.
28
+ (skipped, contributing to no group) — mirrors bin_to OOB.
29
29
 
30
30
  Float reductions carry the CArray ε-close contract: relative error bounded,
31
31
  not bit-exact.
@@ -850,9 +850,8 @@ rb_ca_reduceat_quantile (VALUE self, VALUE roffsets, VALUE rp0, VALUE rp25,
850
850
  group order (float64, weight mask propagated). A cell contributes iff its
851
851
  value AND its weight are present. wsum_out = Sum(v*w) (0.0 for a segment with
852
852
  no present pair, the additive identity). wmean_out = Sum(v*w)/Sum(w), masked
853
- when the segment has no present pair (matching CArray#wmean UNDEF); a present
854
- segment whose weights sum to zero yields NaN/Inf from the division (core's
855
- 0/0 contract). */
853
+ when the segment has no present pair or its weights sum to zero, as
854
+ CArray#wmean leaves both undefined. */
856
855
  #define REDUCEAT_WSUM_BODY(T) \
857
856
  do { \
858
857
  const T *gp = (const T *) grouped->ptr; \
@@ -866,7 +865,7 @@ rb_ca_reduceat_quantile (VALUE self, VALUE roffsets, VALUE rp0, VALUE rp25,
866
865
  { double wv = wp[j]; svw += (double) gp[j] * wv; sw += wv; cnt++; } \
867
866
  } \
868
867
  wsp[c] = svw; \
869
- if ( cnt == 0 ) { wmp[c] = 0.0; wmm[c] = 1; } /* no present pair */ \
868
+ if ( cnt == 0 || sw == 0.0 ) { wmp[c] = 0.0; wmm[c] = 1; } /* undefined */ \
870
869
  else wmp[c] = svw / sw; \
871
870
  } \
872
871
  } while (0)
@@ -1452,15 +1451,16 @@ rb_ca_fiber_scatter_wsum_wmean (VALUE self, VALUE rcodes, VALUE rweights,
1452
1451
  h->data_type);
1453
1452
  }
1454
1453
 
1455
- /* Compute wmean = wsum / sum-of-weights; mask cells with no present pair. */
1454
+ /* Compute wmean = wsum / sum-of-weights; mask cells with no present pair
1455
+ and cells whose weights sum to zero, as CArray#wmean does. */
1456
1456
  ca_create_mask(wmean);
1457
1457
  wmm = (boolean8_t *) wmean->mask->ptr;
1458
1458
  for ( cell = 0; cell < total; cell++ ) {
1459
- if ( cntp[cell] == 0 ) {
1459
+ if ( cntp[cell] == 0 || wsw[cell] == 0.0 ) {
1460
1460
  wmp[cell] = 0.0;
1461
1461
  wmm[cell] = 1;
1462
1462
  } else {
1463
- wmp[cell] = wsp[cell] / wsw[cell]; /* 0/0 -> NaN naturally (core contract) */
1463
+ wmp[cell] = wsp[cell] / wsw[cell];
1464
1464
  }
1465
1465
  }
1466
1466