This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch support/struts-6-x-x
in repository https://gitbox.apache.org/repos/asf/struts.git


The following commit(s) were added to refs/heads/support/struts-6-x-x by this 
push:
     new 016c2ec9c WW-5711 fix(conversion): bound fraction digits when 
formatting BigDecimal (#1888)
016c2ec9c is described below

commit 016c2ec9c3a62b3b64807c58d67ffbbebdf62af3
Author: Lukasz Lenart <[email protected]>
AuthorDate: Thu Sep 3 13:51:02 2026 +0200

    WW-5711 fix(conversion): bound fraction digits when formatting BigDecimal 
(#1888)
    
    StringConverter formatted BigDecimal, Double and Float with
    maximumFractionDigits set to Integer.MAX_VALUE. That constant arrived
    with WW-4871, which fixed round-trip precision loss for double and
    float; both of those types are naturally bounded, the widest being
    Double.MIN_VALUE at 325 fraction digits.
    
    BigDecimal has no such bound. DecimalFormat honours
    maximumFractionDigits literally and pads the fraction out to the
    value's full scale, so the length of the formatted output followed the
    scale of the value rather than its precision.
    
    Bound the setting to 340. Every double and float value still formats in
    full, as does every BigDecimal within that range; beyond it the value is
    rounded to the bound. The existing round-trip assertions for
    Double.MIN_VALUE (325 fraction digits) and for a BigDecimal slightly
    wider than double (326) are untouched and still pass.
    
    Backport of the same change on main, adjusted for the
    com.opensymphony.xwork2 package layout of this line.
    
    Fixes: https://issues.apache.org/jira/browse/WW-5711
    
    
    Claude-Session: https://claude.ai/code/session_01LwgeV4TN78ke2hHKTVWAUP
    
    Co-authored-by: Claude Opus 5 <[email protected]>
---
 .../xwork2/conversion/impl/StringConverter.java            | 14 +++++++++++++-
 .../xwork2/conversion/impl/StringConverterTest.java        | 13 +++++++++++++
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java
 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java
index c1abc9b7c..6b1cb2fbc 100644
--- 
a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java
+++ 
b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java
@@ -36,6 +36,18 @@ import java.util.Objects;
 
 public class StringConverter extends DefaultTypeConverter {
 
+    /**
+     * Upper bound on the number of fraction digits emitted when formatting a 
number.
+     * <p>
+     * Covers every {@code double} and {@code float} value in full - the 
widest is
+     * {@link Double#MIN_VALUE} at 325 fraction digits - so the round-trip 
precision
+     * introduced by WW-4871 is preserved. Beyond that bound the length of the 
output
+     * would follow the scale of the value rather than its precision, so a
+     * {@link BigDecimal} scaled past this limit is rounded to it.
+     */
+    private static final int MAX_FRACTION_DIGITS = 340;
+
+
     @Override
     public Object convertValue(Map<String, Object> context, Object target, 
Member member, String propertyName, Object value, Class toType) {
         String result;
@@ -86,7 +98,7 @@ public class StringConverter extends DefaultTypeConverter {
             // TODO: delete this variable and corresponding if statement when 
jdk fixed java.text.NumberFormat.format's behavior with Float
             Object fixedValue = value;
             if (BigDecimal.class.isInstance(value) || 
Double.class.isInstance(value) || Float.class.isInstance(value)) {
-                format.setMaximumFractionDigits(Integer.MAX_VALUE);
+                format.setMaximumFractionDigits(MAX_FRACTION_DIGITS);
                 if (Float.class.isInstance(value)) {
                     fixedValue = Double.valueOf(value.toString());
                 }
diff --git 
a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/StringConverterTest.java
 
b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/StringConverterTest.java
index 92d0814d6..afc6072cb 100644
--- 
a/core/src/test/java/com/opensymphony/xwork2/conversion/impl/StringConverterTest.java
+++ 
b/core/src/test/java/com/opensymphony/xwork2/conversion/impl/StringConverterTest.java
@@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.struts2.StrutsInternalTestCase;
 
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Locale;
 import java.util.Map;
 
@@ -102,6 +103,18 @@ public class StringConverterTest extends 
StrutsInternalTestCase {
         assertEquals(aBitBiggerThanDouble.substring(0, 309) + "," + 
aBitBiggerThanDouble.substring(310), value);
     }
 
+    public void testBigDecimalFractionDigitsAreBounded() throws Exception {
+        // given
+        StringConverter converter = new StringConverter();
+        Map<String, Object> context = createContextWithLocale(new Locale("pl", 
"PL"));
+
+        // when the scale of the value exceeds the supported number of 
fraction digits
+        Object 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("0", value);
+    }
+
     public void testStringArrayToStringConversion() {
         // given
         StringConverter converter = new StringConverter();

Reply via email to