This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new a289c8458 WW-5828 fix(conversion): bound integer digits when
formatting BigDecimal
a289c8458 is described below
commit a289c8458fa5702a18ca1c956608cc3c80838a59
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Oct 31 14:32:49 2026 +0100
WW-5828 fix(conversion): bound integer digits when formatting BigDecimal
WW-5711 bounded maximumFractionDigits to 340, so the length of a
formatted BigDecimal no longer follows a large positive scale. The
integer side had no equivalent bound: DecimalFormat keeps its default
maximumIntegerDigits of Integer.MAX_VALUE and pads the integer part of
a value with a large negative scale out in full, so the output length
followed the scale of the value rather than its precision.
setMaximumIntegerDigits is not the right bound, because DecimalFormat
applies it by dropping the leading digits rather than by rounding.
Bound the integer digit count (precision - scale, computed in long
because the subtraction overflows int at the edge of the scale range)
to 340 and, past it, fall back to BigDecimal.toString(), which renders
the value compactly in scientific notation. Every double still formats
in full; the widest, Double.MAX_VALUE, has 309 integer digits and the
existing test pins it.
Fixes: https://issues.apache.org/jira/browse/WW-5828
Co-Authored-By: Claude Mythos 5.1 <[email protected]>
---
.../struts2/conversion/impl/StringConverter.java | 17 ++++++++++++
.../conversion/impl/StringConverterTest.java | 30 ++++++++++++++++++++++
2 files changed, 47 insertions(+)
diff --git
a/core/src/main/java/org/apache/struts2/conversion/impl/StringConverter.java
b/core/src/main/java/org/apache/struts2/conversion/impl/StringConverter.java
index e79fcf7c4..0260e8b47 100644
--- a/core/src/main/java/org/apache/struts2/conversion/impl/StringConverter.java
+++ b/core/src/main/java/org/apache/struts2/conversion/impl/StringConverter.java
@@ -47,6 +47,16 @@ public class StringConverter extends DefaultTypeConverter {
*/
private static final int MAX_FRACTION_DIGITS = 340;
+ /**
+ * Upper bound on the number of integer digits emitted when formatting a
number.
+ * <p>
+ * Covers every {@code double} value in full - the widest is {@link
Double#MAX_VALUE}
+ * at 309 integer digits. A {@link BigDecimal} wider than this is rendered
by
+ * {@link BigDecimal#toString()} instead, so the length of the output
follows its
+ * precision rather than its scale.
+ */
+ private static final int MAX_INTEGER_DIGITS = 340;
+
@Override
public Object convertValue(Map<String, Object> context, Object target,
Member member, String propertyName, Object value, Class toType) {
@@ -92,6 +102,9 @@ public class StringConverter extends DefaultTypeConverter {
}
protected String convertToString(Locale locale, Object value) {
+ if (value instanceof BigDecimal decimal && integerDigits(decimal) >
MAX_INTEGER_DIGITS) {
+ return decimal.toString();
+ }
if (value instanceof Number) {
NumberFormat format = NumberFormat.getNumberInstance(locale);
format.setGroupingUsed(false);
@@ -109,4 +122,8 @@ public class StringConverter extends DefaultTypeConverter {
}
}
+ private static long integerDigits(BigDecimal value) {
+ return (long) value.precision() - value.scale();
+ }
+
}
diff --git
a/core/src/test/java/org/apache/struts2/conversion/impl/StringConverterTest.java
b/core/src/test/java/org/apache/struts2/conversion/impl/StringConverterTest.java
index fd3f673ab..504c45119 100644
---
a/core/src/test/java/org/apache/struts2/conversion/impl/StringConverterTest.java
+++
b/core/src/test/java/org/apache/struts2/conversion/impl/StringConverterTest.java
@@ -115,6 +115,36 @@ public class StringConverterTest extends
StrutsInternalTestCase {
assertEquals("0", value);
}
+ public void testBigDecimalIntegerDigitsAreBounded() throws Exception {
+ // given
+ StringConverter converter = new StringConverter();
+ Map<String, Object> context = createContextWithLocale(new Locale("pl",
"PL"));
+
+ // when the number of integer digits is within the bound
+ Object value = converter.convertValue(context, null, null, null, new
BigDecimal(BigInteger.ONE, -339), null);
+
+ // then the value is rendered in full
+ assertEquals("1" + StringUtils.repeat('0', 339), value);
+
+ // when the number of integer digits exceeds the bound
+ value = converter.convertValue(context, null, null, null, new
BigDecimal(BigInteger.ONE, -340), null);
+
+ // then the value is rendered in its compact form rather than padded
out to its scale
+ assertEquals("1E+340", value);
+
+ // when the scale is far beyond the bound
+ value = converter.convertValue(context, null, null, null, new
BigDecimal(BigInteger.ONE, -100_000), null);
+
+ // then the length of the output is bounded by the converter, not by
the scale of the value
+ assertEquals("1E+100000", value);
+
+ // when the scale sits at the edge of its range
+ value = converter.convertValue(context, null, null, null, new
BigDecimal(BigInteger.ONE, Integer.MIN_VALUE + 1), null);
+
+ // then the bound still applies
+ assertEquals("1E+2147483647", value);
+ }
+
public void testStringArrayToStringConversion() {
// given
StringConverter converter = new StringConverter();