Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ExtendedNumberTest.cpp
Go to the documentation of this file.
1#include "storm-config.h"
2#include "test/storm_gtest.h"
3
4#include <iostream>
5
12
13namespace {
15
16storm::RationalNumber rational(double value) {
18}
19} // namespace
20
21TEST(ExtendedNumberTest, extendedValueTypeOnlyWrapsWhereNeeded) {
22 // double has its own infinity, so it must not be wrapped: the floating point path pays nothing for this.
23 EXPECT_TRUE((std::is_same_v<storm::utility::ExtendedValueType<double>, double>));
24 // A rational number has none, so it is wrapped, and that is what the ExtendedRationalNumber alias names.
26 // The same holds for a rational function, which the ExtendedRationalFunction alias names.
28}
29
30TEST(ExtendedNumberTest, kinds) {
31 ExtendedRationalNumber const two(rational(2));
32 EXPECT_TRUE(two.isFinite());
33 EXPECT_FALSE(two.isInfinite());
34 EXPECT_EQ(rational(2), two.getFinite());
35
36 EXPECT_TRUE(ExtendedRationalNumber::infinity().isPositiveInfinity());
37 EXPECT_TRUE(ExtendedRationalNumber::infinity().isInfinite());
38 EXPECT_TRUE(ExtendedRationalNumber::negativeInfinity().isNegativeInfinity());
39 EXPECT_FALSE(ExtendedRationalNumber::negativeInfinity().isPositiveInfinity());
40
41 // The default value is a finite zero.
42 EXPECT_TRUE(ExtendedRationalNumber().isFinite());
43 EXPECT_TRUE(storm::utility::isZero(ExtendedRationalNumber().getFinite()));
44}
45
46TEST(ExtendedNumberTest, ordering) {
47 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
48 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
49 ExtendedRationalNumber const two(rational(2));
50
51 EXPECT_LT(negInf, two);
52 EXPECT_LT(two, inf);
53 EXPECT_LT(negInf, inf);
54 EXPECT_LE(inf, inf);
55 EXPECT_GT(inf, two);
56
57 // The previous representation of infinity was the literal 100000000000, so any larger value compared greater than
58 // "infinity". Every finite value must now be below it.
59 EXPECT_LT(ExtendedRationalNumber(rational(1e12)), inf);
60 EXPECT_LT(ExtendedRationalNumber(rational(1e11)), inf);
61}
62
63TEST(ExtendedNumberTest, equality) {
64 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
65 EXPECT_EQ(inf, ExtendedRationalNumber::infinity());
66 EXPECT_NE(inf, ExtendedRationalNumber::negativeInfinity());
67 EXPECT_NE(inf, ExtendedRationalNumber(rational(1e11)));
68 EXPECT_EQ(ExtendedRationalNumber(rational(2)), ExtendedRationalNumber(rational(2)));
69}
70
71TEST(ExtendedNumberTest, arithmeticIsAbsorbing) {
72 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
73 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
74 ExtendedRationalNumber const two(rational(2));
75 ExtendedRationalNumber const zero;
76
77 EXPECT_EQ(inf, inf + two);
78 EXPECT_EQ(inf, two + inf);
79 EXPECT_EQ(inf, inf - two);
80 EXPECT_EQ(inf, inf + inf);
81 EXPECT_EQ(inf, inf * two);
82 EXPECT_EQ(negInf, inf * -two);
83 EXPECT_EQ(inf, inf / two);
84 EXPECT_EQ(negInf, inf / -two);
85 EXPECT_EQ(zero, two / inf);
86 EXPECT_EQ(negInf, -inf);
87 EXPECT_EQ(inf, -negInf);
88
89 EXPECT_EQ(ExtendedRationalNumber(rational(6)), ExtendedRationalNumber(rational(2)) * ExtendedRationalNumber(rational(3)));
90}
91
92TEST(ExtendedNumberTest, undefinedFormsThrow) {
93 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
94 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
95 ExtendedRationalNumber const zero;
96
97 // The exact value types have no NaN, so these throw rather than producing a quiet junk value.
98 STORM_SILENT_EXPECT_THROW(inf - inf, storm::exceptions::InvalidOperationException);
99 STORM_SILENT_EXPECT_THROW(inf + negInf, storm::exceptions::InvalidOperationException);
100 STORM_SILENT_EXPECT_THROW(inf * zero, storm::exceptions::InvalidOperationException);
101 STORM_SILENT_EXPECT_THROW(inf / inf, storm::exceptions::InvalidOperationException);
102 STORM_SILENT_EXPECT_THROW(inf / zero, storm::exceptions::InvalidOperationException);
103}
104
105TEST(ExtendedNumberTest, conversionAcrossValueTypes) {
106 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
107 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
108
109 // Into a type that has its own infinity.
112 EXPECT_EQ(2.0, storm::utility::convertNumber<double>(ExtendedRationalNumber(rational(2))));
113
114 // Into another extended type. This is what the parameter lifting model checker needs, where infinity has to travel
115 // from the constant type to the coefficient type.
117 EXPECT_TRUE(asFunction.isPositiveInfinity());
118 auto const finiteAsFunction = storm::utility::convertNumber<storm::ExtendedRationalFunction>(ExtendedRationalNumber(rational(2)));
119 EXPECT_TRUE(finiteAsFunction.isFinite());
120}
121
122TEST(ExtendedNumberTest, output) {
123 std::stringstream stream;
124 stream << ExtendedRationalNumber::infinity() << " " << ExtendedRationalNumber::negativeInfinity() << " " << ExtendedRationalNumber(rational(2));
125 EXPECT_EQ("inf -inf 2", stream.str());
126}
127
128TEST(ExtendedNumberTest, mixesWithFiniteValues) {
129 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
130 storm::RationalNumber const two = rational(2);
131
132 // The operators are hidden friends, so a finite value converts on either side of them.
133 EXPECT_TRUE(two < inf);
134 EXPECT_TRUE(inf > two);
135 EXPECT_EQ(ExtendedRationalNumber(rational(4)), two + ExtendedRationalNumber(two));
136 EXPECT_EQ(ExtendedRationalNumber(rational(4)), ExtendedRationalNumber(two) + two);
137 EXPECT_EQ(inf, two + inf);
138 EXPECT_EQ(ExtendedRationalNumber(two), two);
139}
140
141TEST(ExtendedNumberTest, reportsItsInfinityToTheGenericUtilities) {
142 // NumberTraits and numeric_limits both have to say that this type does have an infinity, otherwise the generic
143 // storm::utility::infinity and isInfinity would not reach it.
146 EXPECT_TRUE(std::numeric_limits<ExtendedRationalNumber>::has_infinity);
147 EXPECT_EQ(ExtendedRationalNumber::infinity(), std::numeric_limits<ExtendedRationalNumber>::infinity());
148
149 // Extending an already extended type is idempotent.
150 EXPECT_TRUE((std::is_same_v<storm::utility::ExtendedValueType<ExtendedRationalNumber>, ExtendedRationalNumber>));
151 EXPECT_TRUE((std::is_same_v<storm::utility::FiniteValueType<ExtendedRationalNumber>, storm::RationalNumber>));
152 EXPECT_TRUE((std::is_same_v<storm::utility::FiniteValueType<double>, double>));
153}
154
155TEST(ExtendedNumberTest, conversionIntoAnExtendedType) {
157 EXPECT_TRUE(widened.isFinite());
158 EXPECT_EQ(rational(2), widened.getFinite());
159}
160
161TEST(ExtendedNumberTest, infinityOfTheExtendedType) {
162 EXPECT_EQ(ExtendedRationalNumber::infinity(), storm::utility::positiveInfinity<storm::RationalNumber>());
163 EXPECT_EQ(ExtendedRationalNumber::negativeInfinity(), storm::utility::negativeInfinity<storm::RationalNumber>());
164
165 // For a type that has its own infinity nothing is wrapped, so this stays the IEEE infinity.
168}
169
170TEST(ExtendedNumberTest, comparesAgainstAPlainValue) {
171 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
172 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
173 ExtendedRationalNumber const two(rational(2));
174 storm::RationalNumber const plainTwo = rational(2);
175 storm::RationalNumber const plainThree = rational(3);
176
177 // A finite extended value compares as the value it holds, in both argument orders.
178 EXPECT_TRUE(two == plainTwo);
179 EXPECT_TRUE(plainTwo == two);
180 EXPECT_FALSE(two != plainTwo);
181 EXPECT_TRUE(two < plainThree);
182 EXPECT_TRUE(plainTwo < ExtendedRationalNumber(plainThree));
183 EXPECT_TRUE(two <= plainTwo);
184 EXPECT_TRUE(two >= plainTwo);
185 EXPECT_TRUE(ExtendedRationalNumber(plainThree) > plainTwo);
186 EXPECT_TRUE(plainThree > two);
187
188 // Every plain value lies strictly between the two infinities.
189 EXPECT_TRUE(negInf < plainTwo);
190 EXPECT_TRUE(plainTwo < inf);
191 EXPECT_FALSE(inf < plainTwo);
192 EXPECT_FALSE(plainTwo < negInf);
193 EXPECT_TRUE(inf > plainTwo);
194 EXPECT_TRUE(plainTwo > negInf);
195 EXPECT_TRUE(negInf <= plainTwo);
196 EXPECT_TRUE(inf >= plainTwo);
197
198 // An infinity is equal to no plain value at all.
199 EXPECT_FALSE(inf == plainTwo);
200 EXPECT_FALSE(negInf == plainTwo);
201 EXPECT_TRUE(inf != plainTwo);
202 EXPECT_TRUE(plainTwo != negInf);
203
204 // The mixed comparisons must agree with the ones that go through the conversion, which is what they replace.
205 for (auto const& plain : {rational(-1), rational(0), rational(2), rational(1000)}) {
206 for (auto const& extended : {inf, negInf, two}) {
207 EXPECT_EQ(extended < ExtendedRationalNumber(plain), extended < plain);
208 EXPECT_EQ(ExtendedRationalNumber(plain) < extended, plain < extended);
209 EXPECT_EQ(extended == ExtendedRationalNumber(plain), extended == plain);
210 EXPECT_EQ(extended <= ExtendedRationalNumber(plain), extended <= plain);
211 EXPECT_EQ(extended > ExtendedRationalNumber(plain), extended > plain);
212 EXPECT_EQ(extended >= ExtendedRationalNumber(plain), extended >= plain);
213 }
214 }
215}
216
217TEST(ExtendedNumberTest, negativeInfinityArithmetic) {
218 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
219 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
220 ExtendedRationalNumber const zero;
221 ExtendedRationalNumber const two(rational(2));
222
223 EXPECT_EQ(negInf, negInf + negInf);
224 EXPECT_EQ(negInf, negInf + two);
225 EXPECT_EQ(negInf, negInf - two);
226 EXPECT_EQ(negInf, negInf - inf);
227 EXPECT_EQ(inf, negInf * negInf);
228 EXPECT_EQ(negInf, negInf * two);
229 EXPECT_EQ(negInf, negInf * inf);
230 EXPECT_EQ(inf, -negInf);
231 EXPECT_EQ(negInf, -inf);
232 EXPECT_EQ(zero, two / negInf);
233
234 // The undefined forms are undefined in this direction too.
235 STORM_SILENT_EXPECT_THROW(negInf - negInf, storm::exceptions::InvalidOperationException);
236 STORM_SILENT_EXPECT_THROW(negInf + inf, storm::exceptions::InvalidOperationException);
237 STORM_SILENT_EXPECT_THROW(negInf * zero, storm::exceptions::InvalidOperationException);
238 STORM_SILENT_EXPECT_THROW(negInf / negInf, storm::exceptions::InvalidOperationException);
239 STORM_SILENT_EXPECT_THROW(negInf / inf, storm::exceptions::InvalidOperationException);
240 STORM_SILENT_EXPECT_THROW(negInf / zero, storm::exceptions::InvalidOperationException);
241}
242
243TEST(ExtendedNumberTest, reportsNegativeInfinity) {
244 EXPECT_TRUE(storm::utility::isNegativeInfinity(ExtendedRationalNumber::negativeInfinity()));
245 EXPECT_FALSE(storm::utility::isNegativeInfinity(ExtendedRationalNumber::infinity()));
246 EXPECT_FALSE(storm::utility::isNegativeInfinity(ExtendedRationalNumber(rational(-2))));
247
248 // The same question is answerable for a type that brings its own infinity.
251 EXPECT_FALSE(storm::utility::isNegativeInfinity(-2.0));
252 // A value type without an infinity of its own has no negative infinity to report.
253 EXPECT_FALSE(storm::utility::isNegativeInfinity(rational(-2)));
254}
255
256TEST(ExtendedNumberTest, isFiniteRejectsNotANumber) {
257 EXPECT_TRUE(storm::utility::isFinite(2.0));
260 // A NaN is not an infinity, but it is not a finite value either. The guards built on this predicate exist to keep
261 // a value that is not a number out of a computation.
262 EXPECT_FALSE(storm::utility::isFinite(std::nan("")));
263
264 EXPECT_TRUE(storm::utility::isFinite(ExtendedRationalNumber(rational(2))));
265 EXPECT_FALSE(storm::utility::isFinite(ExtendedRationalNumber::infinity()));
266 EXPECT_FALSE(storm::utility::isFinite(ExtendedRationalNumber::negativeInfinity()));
267}
268
269TEST(ExtendedNumberTest, widenDoesNotInterpretTheSentinel) {
270 // widen is for values that cannot be infinite. It must take the sentinel at face value: reading it as an infinity
271 // would be guesswork, and the sentinel is a perfectly ordinary number that a computation may legitimately produce.
272 storm::RationalNumber const sentinel = storm::utility::infinity<storm::RationalNumber>();
273 ExtendedRationalNumber const widened(sentinel);
274 EXPECT_TRUE(widened.isFinite());
275 EXPECT_EQ(sentinel, widened.getFinite());
276
277 // fromSentinel is the one that translates it, and it is the only one that may be used on a vector that came out of
278 // a part of Storm that still produces the sentinel.
279 EXPECT_TRUE(storm::utility::fromSentinel(sentinel).isPositiveInfinity());
280
281 // For a type that has its own infinity the sentinel is that infinity, so fromSentinel is the identity there.
283 EXPECT_TRUE(storm::utility::isInfinity(storm::utility::widen(std::vector<double>{storm::utility::infinity<double>()}).front()));
284}
285
286TEST(ExtendedNumberTest, widenAndNarrowVectors) {
287 std::vector<storm::RationalNumber> const finite{rational(1), rational(2)};
288 auto const widened = storm::utility::widen(std::vector<storm::RationalNumber>(finite));
289 ASSERT_EQ(2ull, widened.size());
290 EXPECT_EQ(ExtendedRationalNumber(rational(1)), widened[0]);
291 EXPECT_EQ(ExtendedRationalNumber(rational(2)), widened[1]);
292 EXPECT_EQ(finite, storm::utility::narrowFinite<storm::RationalNumber>(std::vector<ExtendedRationalNumber>(widened)));
293
294 std::vector<storm::RationalNumber> const withSentinel{rational(1), storm::utility::infinity<storm::RationalNumber>()};
295 auto const translated = storm::utility::fromSentinel(std::vector<storm::RationalNumber>(withSentinel));
296 ASSERT_EQ(2ull, translated.size());
297 EXPECT_TRUE(translated[0].isFinite());
298 EXPECT_TRUE(translated[1].isPositiveInfinity());
299
300 // narrowFinite has no number to put in place of an infinity, so it refuses rather than inventing one. This has to
301 // hold in a release build too, which is why it throws rather than asserting.
302 STORM_SILENT_EXPECT_THROW(storm::utility::narrowFinite<storm::RationalNumber>(std::vector<ExtendedRationalNumber>(translated)),
303 storm::exceptions::InvalidOperationException);
304}
305
306TEST(ExtendedNumberTest, narrowsToADefaultValue) {
307 std::vector<ExtendedRationalNumber> const values{ExtendedRationalNumber(rational(1)), ExtendedRationalNumber::infinity(),
308 ExtendedRationalNumber::negativeInfinity()};
309 std::vector<storm::RationalNumber> const narrowed =
310 storm::utility::narrowFinite<storm::RationalNumber>(std::vector<ExtendedRationalNumber>(values), rational(7));
311 ASSERT_EQ(3ull, narrowed.size());
312 EXPECT_EQ(rational(1), narrowed[0]);
313 EXPECT_EQ(rational(7), narrowed[1]);
314 EXPECT_EQ(rational(7), narrowed[2]);
315
316 // A value type that brings its own infinity keeps the default in the same places.
317 std::vector<double> const doubles = storm::utility::narrowFinite<double>(std::vector<double>{1.0, storm::utility::infinity<double>()}, 7.0);
318 ASSERT_EQ(2ull, doubles.size());
319 EXPECT_EQ(1.0, doubles[0]);
320 EXPECT_EQ(7.0, doubles[1]);
321}
322
323TEST(ExtendedNumberTest, narrowingRefusesAnInfiniteValue) {
324 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
325 ExtendedRationalNumber const negInf = ExtendedRationalNumber::negativeInfinity();
326
327 EXPECT_EQ(rational(2), storm::utility::narrow<storm::RationalNumber>(ExtendedRationalNumber(rational(2))));
328 STORM_SILENT_EXPECT_THROW(storm::utility::narrow<storm::RationalNumber>(inf), storm::exceptions::NotSupportedException);
329 STORM_SILENT_EXPECT_THROW(storm::utility::narrow<storm::RationalNumber>(negInf), storm::exceptions::NotSupportedException);
330
331 EXPECT_EQ(rational(2), storm::utility::getFinite(ExtendedRationalNumber(rational(2))));
332
333 // A type that has its own infinity narrows to itself, infinity included.
335}
336
337TEST(ExtendedNumberDeathTest, getFiniteRefusesAnInfiniteValue) {
338 // Being finite is a precondition of getFinite, so a violation is caught by an assertion rather than an exception.
339 // It must not quietly hand out the zero that an infinite value happens to carry as its payload.
340 ExtendedRationalNumber const inf = ExtendedRationalNumber::infinity();
341
342#ifndef NDEBUG
343 EXPECT_DEATH_IF_SUPPORTED(storm::utility::getFinite(inf), "");
344 EXPECT_DEATH_IF_SUPPORTED(inf.getFinite(), "");
345#else
346 std::cerr << "WARNING: Not testing the getFinite assertion, as it is disabled in release mode.\n";
347 SUCCEED();
348#endif
349}
350
351TEST(ExtendedNumberTest, toSentinelHasNoNegativeInfinity) {
352 EXPECT_EQ(rational(2), storm::utility::toSentinel<storm::RationalNumber>(ExtendedRationalNumber(rational(2))));
354
355 // The sentinel is a single magic number with no negative counterpart, so this direction is rejected rather than
356 // silently turned into something else.
357 STORM_SILENT_EXPECT_THROW(storm::utility::toSentinel<storm::RationalNumber>(ExtendedRationalNumber::negativeInfinity()),
358 storm::exceptions::NotSupportedException);
359
360 // Round tripping through the sentinel is lossless for the one infinity it can represent.
361 EXPECT_TRUE(storm::utility::fromSentinel(storm::utility::toSentinel<storm::RationalNumber>(ExtendedRationalNumber::infinity())).isPositiveInfinity());
362}
363
364TEST(ExtendedNumberTest, convertsAnInfinityFromATypeThatHasItsOwn) {
365 // This is the path that carries an infinity out of a double computation into an exact coefficient type.
367 EXPECT_TRUE(fromDouble.isPositiveInfinity());
369 EXPECT_TRUE(negFromDouble.isNegativeInfinity());
370 EXPECT_EQ(rational(0.5), storm::utility::convertNumber<ExtendedRationalNumber>(0.5).getFinite());
371}
TEST(ExtendedNumberTest, extendedValueTypeOnlyWrapsWhereNeeded)
A value of ValueType extended with the two infinities -infinity and +infinity.
ExtendedValueType< ValueType > positiveInfinity()
bool isFinite(ExtendedNumber< ValueType > const &value)
ExtendedValueType< ValueType > fromSentinel(ValueType &&value)
Recognises the value that storm::utility::infinity still yields for the value types that have no infi...
std::vector< ExtendedValueType< ValueType > > widen(std::vector< ValueType > &&values)
Widens a vector of finite values into the extended value type.
ExtendedValueType< ValueType > negativeInfinity()
bool isNegativeInfinity(ExtendedNumber< ValueType > const &number)
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType infinity()
Definition constants.cpp:31
std::conditional_t< storm::NumberTraits< ValueType >::HasInfinity, ValueType, ExtendedNumber< ValueType > > ExtendedValueType
The type to use for values that may be infinite.
ValueType narrow(ExtendedValueType< ValueType > const &value)
Narrows an extended value back into the plain value type, for the interfaces that cannot hold an infi...
std::vector< ValueType > narrowFinite(std::vector< ExtendedValueType< ValueType > > &&values)
The counterpart of widen for a vector all of whose values are finite.
ValueType toSentinel(ExtendedValueType< ValueType > const &value)
The counterpart of fromSentinel: hands an extended value back to a part of Storm that still expects t...
typename detail::FiniteValueType< ValueType >::type FiniteValueType
The finite value type underlying a (possibly extended) type.
bool isInfinity(ValueType const &a)
ValueType const & getFinite(ExtendedNumber< ValueType > const &value)
TargetType convertNumber(SourceType const &number)
storm::utility::ExtendedValueType< RationalNumber > ExtendedRationalNumber
The rational number type extended with the two infinities.
storm::utility::ExtendedValueType< RationalFunction > ExtendedRationalFunction
The rational function type extended with the two infinities.
#define STORM_SILENT_EXPECT_THROW(statement, expected_exception)
Definition storm_gtest.h:21
static const bool HasInfinity
Whether the type can represent +/- infinity itself.
static const bool IsExact