Skip to content

Commit

Permalink
Allow NaN values to pass floating point bounds check. (#167)
Browse files Browse the repository at this point in the history
* Invert bounds check in order to permit floating point NaN values

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Allow floating point numbers set to Inf to pass bounds check

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Add float/double NaN/Inf tests

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Increase precision of float32 bounds

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Only perform float64 bounds check on non-compliant IEEE 754 systems

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Disable bounds test for float64 on IEEE 754 compatible systems

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Disable bounds test for float64 on IEEE 754 compatible systems

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Resolve failing nan/inf test by comparing the same types

Signed-off-by: Øystein Sture <os@skarvtech.com>

* Change decode error mode to replace (#176)

Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>

* Replace rosidl_cmake imports with rosidl_pycommon (#177)

Signed-off-by: Jacob Perron <jacob@openrobotics.org>

* Delete trailing whitespace

Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>

* Split fail case array check tests into two

Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>

Signed-off-by: Øystein Sture <os@skarvtech.com>
Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
Signed-off-by: Jacob Perron <jacob@openrobotics.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Co-authored-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
Co-authored-by: Jacob Perron <jacob@openrobotics.org>
Co-authored-by: Shane Loretz <sloretz@osrfoundation.org>
  • Loading branch information
4 people authored and clalancette committed Nov 7, 2022
1 parent 940a314 commit db6c5b8
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 18 deletions.
16 changes: 11 additions & 5 deletions rosidl_generator_py/resource/_msg.py.em
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ if message.structure.members:
imports.setdefault(
'import rosidl_parser.definition', []) # used for SLOT_TYPES
for member in message.structure.members:
type_ = member.type
if isinstance(type_, AbstractNestedType):
type_ = type_.value_type
if member.name != EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME:
imports.setdefault(
'import builtins', []) # used for @builtins.property
if isinstance(type_, BasicType) and type_.typename in FLOATING_POINT_TYPES:
imports.setdefault(
'import math', []) # used for math.isinf
if (
isinstance(member.type, AbstractNestedType) and
isinstance(member.type.value_type, BasicType) and
Expand Down Expand Up @@ -504,16 +510,16 @@ bound = 2**nbits
@[ if type_.typename == "float"]@
@{
name = "float"
bound = 3.402823e+38
bound = 3.402823466e+38
}@
all(val >= -@(bound) and val <= @(bound) for val in value)), \
all(not (val < -@(bound) or val > @(bound)) or math.isinf(val) for val in value)), \
@{assert_msg_suffixes.append('and each float in [%f, %f]' % (-bound, bound))}@
@[ elif type_.typename == "double"]@
@{
name = "double"
bound = 1.7976931348623157e+308
}@
all(val >= -@(bound) and val <= @(bound) for val in value)), \
all(not (val < -@(bound) or val > @(bound)) or math.isinf(val) for val in value)), \
@{assert_msg_suffixes.append('and each double in [%f, %f]' % (-bound, bound))}@
@[ end if]@
@[ else]@
Expand Down Expand Up @@ -561,15 +567,15 @@ bound = 2**nbits
@[ if type_.typename == "float"]@
@{
name = "float"
bound = 3.402823e+38
bound = 3.402823466e+38
}@
@[ elif type_.typename == "double"]@
@{
name = "double"
bound = 1.7976931348623157e+308
}@
@[ end if]@
assert value >= -@(bound) and value <= @(bound), \
assert not (value < -@(bound) or value > @(bound)) or math.isinf(value), \
"The '@(member.name)' field must be a @(name) in [@(-bound), @(bound)]"
@[ end if]@
@[ else]@
Expand Down
95 changes: 82 additions & 13 deletions rosidl_generator_py/test/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.

import array
import math
import sys

import numpy
import pytest
Expand Down Expand Up @@ -112,14 +114,38 @@ def test_basic_types():
setattr(msg, 'uint%d_value' % i, -1)
with pytest.raises(AssertionError):
setattr(msg, 'int%d_value' % i, 2**i)
float32_ieee_max_next = numpy.nextafter(3.402823466e+38, math.inf)
with pytest.raises(AssertionError):
setattr(msg, 'float32_value', -3.5e+38)
setattr(msg, 'float32_value', -float32_ieee_max_next)
with pytest.raises(AssertionError):
setattr(msg, 'float32_value', 3.5e+38)
with pytest.raises(AssertionError):
setattr(msg, 'float64_value', 1.8e+308)
with pytest.raises(AssertionError):
setattr(msg, 'float64_value', -1.8e+308)
setattr(msg, 'float32_value', float32_ieee_max_next)

# Only run bounds test on system with non-compliant IEEE 754 float64.
# Otherwise the number is implicitly converted to inf.
if sys.float_info.max > 1.7976931348623157e+308:
float64_ieee_max_next = numpy.nextafter(1.7976931348623157e+308, math.inf)
with pytest.raises(AssertionError):
setattr(msg, 'float64_value', -float64_ieee_max_next)
with pytest.raises(AssertionError):
setattr(msg, 'float64_value', float64_ieee_max_next)

# NaN
setattr(msg, 'float32_value', math.nan)
assert math.isnan(msg.float32_value)
setattr(msg, 'float64_value', math.nan)
assert math.isnan(msg.float64_value)

