Skip to content

Detect overflow for floating-point numbers in NumberUtils - #37586

Merged
sbrannen merged 2 commits into
spring-projects:mainfrom
kimseongchan0914:numberutils-floating-point-overflow
Oct 28, 2026
Merged

sbrannen merged 2 commits into
spring-projects:mainfrom
kimseongchan0914:numberutils-floating-point-overflow

Conversation

@kimseongchan0914

@kimseongchan0914 kimseongchan0914 commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Overview

NumberUtils.convertNumberToTargetClass() already checks for overflow when converting BigInteger and BigDecimal values to integral types (see gh-16060 and gh-18613), but Double and Float values are passed to Number.longValue() unchecked. Values outside the long range therefore saturate silently, and NaN becomes 0.

The same behavior shows up through DefaultConversionService:

DefaultConversionService cs = new DefaultConversionService();
cs.convert(1e20, Integer.class);    // ConversionFailedException (overflow)
cs.convert(1e20, Long.class);       // 9223372036854775807
cs.convert(Double.NaN, Long.class); // 0
cs.convert(1e20, BigInteger.class); // 9223372036854775807
cs.convert(new BigDecimal("1e20"), BigInteger.class); // 100000000000000000000

This PR contains two commits:

  1. Detect long overflow for floating-point numbers: checkedLongValue() now rejects Double/Float values outside [-2^63, 2^63), which includes NaN and infinite values. Integral targets, Long among them, now raise the same overflow exception that Integer raises today.
  2. Avoid precision loss for floating-point numbers to BigInteger: these values are converted via BigDecimal, the same way the existing BigDecimal to BigInteger path works, instead of being truncated to the long range.

In-range values behave as before, for example 1.5 still converts to 1. SpEL operators and comparisons are not affected, because they handle Double/Float operands before taking the BigInteger path.

Related Issues

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 24, 2026
@sbrannen sbrannen added in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Oct 25, 2026
@sbrannen sbrannen changed the title Detect overflow for floating-point numbers in NumberUtils Detect overflow for floating-point numbers in NumberUtils Oct 25, 2026
@sbrannen sbrannen self-assigned this Oct 25, 2026
@sbrannen sbrannen added this to the 7.1.0-RC2 milestone Oct 25, 2026

@sbrannen sbrannen left a comment

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.

Hi @kimseongchan0914,

Congratulations on submitting your first PR for the Spring Framework! 👍

The first commit looks good: detecting overflow for Double and Float values when converting to Long makes sense, as does rejecting NaN and infinite values for all integral types. Your range check handles the edge cases correctly, including the exclusive upper bound at 2^63. For readability, you might consider introducing constants for the bounds (-0x1p63 and 0x1p63) instead of using -(double) Long.MIN_VALUE.

However, the second commit introduces a regression for large values that are still within the long range. Float.toString() and Double.toString() return the shortest decimal string that represents the value, not its exact value, so digits are lost. For example:

  • 0x1p40f (2^40) is converted to 1099511600000 instead of 1099511627776.
  • 0x1p60 (2^60) is converted to 1152921504606847000 instead of 1152921504606846976.

Prior to this PR, both conversions were exact. In addition, converting NaN or an infinite value to BigInteger now fails with a rather cryptic NumberFormatException instead of the overflow exception used for the other integral types.

To address that in the second commit, please:

  • Use new BigDecimal(number.doubleValue()).toBigInteger(), which is exact for all finite values and consistent with the existing behavior for in-range values.
  • Raise the overflow exception for NaN and infinite values before converting to BigInteger.

Regarding tests, please add assertions for:

  • Large in-range values converted to BigInteger (for example, 0x1p40f and 0x1p60), verifying that the results match the Long conversion.
  • NaN and infinite values converted to BigInteger, verifying that the overflow exception is thrown.

Since this changes existing behavior (for example, NaN now throws instead of converting to 0), we'll consider it for 7.1 rather than backporting it.

Cheers,

Sam

@sbrannen sbrannen added the status: waiting-for-feedback We need additional information before we can continue label Oct 26, 2026
Prior to this commit, NumberUtils.convertNumberToTargetClass()
silently saturated Double and Float values outside the long range
when converting to Long, for example 1e20 became Long.MAX_VALUE,
while the same value raised an overflow exception for Integer.
Likewise, NaN was silently converted to 0 for all integral types.

This commit extends the long overflow check to Double and Float
values, consistent with the existing check for BigInteger and
BigDecimal values.

Signed-off-by: 김성찬 <265934944+kimseongchan0914@users.noreply.github.com>
Prior to this commit, NumberUtils.convertNumberToTargetClass()
converted Double and Float values to BigInteger through
Number.longValue(), so values beyond the long range were silently
truncated, for example 1e20 became Long.MAX_VALUE.

This commit converts such values via their exact BigDecimal
representation, analogous to the existing BigDecimal to BigInteger
conversion, and raises an overflow exception for NaN and infinite
values, consistent with the other integral target types.

Signed-off-by: 김성찬 <265934944+kimseongchan0914@users.noreply.github.com>
@kimseongchan0914
kimseongchan0914 force-pushed the numberutils-floating-point-overflow branch from 105a1b2 to afc046f Compare October 26, 2026 22:45
@kimseongchan0914

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @sbrannen!

I've updated both commits:

  • Introduced LONG_MIN_AS_DOUBLE / LONG_MAX_AS_DOUBLE_EXCLUSIVE constants for the range check.
  • Switched to new BigDecimal(number.doubleValue()).toBigInteger() for exact conversion, and raised the overflow exception for NaN and infinite values before converting.
  • Added the requested assertions for 0x1p40f / 0x1p60 (matching the Long conversion) and for NaN / infinite values. I also fixed the existing 1e20f expectation, which had baked in the lossy toString() result.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Oct 26, 2026
@sbrannen sbrannen removed the status: feedback-provided Feedback has been provided label Oct 28, 2026
@sbrannen
sbrannen merged commit a13ae06 into spring-projects:main Oct 28, 2026
1 check passed
sbrannen added a commit that referenced this pull request Oct 28, 2026
@sbrannen

Copy link
Copy Markdown
Member

Thanks for making the requested changes.

This has been merged into main in a13ae06 and revised in dfbe36b.

kimseongchan0914 added a commit to kimseongchan0914/kimseongchan0914 that referenced this pull request Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants