Hi all,

I found an issue in the table-model grouped MAX aggregation for FLOAT
and DOUBLE values.

Bug description
---------------

When all values in a group are zero, MAX returns NULL instead of 0.
MIN and AVG return the expected result, and MAX_BY(value, value) also
works correctly.

The issue is not limited to zero. It affects any FLOAT or DOUBLE group
whose maximum value is non-positive, including groups containing only
negative values.

For example:

  SELECT device_id, MAX(value), MIN(value), AVG(value)
  FROM table1
  GROUP BY device_id;

If all value entries for a device are 0, the result is:

  MAX(value) = NULL
  MIN(value) = 0
  AVG(value) = 0

Root cause
----------

GroupedMaxAccumulator used the following initial values:

  new FloatBigArray(Float.MIN_VALUE)
  new DoubleBigArray(Double.MIN_VALUE)

In Java, Float.MIN_VALUE and Double.MIN_VALUE are the smallest
positive non-zero values, rather than the most negative representable
values.

The grouped accumulator updates its state using logic equivalent to:

  if (value >= currentMax) {
    inits.set(groupId, true);
    currentMax = value;
  }

Therefore, when every input value is zero or negative, the condition
is never satisfied. The group's initialization flag remains false, and
evaluateFinal() treats the group as empty and emits NULL.

This explains why positive values work and why MAX_BY is unaffected.
MAX_BY explicitly checks whether the group has been initialized before
comparing values.

Affected scope
--------------

The issue affects table-model grouped MAX(FLOAT) and MAX(DOUBLE) when
the query uses GroupedMaxAccumulator, such as the hash or
streaming-hash aggregation path.

The non-grouped MaxAccumulator and the tree-model MAX_VALUE
implementation use separate initialization handling and are not
affected.

Proposed fix
------------

Initialize the floating-point maximum state with negative infinity:

  new FloatBigArray(Float.NEGATIVE_INFINITY)
  new DoubleBigArray(Double.NEGATIVE_INFINITY)

This preserves the existing sentinel-based update path while correctly
handling zero, negative values, and negative infinity. Truly empty
groups still return NULL because their initialization flags remain
false.

Validation
----------

The fix includes:

- Unit tests for FLOAT and DOUBLE values containing zero, negative
values, and negative infinity.
- Coverage for both raw input and intermediate aggregation input.
- Verification that groups without input still return NULL.
- A table-model integration test covering the non-stream grouped
aggregation path.

The targeted unit and integration tests passed, along with a clean
37-module reactor build.

I opened a draft PR here:
https://github.com/apache/iotdb/pull/18300

Feedback is welcome.

Best regards,
Yuan Tian

Reply via email to