Ensure consistency between NumberUtils.isCreatable and createNumber for extreme exponents#1623
Ensure consistency between NumberUtils.isCreatable and createNumber for extreme exponents#1623FloydDsilva wants to merge 11 commits intoapache:masterfrom
Conversation
…rror handling for invalid exponential formats in createNumber and updated isCreatable to handle edge cases. Added new test cases for extreme exponent values.
|
Hello @FloydDsilva -1: The build fails. Please run 'mvn' by itself before you push. |
| try { | ||
| return createBigDecimal(str); | ||
| } catch (final NumberFormatException ignored) { | ||
| throw new NumberFormatException(str + " is not a valid number."); |
There was a problem hiding this comment.
This doesn't seem necessary. Also the original cause is lost, making the input harder to debug.
…atch block for createBigDecimal.
… removing unnecessary line breaks and ensuring consistent formatting.
|
sorry for the failed build it did not give me any issue when i ran "mvn" |
|
i think the behaviour for BigDecimal for java 8 and java 21 is different for 1E2147483648 |
Then you'll need to account for that... |
…rge exponent values based on Java version. Updated corresponding test cases for consistency.
garydgregory
left a comment
There was a problem hiding this comment.
Hello @FloydDsilva
Thank you for your updates. Please see my comment.
TY!
| } | ||
| } catch (final NumberFormatException ignored) { | ||
| return false; | ||
| return ("2147483648".equals(expStr) || "+2147483648".equals(expStr)) && SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_21); |
There was a problem hiding this comment.
This is rather messy IMO. You'll need to provide comments here to explain what's going on. The magic strings should likely be derived from constants like Integer.MAX_VALUE.
There was a problem hiding this comment.
Hey i have refactored the code and also added more test cases. Kindly let me know about if more changes are required.
| compareIsCreatableWithCreateNumber(".e10", false); // LANG-1646 | ||
| compareIsCreatableWithCreateNumber(".e10D", false); // LANG-1646 | ||
| compareIsCreatableWithCreateNumber("1E-2147483648", false); | ||
| compareIsCreatableWithCreateNumber("1E2147483648", SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_21)); |
There was a problem hiding this comment.
You need more tests around the edge cases.
TY!
…xponent values for Java 21 and later. Added comprehensive test cases to validate behavior for various exponent scenarios.
|
I had to fix more Checkstyle errors... Do run |
…rgest positive exponent and enhance test cases for various exponent formats, including edge cases for Java 21 compatibility.
|
Hi @chtompki Does this look OK to you from a mathy POV? |
|
Thinking...like the edge case testing with exponent |
|
We could simply wrap instantiation in a try catch. Return true if it works and false if it doesnt? May not be as efficient but it would work exactly |
NumberUtils.createNumber(String), exactly Add tests from PR apache#1623
|
Closing in favor of #1626 |
However, I don’t think using createNumber directly inside isCreatable is a good fit here, mainly for two reasons: 1)Performance considerations: isCreatable is commonly used in loops and validation paths. Calling createNumber would introduce object creation and additional overhead, which would be a noticeable regression for a method intended to be lightweight. 2)Separation of concerns: isCreatable is designed as a validation method that checks whether number creation is viable, not to actually construct the number. If callers already need to invoke createNumber, validating by constructing the number twice defeats the purpose of having a separate, inexpensive check. Is there some perspective i haven't accounted for or i am missing? can you help me out? |
|
Hello @FloydDsilva The main issue, I think, is having two methods with distinct, complex custom parsing code that attempts (and has obviously failed) to match not only what the JRE number classes allow but also our own rules. In addition, we want the methods to be consistent with each other, if the "is" method returns true, then the "create" method should always work, and vice versa. Unless it is measured (say with a JMH benchmark), we'll never know. Unfortunately, even with a benchmark, there are many "it depends". For example, classes like Integer, Short, Long, Byte, and Character, but not Float and Double, have their own cache to reduce object creation. Another is that the parsing code is going to favor, intentionally or not, one type over another. Parsing in the "is" method wasn't allocation free either. HTH |
Background
NumberUtils.isCreatable(String) previously returned true for certain scientific‑notation inputs (e.g. 1E-2147483648) that cannot be represented by any Java Number type. Calling NumberUtils.createNumber with such inputs resulted in a NumberFormatException, breaking the implied contract between the two methods.
Changes
Updated isCreatable to reject unrepresentable negative exponents in scientific notation.
Refined exponent parsing to correctly handle type suffixes (D, F, L) and preserve valid inputs.
Updated createNumber to gracefully handle NumberFormatException raised during BigDecimal creation by propagating a consistent and descriptive failure for invalid values.
Result
These changes restore consistency between isCreatable and createNumber, ensuring that isCreatable only returns true for values that createNumber can successfully construct, while keeping existing behavior intact for all valid numeric representations across supported Java versions.
Test cases for the same has been added.