Skip to content

reject out-of-range values in the double to float conversions - #1249

Merged
facontidavide merged 3 commits into
BehaviorTree:masterfrom
aysha-afrah26:float-conversion-range
Oct 24, 2026
Merged

facontidavide merged 3 commits into
BehaviorTree:masterfrom
aysha-afrah26:float-conversion-range

Conversation

@aysha-afrah26

Copy link
Copy Markdown
Contributor

On master, the new regression test fails with:

tests/gtest_basic_types.cpp:156: Failure
Expected: (void)BT::Any(1e300).cast<float>() throws an exception.
  Actual: it doesn't.

convertNumber skips checkTruncation whenever both types are floating point, so Any::cast<float> of a double outside the float range is a bare static_cast<float>. The standard leaves that conversion undefined ([conv.double]) and in practice the caller quietly gets inf, while every other out-of-range narrowing in the same helper throws. Reaching it only takes a blackboard entry that holds a double (a script assignment, a JSON import, another node's output) read through getInput<float> or Blackboard::get<float>.

The two string paths have the same shape on toolchains without the floating-point std::from_chars (Apple libc++, the macOS CI target): convertFromString<float> and the Any::stringToNumber fallback parse a double and cast it, so "1e300" becomes inf on macOS where the from_chars<float> branch used on Linux reports result_out_of_range and fails.

The range check sits in checkTruncation next to the other numeric limits, so Any::cast, Blackboard::get and getInput all pick it up, and the two fallbacks get the same guard so they agree with the from_chars branch. Rounding between double and float stays accepted. Header-inline code plus one exported function body, no API or ABI change.

BasicTypes.FloatConversionLimits fails before and passes after. The full suite is green under ASan+UBSan (Debug) and in Release on macOS, except for the pre-existing Parallel.FailingParallel timer abort and the cmake_subproject_version local SDK failure, both identical on the untouched tree. clang-format is clean; clang-tidy-21 is not installed on this machine.

convertNumber skipped every check for a floating-to-floating conversion,
so Any::cast<float> of a double beyond the float range was a bare
static_cast that yields an infinity (undefined per [conv.double]). The
convertFromString<float> and Any::stringToNumber fallbacks used without
the floating-point std::from_chars had the same cast, while the
from_chars<float> branch rejects such literals as out of range.
@facontidavide

Copy link
Copy Markdown
Collaborator

Thanks @aysha-afrah26, the bug is real and I'd like to merge this, but there's an off-by-half-ulp at the boundary.

from > double(FLT_MAX) also rejects finite doubles in the band (FLT_MAX, FLT_MAX + ½ ulp), roughly (3.4028234663852886e38, 3.4028235677973366e38). Those round to FLT_MAX and are accepted on master. The most common one is 3.4028235e38, the usual 8-digit decimal print of FLT_MAX: it's > FLT_MAX as a double, but static_cast<float> gives exactly FLT_MAX. With this PR Any(3.4028235e38).cast<float>() throws. On macOS the convertFromString<float> fallback would reject "3.4028235e+38", while std::from_chars<float> accepts it on Linux, so the platforms would diverge.

Suggested check, in both convert_impl.hpp and the basic_types.cpp fallback: reject only when the narrowing overflows.

const auto narrowed = static_cast<To>(from);
if(std::isinf(narrowed) && !std::isinf(from)) { /* throw */ }

(or compare with >= 0x1.ffffffp+127, i.e. FLT_MAX + ½ ulp). Please also add a test asserting that 3.4028235e38 converts to FLT_MAX without throwing.

One more thing: this PR conflicts textually with the already-merged #1238 in tests/gtest_basic_types.cpp (both insert after DoubleToIntBoundary). Please rebase and keep both tests.

facontidavide and others added 2 commits October 24, 2026 15:31
The check rejected every finite double above FLT_MAX, but values below
FLT_MAX + half an ulp still round to FLT_MAX: 3.4028235e38, the usual
printout of FLT_MAX, was accepted before and std::from_chars<float>
accepts it too. Reject only values from FLT_MAX + half an ulp on, which
round to infinity, in both checkTruncation and the macOS
convertFromString<float> fallback.

Also skip the check for widening conversions (float to double), where
casting the limits of To to From would itself overflow, and move it to a
helper to keep checkTruncation readable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@facontidavide

Copy link
Copy Markdown
Collaborator

To move this forward I pushed two commits on top of yours (thanks for enabling maintainer edits): a merge of master, which resolves the test conflict with #1238, and a fix for the boundary discussed above. Values are now rejected only from FLT_MAX + ½ ulp on, both in checkTruncation and in the macOS fallback. I also skipped the check for widening conversions and added tests that 3.4028235e38 rounds to FLT_MAX. I'll squash-merge once CI is green; you stay the commit author.

@facontidavide
facontidavide merged commit fca5e6c into BehaviorTree:master Oct 24, 2026
17 checks passed
@aysha-afrah26

Copy link
Copy Markdown
Contributor Author

Good catch on the half-ulp band, I hadn't considered the 8-digit FLT_MAX print. Thanks for fixing it up and merging.

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.

2 participants