# -Inf
setattr(msg, 'float32_value', -math.inf)
assert math.isinf(msg.float32_value)
setattr(msg, 'float64_value', -math.inf)
assert math.isinf(msg.float64_value)

# +Inf
setattr(msg, 'float32_value', math.inf)
assert math.isinf(msg.float32_value)
setattr(msg, 'float64_value', math.inf)
assert math.isinf(msg.float64_value)


def test_strings():
Expand Down Expand Up @@ -460,9 +486,37 @@ def test_arrays():
with pytest.raises(AssertionError):
setattr(msg, 'uint64_values', [-1, 1, 2])
with pytest.raises(AssertionError):
setattr(msg, 'float32_values', [-3.5e+38, 0.0, 3.5e+38])
float32_ieee_max_next = numpy.nextafter(3.402823466e+38, math.inf)
setattr(msg, 'float32_values', [-float32_ieee_max_next, 0.0])
with pytest.raises(AssertionError):
setattr(msg, 'float64_values', [-1.8e+308, 0.0, 1.8e+308])
float32_ieee_max_next = numpy.nextafter(3.402823466e+38, math.inf)
setattr(msg, 'float32_values', [0.0, float32_ieee_max_next])

# If target system is IEEE 754 compliant, the next number is rounded to inf.
# Only perform this check on non-compliant systems.
if sys.float_info.max > 1.7976931348623157e+308:
with pytest.raises(AssertionError):
float64_ieee_max_next = numpy.nextafter(1.7976931348623157e+308, math.inf)
setattr(msg, 'float64_values', [-float64_ieee_max_next, 0.0])
with pytest.raises(AssertionError):
float64_ieee_max_next = numpy.nextafter(1.7976931348623157e+308, math.inf)
setattr(msg, 'float64_values', [-float64_ieee_max_next, 0.0])

# NaN
arr_of_float32_with_nan = numpy.array([-1.33, math.nan, 1.33], dtype=numpy.float32)
setattr(msg, 'float32_values', arr_of_float32_with_nan)
assert numpy.array_equal(arr_of_float32_with_nan, msg.float32_values, equal_nan=True)
arr_of_float64_with_nan = numpy.array([-1.66, math.nan, 1.66], dtype=numpy.float64)
setattr(msg, 'float64_values', arr_of_float64_with_nan)
assert numpy.array_equal(arr_of_float64_with_nan, msg.float64_values, equal_nan=True)

# Inf
arr_of_float32_with_inf = numpy.array([-math.inf, 5.5, math.inf], dtype=numpy.float32)
setattr(msg, 'float32_values', arr_of_float32_with_inf)
assert numpy.array_equal(arr_of_float32_with_inf, msg.float32_values)
arr_of_float64_with_inf = numpy.array([-math.inf, 5.5, math.inf], dtype=numpy.float64)
setattr(msg, 'float64_values', arr_of_float64_with_inf)
assert numpy.array_equal(arr_of_float64_with_inf, msg.float64_values)


def test_bounded_sequences():
Expand Down Expand Up @@ -675,9 +729,18 @@ def test_bounded_sequences():
with pytest.raises(AssertionError):
setattr(msg, 'uint64_values', [-1, 1, 2])
with pytest.raises(AssertionError):
setattr(msg, 'float32_values', [-3.5e+38, 0.0, 3.5e+38])
float32_ieee_max_next = numpy.nextafter(3.402823466e+38, math.inf)
setattr(msg, 'float32_values', [-float32_ieee_max_next, 0.0])
with pytest.raises(AssertionError):
setattr(msg, 'float64_values', [-1.8e+308, 0.0, 1.8e+308])
float32_ieee_max_next = numpy.nextafter(3.402823466e+38, math.inf)
setattr(msg, 'float32_values', [0.0, float32_ieee_max_next])

# If target system is IEEE 754 compliant, the next number is rounded to inf.
# Only perform this check on non-compliant systems.
if sys.float_info.max > 1.7976931348623157e+308:
with pytest.raises(AssertionError):
float64_ieee_max_next = numpy.nextafter(1.7976931348623157e+308, math.inf)
setattr(msg, 'float64_values', [-float64_ieee_max_next, 0.0, float64_ieee_max_next])


def test_unbounded_sequences():
Expand Down Expand Up @@ -818,9 +881,15 @@ def test_unbounded_sequences():
with pytest.raises(AssertionError):
setattr(msg, 'uint64_values', [-1, 1, 2])
with pytest.raises(AssertionError):
setattr(msg, 'float32_values', [-3.5e+38, 0.0, 3.5e+38])
with pytest.raises(AssertionError):
setattr(msg, 'float64_values', [-1.8e+308, 0.0, 1.8e+308])
float32_ieee_max_next = numpy.nextafter(3.402823466e+38, math.inf)
setattr(msg, 'float32_values', [-float32_ieee_max_next, 0.0, float32_ieee_max_next])

# If target system is IEEE 754 compliant, the next number is rounded to inf.
# Only perform this check on non-compliant systems.
if sys.float_info.max > 1.7976931348623157e+308:
with pytest.raises(AssertionError):
float64_ieee_max_next = numpy.nextafter(1.7976931348623157e+308, math.inf)
setattr(msg, 'float64_values', [-float64_ieee_max_next, 0.0, float64_ieee_max_next])


def test_slot_attributes():
Expand Down

0 comments on commit db6c5b8

Please sign in to comment.