Skip to content

Commit 4177517

Browse files
Merge pull request #539 from arduino-libraries/handle-nan
Explicitly handle NAN values
2 parents da9c776 + 9c2aaef commit 4177517

File tree

6 files changed

+265
-2
lines changed

6 files changed

+265
-2
lines changed

Diff for: extras/test/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ set(TEST_SRCS
6666
src/test_addPropertyReal.cpp
6767
src/test_callback.cpp
6868
src/test_CloudColor.cpp
69+
src/test_CloudFloat.cpp
70+
src/test_CloudWrapperFloat.cpp
6971
src/test_CloudLocation.cpp
7072
src/test_CloudSchedule.cpp
7173
src/test_decode.cpp

Diff for: extras/test/src/test_CloudFloat.cpp

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright (c) 2019 Arduino. All rights reserved.
3+
*/
4+
5+
/**************************************************************************************
6+
INCLUDE
7+
**************************************************************************************/
8+
9+
#include <catch2/catch_test_macros.hpp>
10+
11+
#include <CBORDecoder.h>
12+
13+
#include <property/types/CloudFloat.h>
14+
15+
/**************************************************************************************
16+
TEST CODE
17+
**************************************************************************************/
18+
19+
SCENARIO("Arduino Cloud Properties ", "[ArduinoCloudThing::CloudFloat]")
20+
{
21+
WHEN("NAN value from cloud is received")
22+
{
23+
PropertyContainer property_container;
24+
CloudFloat prop = 2.0f;
25+
prop.writeOnDemand();
26+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
27+
28+
/* [{0: "test", 2: NaN}] = 81A200647465737402F97E00 */
29+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
30+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
31+
CBORDecoder::decode(property_container, payload, payload_length);
32+
33+
REQUIRE(prop.isDifferentFromCloud() == true);
34+
}
35+
36+
WHEN("Local value is NAN")
37+
{
38+
PropertyContainer property_container;
39+
CloudFloat prop = NAN;
40+
prop.writeOnDemand();
41+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
42+
43+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F93E00 */
44+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x3E, 0x00 };
45+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
46+
CBORDecoder::decode(property_container, payload, payload_length);
47+
48+
REQUIRE(prop.isDifferentFromCloud() == true);
49+
}
50+
51+
WHEN("both, the local and the cloud values are NaN")
52+
{
53+
PropertyContainer property_container;
54+
CloudFloat prop = NAN;
55+
prop.writeOnDemand();
56+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
57+
58+
/* [{0: "test", 2: NaN}] = 81A200647465737402F97E00 */
59+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
60+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
61+
CBORDecoder::decode(property_container, payload, payload_length);
62+
63+
REQUIRE(prop.isDifferentFromCloud() == false);
64+
}
65+
66+
WHEN("the local and cloud values are the same")
67+
{
68+
PropertyContainer property_container;
69+
CloudFloat prop = 1.5;
70+
prop.writeOnDemand();
71+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
72+
73+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F93E00 */
74+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x3E, 0x00 };
75+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
76+
CBORDecoder::decode(property_container, payload, payload_length);
77+
78+
REQUIRE(prop.isDifferentFromCloud() == false);
79+
}
80+
81+
WHEN("the local and the cloud values differ")
82+
{
83+
PropertyContainer property_container;
84+
CloudFloat prop = 1.0f;
85+
prop.writeOnDemand();
86+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
87+
88+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F97E00 */
89+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
90+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
91+
CBORDecoder::decode(property_container, payload, payload_length);
92+
93+
REQUIRE(prop.isDifferentFromCloud() == true);
94+
}
95+
96+
WHEN("the local value differs by 0.25 from the cloud value")
97+
{
98+
PropertyContainer property_container;
99+
CloudFloat prop = 1.0f;
100+
prop.writeOnDemand();
101+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
102+
103+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F97E00 */
104+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
105+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
106+
CBORDecoder::decode(property_container, payload, payload_length);
107+
prop.publishOnChange(0.25f, 0); // 0.25 min delta
108+
109+
REQUIRE(prop.isDifferentFromCloud() == true);
110+
}
111+
112+
}

