From 6f7554231515d2554ac8ca96f2ed8ca5113b6658 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 12 May 2026 16:28:13 +0300 Subject: [PATCH] fix(cf): prevent integer overflow in calculated field SUM output SimpleCalculatedFieldState.formatResult cast the double result down to int via TbUtils.toInt when "Decimals by default" was 0 (the UI default). BigDecimal.intValue() returns only the low-order 32 bits, so a sum above ~2.1B wrapped to a negative number (e.g. 3,980,173,734 -> -314,793,562). Add TbUtils.toLong(double) alongside toInt (toInt is left untouched to preserve TBEL script behavior), switch the CF precision=0 path to it, and add a Long branch in createResultJson so the JSON node is emitted as a numeric long --- .../service/cf/ctx/state/SimpleCalculatedFieldState.java | 4 +++- .../java/org/thingsboard/script/api/tbel/TbUtils.java | 4 ++++ .../java/org/thingsboard/script/api/tbel/TbUtilsTest.java | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java index 0d018f5def..e3f1e94bf4 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java @@ -85,7 +85,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { return expressionResult; } if (decimals.equals(0)) { - return TbUtils.toInt(expressionResult); + return TbUtils.toLong(expressionResult); } return TbUtils.toFixed(expressionResult, decimals); } @@ -96,6 +96,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 { diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java index e206d2d78d..a66a1860cd 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java @@ -1186,6 +1186,10 @@ public class TbUtils { 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 boolean isNaN(double value) { return Double.isNaN(value); } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java index 622d298bbb..dbf56d5140 100644 --- a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -1249,6 +1249,14 @@ public class TbUtilsTest { Assertions.assertEquals(28, TbUtils.toInt(28.0)); } + @Test + public void toLong() { + Assertions.assertEquals(1729L, TbUtils.toLong(doubleVal)); + Assertions.assertEquals(13L, TbUtils.toLong(12.8)); + Assertions.assertEquals(28L, TbUtils.toLong(28.0)); + Assertions.assertEquals(3_980_173_734L, TbUtils.toLong(3_980_173_734.0)); + } + @Test public void isNaN() { assertFalse(TbUtils.isNaN(doubleVal));