Browse Source

fix(cf): prevent integer overflow in calculated field SUM output (lts-4.3)

Merge fix-sum-overflow (lts-4.2) into lts-4.3 and adapt to the 4.3
code structure.

In 4.3 the rounding is centralized in NumberUtils.roundResult (not
TbUtils.toInt as in 4.2), so the fix moves there: rename toInt(double)
to toLong(double) and have the precision=0 path return a long.
BigDecimal.intValue() returned only the low-order 32 bits, so a
calculated field SUM above ~2.1B wrapped to a negative number
(e.g. 3,980,173,734 -> -314,793,562) when "Decimals by default"
was 0 (the UI default).

The 4.2 TbUtils.toLong addition is dropped since TbUtils.toInt is
not used in 4.3 (CF rounding flows through NumberUtils only).
SimpleCalculatedFieldState.createResultJson gains a Long branch so
the JSON node is emitted as a numeric long; the aggregation path
already routes through JacksonUtil.toString, which handles Long
transparently.
pull/15621/head
Volodymyr Babak 3 months ago
parent
commit
0c027bbce7
  1. 2
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java
  2. 6
      common/util/src/main/java/org/thingsboard/common/util/NumberUtils.java
  3. 12
      common/util/src/test/java/org/thingsboard/common/util/NumberUtilsTest.java

2
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java

@ -67,6 +67,8 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
ObjectNode valuesNode = JacksonUtil.newObjectNode();
if (result instanceof Double doubleValue) {
valuesNode.put(outputName, doubleValue);
} else if (result instanceof Long longValue) {
valuesNode.put(outputName, longValue);
} else if (result instanceof Integer integerValue) {
valuesNode.put(outputName, integerValue);
} else {

6
common/util/src/main/java/org/thingsboard/common/util/NumberUtils.java

@ -32,8 +32,8 @@ public class NumberUtils {
return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).floatValue();
}
public static int toInt(double value) {
return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP).intValue();
public static long toLong(double value) {
return BigDecimal.valueOf(value).setScale(0, RoundingMode.HALF_UP).longValue();
}
public static Object roundResult(double value, Integer precision) {
@ -41,7 +41,7 @@ public class NumberUtils {
return value;
}
if (precision.equals(0)) {
return toInt(value);
return toLong(value);
}
return toFixed(value, precision);
}

12
common/util/src/test/java/org/thingsboard/common/util/NumberUtilsTest.java

@ -45,17 +45,19 @@ public class NumberUtilsTest {
}
@Test
public void toInt() {
assertThat(NumberUtils.toInt(doubleVal)).isEqualTo(1729);
assertThat(NumberUtils.toInt(12.8)).isEqualTo(13);
assertThat(NumberUtils.toInt(28.0)).isEqualTo(28);
public void toLong() {
assertThat(NumberUtils.toLong(doubleVal)).isEqualTo(1729L);
assertThat(NumberUtils.toLong(12.8)).isEqualTo(13L);
assertThat(NumberUtils.toLong(28.0)).isEqualTo(28L);
assertThat(NumberUtils.toLong(3_980_173_734.0)).isEqualTo(3_980_173_734L);
}
@Test
public void roundResult() {
assertThat(NumberUtils.roundResult(doubleVal, null)).isEqualTo(1729.1729);
assertThat(NumberUtils.roundResult(doubleVal, 0)).isEqualTo(1729);
assertThat(NumberUtils.roundResult(doubleVal, 0)).isEqualTo(1729L);
assertThat(NumberUtils.roundResult(doubleVal, 2)).isEqualTo(1729.17);
assertThat(NumberUtils.roundResult(3_980_173_734.0, 0)).isEqualTo(3_980_173_734L);
}
}

Loading…
Cancel
Save