Skip to content

Ensure consistency between NumberUtils.isCreatable and createNumber for extreme exponents#1623

Closed
FloydDsilva wants to merge 11 commits intoapache:masterfrom
FloydDsilva:fix/num-isCreatable
Closed

Ensure consistency between NumberUtils.isCreatable and createNumber for extreme exponents#1623
FloydDsilva wants to merge 11 commits intoapache:masterfrom
FloydDsilva:fix/num-isCreatable

Conversation

@FloydDsilva
Copy link
Copy Markdown

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.

floyd.dsilva added 3 commits April 3, 2026 17:43
…rror handling for invalid exponential formats in createNumber and updated isCreatable to handle edge cases. Added new test cases for extreme exponent values.
@garydgregory
Copy link
Copy Markdown
Member

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.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem necessary. Also the original cause is lost, making the input harder to debug.

Copy link
Copy Markdown
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FloydDsilva

Please see my comment.

floyd.dsilva added 2 commits April 5, 2026 18:27
… removing unnecessary line breaks and ensuring consistent formatting.
@FloydDsilva
Copy link
Copy Markdown
Author

sorry for the failed build it did not give me any issue when i ran "mvn"

@FloydDsilva
Copy link
Copy Markdown
Author

i think the behaviour for BigDecimal for java 8 and java 21 is different for 1E2147483648

@garydgregory
Copy link
Copy Markdown
Member

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.
Copy link
Copy Markdown
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need more tests around the edge cases.
TY!

floyd.dsilva and others added 3 commits April 8, 2026 22:06
…xponent values for Java 21 and later. Added comprehensive test cases to validate behavior for various exponent scenarios.
@garydgregory
Copy link
Copy Markdown
Member

garydgregory commented Apr 8, 2026

@FloydDsilva

I had to fix more Checkstyle errors... Do run mvn as noted in the PR template before you push and fix all issues please.

Copy link
Copy Markdown
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FloydDsilva

The unit tests fail.

…rgest positive exponent and enhance test cases for various exponent formats, including edge cases for Java 21 compatibility.
@garydgregory
Copy link
Copy Markdown
Member

Hi @chtompki

Does this look OK to you from a mathy POV?

@chtompki
Copy link
Copy Markdown
Member

Thinking...like the edge case testing with exponent

@chtompki
Copy link
Copy Markdown
Member

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

garydgregory added a commit to garydgregory/commons-lang that referenced this pull request Apr 15, 2026
NumberUtils.createNumber(String), exactly

Add tests from PR apache#1623
@garydgregory
Copy link
Copy Markdown
Member

See #1626 for simpler solution CC @chtompki

garydgregory added a commit that referenced this pull request Apr 16, 2026
…(String), exactly (#1626)

* Add testLang1641()

* Rename some test methods

* NumberUtils.isCreatable(String) should match
NumberUtils.createNumber(String), exactly

Add tests from PR #1623
@garydgregory
Copy link
Copy Markdown
Member

Closing in favor of #1626

@FloydDsilva
Copy link
Copy Markdown
Author

FloydDsilva commented Apr 16, 2026

Closing in favor of #1626
Hi @garydgregory, it’s a neat and simple approach.

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?

@garydgregory
Copy link
Copy Markdown
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants