From 46f608f18487a49cfb76094bc62d33ab959b2dd6 Mon Sep 17 00:00:00 2001 From: Ryan Friedman Date: Tue, 5 Mar 2024 01:00:08 -0700 Subject: [PATCH 1/2] Add support for NaN constants and defaults in ROS IDL * Add nan default example and tests * Add NaN constant example and tests * Test NaNs on float64 and float32 in C++ Signed-off-by: Ryan Friedman --- rosidl_adapter/test/test_constant.py | 10 ++++++++++ rosidl_generator_c/resource/idl__struct.h.em | 2 +- rosidl_generator_c/rosidl_generator_c/__init__.py | 11 +++++++++-- rosidl_generator_cpp/resource/idl__struct.hpp.em | 1 + rosidl_generator_cpp/resource/msg__struct.hpp.em | 8 ++++++-- .../rosidl_generator_cpp/__init__.py | 6 ++++++ rosidl_generator_tests/CMakeLists.txt | 2 ++ rosidl_generator_tests/msg/NanValueConstant.msg | 2 ++ rosidl_generator_tests/msg/NanValueDefault.msg | 2 ++ .../test/rosidl_generator_cpp/test_interfaces.cpp | 13 +++++++++++++ 10 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 rosidl_generator_tests/msg/NanValueConstant.msg create mode 100644 rosidl_generator_tests/msg/NanValueDefault.msg diff --git a/rosidl_adapter/test/test_constant.py b/rosidl_adapter/test/test_constant.py index 26d396abd..6ee8fd161 100644 --- a/rosidl_adapter/test/test_constant.py +++ b/rosidl_adapter/test/test_constant.py @@ -30,6 +30,16 @@ def test_constant_constructor(): with pytest.raises(ValueError): Constant('bool', 'FOO', None) + # NaN is case insensitive in python, so test a few variations. + value = Constant('float32', 'FOO', 'nan') + value = Constant('float32', 'FOO', 'Nan') + value = Constant('float32', 'FOO', 'NaN') + value = Constant('float32', 'FOO', 'NAN') + value = Constant('float64', 'FOO', 'nan') + value = Constant('float64', 'FOO', 'Nan') + value = Constant('float64', 'FOO', 'NaN') + value = Constant('float64', 'FOO', 'NAN') + def test_constant_methods(): assert Constant('bool', 'FOO', '1') != 23 diff --git a/rosidl_generator_c/resource/idl__struct.h.em b/rosidl_generator_c/resource/idl__struct.h.em index 546507f47..e0f3571c0 100644 --- a/rosidl_generator_c/resource/idl__struct.h.em +++ b/rosidl_generator_c/resource/idl__struct.h.em @@ -32,10 +32,10 @@ extern "C" { #endif +#include #include #include #include - @####################################################################### @# Handle message @####################################################################### diff --git a/rosidl_generator_c/rosidl_generator_c/__init__.py b/rosidl_generator_c/rosidl_generator_c/__init__.py index 50bb60b9e..7dcd61a40 100644 --- a/rosidl_generator_c/rosidl_generator_c/__init__.py +++ b/rosidl_generator_c/rosidl_generator_c/__init__.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from math import isnan from rosidl_generator_type_description import parse_rihs_string from rosidl_generator_type_description import RIHS01_HASH_VALUE_SIZE @@ -209,10 +210,16 @@ def basic_value_to_c(type_, value): return f'{value}ull' if 'float' == type_.typename: - return f'{value}f' + if isnan(float(value)): + return 'nanf(\"\")' + else: + return f'{value}f' if 'double' == type_.typename: - return f'{value}l' + if isnan(float(value)): + return 'nan(\"\")' + else: + return f'{value}l' assert False, "unknown basic type '%s'" % type_ diff --git a/rosidl_generator_cpp/resource/idl__struct.hpp.em b/rosidl_generator_cpp/resource/idl__struct.hpp.em index c4b7c289e..8f0cfffd3 100644 --- a/rosidl_generator_cpp/resource/idl__struct.hpp.em +++ b/rosidl_generator_cpp/resource/idl__struct.hpp.em @@ -28,6 +28,7 @@ include_directives = set() #include #include +#include #include #include #include diff --git a/rosidl_generator_cpp/resource/msg__struct.hpp.em b/rosidl_generator_cpp/resource/msg__struct.hpp.em index 6e583888f..90cb9e161 100644 --- a/rosidl_generator_cpp/resource/msg__struct.hpp.em +++ b/rosidl_generator_cpp/resource/msg__struct.hpp.em @@ -286,8 +286,12 @@ non_defaulted_zero_initialized_members = [ @[ if constant.type.typename in UNSIGNED_INTEGER_TYPES]@ u@ @[ end if]@ -@[ elif constant.type.typename == 'float']@ - @(constant.value)f@ +@[ elif constant.type.typename == 'float' or constant.type.typename == 'double']@ +@{ +from rosidl_generator_cpp import primitive_value_to_cpp +val = primitive_value_to_cpp(constant.type, constant.value) +}@ + @(val)@ @[ else]@ @(constant.value)@ @[ end if]; diff --git a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py index b076fd3ed..f138bcf10 100644 --- a/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py +++ b/rosidl_generator_cpp/rosidl_generator_cpp/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. from ast import literal_eval +from math import isnan from rosidl_parser.definition import AbstractGenericString from rosidl_parser.definition import AbstractNestedType @@ -195,6 +196,11 @@ def primitive_value_to_cpp(type_, value): if type_.typename == 'boolean': return 'true' if value else 'false' + if type_.typename in ['double', 'long double', 'float']: + # Handle special floating types here. + if isnan(float(value)): + return f'std::numeric_limits<{type_.typename}>::quiet_NaN()' + if type_.typename in [ 'short', 'unsigned short', 'char', 'wchar', diff --git a/rosidl_generator_tests/CMakeLists.txt b/rosidl_generator_tests/CMakeLists.txt index b2b5d13fd..d81b0dc62 100644 --- a/rosidl_generator_tests/CMakeLists.txt +++ b/rosidl_generator_tests/CMakeLists.txt @@ -35,6 +35,8 @@ if(BUILD_TESTING) ${test_interface_files_MSG_FILES} ${test_interface_files_SRV_FILES} msg/BasicIdl.idl + msg/NanValueConstant.msg + msg/NanValueDefault.msg msg/SmallConstant.msg ADD_LINTER_TESTS SKIP_INSTALL diff --git a/rosidl_generator_tests/msg/NanValueConstant.msg b/rosidl_generator_tests/msg/NanValueConstant.msg new file mode 100644 index 000000000..9dd901085 --- /dev/null +++ b/rosidl_generator_tests/msg/NanValueConstant.msg @@ -0,0 +1,2 @@ +float32 FLOAT32_NAN=NaN +float64 FLOAT64_NAN=nan diff --git a/rosidl_generator_tests/msg/NanValueDefault.msg b/rosidl_generator_tests/msg/NanValueDefault.msg new file mode 100644 index 000000000..98aa6a2a4 --- /dev/null +++ b/rosidl_generator_tests/msg/NanValueDefault.msg @@ -0,0 +1,2 @@ +float32 float32_nan NaN +float64 float64_nan nan diff --git a/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp b/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp index 15bb4b7ef..0b2ab6e3d 100644 --- a/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp +++ b/rosidl_generator_tests/test/rosidl_generator_cpp/test_interfaces.cpp @@ -41,6 +41,8 @@ #include "rosidl_generator_tests/msg/multi_nested.hpp" #include "rosidl_generator_tests/msg/nested.hpp" #include "rosidl_generator_tests/msg/small_constant.hpp" +#include "rosidl_generator_tests/msg/nan_value_constant.hpp" +#include "rosidl_generator_tests/msg/nan_value_default.hpp" #include "rosidl_generator_tests/msg/strings.hpp" #include "rosidl_generator_tests/msg/unbounded_sequences.hpp" #include "rosidl_generator_tests/msg/w_strings.hpp" @@ -483,6 +485,11 @@ TEST(Test_messages, constants_assign) { ASSERT_EQ(x, rosidl_generator_tests::msg::SmallConstant::FLOAT32_CONST); } +TEST(Test_messages, nan_constants) { + ASSERT_TRUE(std::isnan(rosidl_generator_tests::msg::NanValueConstant::FLOAT32_NAN)); + ASSERT_TRUE(std::isnan(rosidl_generator_tests::msg::NanValueConstant::FLOAT64_NAN)); +} + // Defaults TEST(Test_messages, defaults) { rosidl_generator_tests::msg::Defaults message; @@ -501,6 +508,12 @@ TEST(Test_messages, defaults) { TEST_BASIC_TYPE_FIELD_ASSIGNMENT(message, uint64_value, 50000000ull, UINT64_MAX); } +TEST(Test_messages, nan_defaults) { + rosidl_generator_tests::msg::NanValueDefault nan_val_default; + ASSERT_TRUE(std::isnan(nan_val_default.float32_nan)); + ASSERT_TRUE(std::isnan(nan_val_default.float64_nan)); +} + // String array with default values TEST(Test_messages, string_arrays_default) { rosidl_generator_tests::msg::Arrays message; From 2e0e7794996212fdc410f50f8e5c729b23b8753a Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 7 Sep 2024 00:29:09 -0600 Subject: [PATCH 2/2] Combine constant tests in a loop Co-authored-by: Henry Moore Signed-off-by: Ryan --- rosidl_adapter/test/test_constant.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/rosidl_adapter/test/test_constant.py b/rosidl_adapter/test/test_constant.py index 6ee8fd161..f6240c414 100644 --- a/rosidl_adapter/test/test_constant.py +++ b/rosidl_adapter/test/test_constant.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import math + import pytest from rosidl_adapter.parser import Constant @@ -31,14 +33,9 @@ def test_constant_constructor(): Constant('bool', 'FOO', None) # NaN is case insensitive in python, so test a few variations. - value = Constant('float32', 'FOO', 'nan') - value = Constant('float32', 'FOO', 'Nan') - value = Constant('float32', 'FOO', 'NaN') - value = Constant('float32', 'FOO', 'NAN') - value = Constant('float64', 'FOO', 'nan') - value = Constant('float64', 'FOO', 'Nan') - value = Constant('float64', 'FOO', 'NaN') - value = Constant('float64', 'FOO', 'NAN') + for nan_string in ['nan', 'Nan', 'NaN', 'NAN', 'nan', 'Nan', 'NaN', 'NAN']: + value = Constant('float32', 'FOO', nan_string) + assert math.isnan(value.value) def test_constant_methods():