Diff for: extras/test/src/test_CloudWrapperFloat.cpp

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
Copyright (c) 2019 Arduino. All rights reserved.
3+
*/
4+
5+
/**************************************************************************************
6+
INCLUDE
7+
**************************************************************************************/
8+
9+
#include <catch2/catch_test_macros.hpp>
10+
11+
#include <CBORDecoder.h>
12+
13+
#include <property/types/CloudWrapperFloat.h>
14+
15+
/**************************************************************************************
16+
TEST CODE
17+
**************************************************************************************/
18+
19+
SCENARIO("Arduino Cloud Properties ", "[ArduinoCloudThing::CloudWrapperFloat]")
20+
{
21+
WHEN("NAN value from cloud is received")
22+
{
23+
PropertyContainer property_container;
24+
float value = 2.0f;
25+
CloudWrapperFloat prop(value);
26+
prop.writeOnDemand();
27+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
28+
29+
/* [{0: "test", 2: NaN}] = 81A200647465737402F97E00 */
30+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
31+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
32+
CBORDecoder::decode(property_container, payload, payload_length);
33+
34+
REQUIRE(prop.isDifferentFromCloud() == true);
35+
}
36+
37+
WHEN("Local value is NAN")
38+
{
39+
PropertyContainer property_container;
40+
float value = NAN;
41+
CloudWrapperFloat prop(value);
42+
prop.writeOnDemand();
43+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
44+
45+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F93E00 */
46+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x3E, 0x00 };
47+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
48+
CBORDecoder::decode(property_container, payload, payload_length);
49+
50+
REQUIRE(prop.isDifferentFromCloud() == true);
51+
}
52+
53+
WHEN("both, the local and the cloud values are NaN")
54+
{
55+
PropertyContainer property_container;
56+
float value = NAN;
57+
CloudWrapperFloat prop(value);
58+
prop.writeOnDemand();
59+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
60+
61+
/* [{0: "test", 2: NaN}] = 81A200647465737402F97E00 */
62+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
63+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
64+
CBORDecoder::decode(property_container, payload, payload_length);
65+
66+
REQUIRE(prop.isDifferentFromCloud() == false);
67+
}
68+
69+
WHEN("the local and cloud values are the same")
70+
{
71+
PropertyContainer property_container;
72+
float value = 1.5f;
73+
CloudWrapperFloat prop(value);
74+
prop.writeOnDemand();
75+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
76+
77+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F93E00 */
78+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x3E, 0x00 };
79+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
80+
CBORDecoder::decode(property_container, payload, payload_length);
81+
82+
REQUIRE(prop.isDifferentFromCloud() == false);
83+
}
84+
85+
WHEN("the local and the cloud values differ")
86+
{
87+
PropertyContainer property_container;
88+
float value = 1.0f;
89+
CloudWrapperFloat prop(value);
90+
prop.writeOnDemand();
91+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
92+
93+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F97E00 */
94+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
95+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
96+
CBORDecoder::decode(property_container, payload, payload_length);
97+
98+
REQUIRE(prop.isDifferentFromCloud() == true);
99+
}
100+
101+
WHEN("the local value differs by 0.25 from the cloud value")
102+
{
103+
PropertyContainer property_container;
104+
float value = 1.0f;
105+
CloudWrapperFloat prop(value);
106+
prop.writeOnDemand();
107+
addPropertyToContainer(property_container, prop, "test", Permission::ReadWrite);
108+
109+
/* [{0: "test", 2: 1.5}] = 81A200647465737402F97E00 */
110+
uint8_t const payload[] = { 0x81, 0xA2, 0x00, 0x64, 0x74, 0x65, 0x73, 0x74, 0x02, 0xF9, 0x7E, 0x00 };
111+
int const payload_length = sizeof(payload) / sizeof(uint8_t);
112+
CBORDecoder::decode(property_container, payload, payload_length);
113+
prop.publishOnChange(0.25f, 0); // 0.25 min delta
114+
115+
REQUIRE(prop.isDifferentFromCloud() == true);
116+
}
117+
118+
}

Diff for: src/property/math_utils.h

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
This file is part of the ArduinoIoTCloud library.
3+
4+
Copyright (c) 2024 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
#pragma once
11+
#include <math.h>
12+
13+
namespace arduino { namespace math {
14+
15+
template<typename T>
16+
inline bool ieee754_different(const T a, const T b, const T delta = 0.0) {
17+
/* The following comparison is introduced to take into account the very peculiar
18+
* way of handling NaN values by the standard IEEE754.
19+
* We consider two floating point number different if their binary representation is different.
20+
* or if those two IEEE754 values are numbers or zero(which is handled on its own) exceed
21+
* a certain threshold
22+
*/
23+
return memcmp((uint8_t*)&a, (uint8_t*)&b, sizeof(b)) != 0 && (
24+
(std::fpclassify(a) != FP_NORMAL && std::fpclassify(a) != FP_ZERO) ||
25+
(std::fpclassify(b) != FP_NORMAL && std::fpclassify(b) != FP_ZERO) ||
26+
fabs(a - b) >= delta
27+
);
28+
}
29+
}}

Diff for: src/property/types/CloudFloat.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define CLOUDFLOAT_H_
2020

2121
#include <math.h>
22+
#include "../math_utils.h"
2223

2324
/******************************************************************************
2425
INCLUDE
@@ -46,7 +47,7 @@ class CloudFloat : public Property {
4647
return _value;
4748
}
4849
virtual bool isDifferentFromCloud() {
49-
return _value != _cloud_value && (abs(_value - _cloud_value) >= Property::_min_delta_property);
50+
return arduino::math::ieee754_different(_value, _cloud_value, Property::_min_delta_property);
5051
}
5152
virtual void fromCloudToLocal() {
5253
_value = _cloud_value;

Diff for: src/property/types/CloudWrapperFloat.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define CLOUDWRAPPERFLOAT_H_
2020

2121
#include <math.h>
22+
#include "../math_utils.h"
2223

2324
/******************************************************************************
2425
INCLUDE
@@ -39,7 +40,7 @@ class CloudWrapperFloat : public CloudWrapperBase {
3940
public:
4041
CloudWrapperFloat(float& v) : _primitive_value(v), _cloud_value(v), _local_value(v) {}
4142
virtual bool isDifferentFromCloud() {
42-
return _primitive_value != _cloud_value && (abs(_primitive_value - _cloud_value) >= Property::_min_delta_property);
43+
return arduino::math::ieee754_different(_primitive_value, _cloud_value, Property::_min_delta_property);
4344
}
4445
virtual void fromCloudToLocal() {
4546
_primitive_value = _cloud_value;

0 commit comments

Comments
 (0)