DCCL v5
Loading...
Searching...
No Matches
test.cpp
1// Copyright 2026:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6//
7//
8// This file is part of the Dynamic Compact Control Language Library
9// ("DCCL").
10//
11// DCCL is free software: you can redistribute it and/or modify
12// it under the terms of the GNU Lesser General Public License as published by
13// the Free Software Foundation, either version 2.1 of the License, or
14// (at your option) any later version.
15//
16// DCCL is distributed in the hope that it will be useful,
17// but WITHOUT ANY WARRANTY; without even the implied warranty of
18// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19// GNU Lesser General Public License for more details.
20//
21// You should have received a copy of the GNU Lesser General Public License
22// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
23
24// Tests the IEEE-754 decompose/compose helpers and the fixed-width bitset
25// arithmetic in numeric.h that the arithmetic and v4+ float codecs build on.
26
27#include <cassert>
28#include <cmath>
29#include <cstdint>
30#include <iostream>
31#include <limits>
32
33#include "dccl/logger.h"
34
35#include "../../numeric.h"
36
37namespace
38{
39template <typename Float, typename Int> void check_special_values()
40{
41 using FloatLimits = std::numeric_limits<Float>;
42 using IntLimits = std::numeric_limits<Int>;
43
44 int16_t exponent = 12345; // sentinel: untouched by the non-finite branches
45
46 assert(dccl::decompose_float_format(FloatLimits::infinity(), exponent) == IntLimits::max());
47 assert(dccl::decompose_float_format(-FloatLimits::infinity(), exponent) == IntLimits::lowest());
48 assert(dccl::decompose_float_format(FloatLimits::quiet_NaN(), exponent) ==
49 IntLimits::lowest() + 1);
50
51 // zero is the one special case that does define the exponent
52 assert(dccl::decompose_float_format(static_cast<Float>(0.0), exponent) == 0);
53 assert(exponent == 0);
54
55 exponent = 12345;
56 assert(dccl::decompose_float_format(static_cast<Float>(-0.0), exponent) == 0);
57 assert(exponent == 0);
58
59 assert(dccl::compose_float_format(IntLimits::max(), 0) == FloatLimits::infinity());
60 assert(dccl::compose_float_format(IntLimits::lowest(), 0) == -FloatLimits::infinity());
61 assert(std::isnan(dccl::compose_float_format(static_cast<Int>(IntLimits::lowest() + 1), 0)));
62 assert(dccl::compose_float_format(static_cast<Int>(0), 0) == static_cast<Float>(0.0));
63}
64
65// A decompose/compose pair must reproduce the input bit-for-bit.
66template <typename Float> void check_round_trip(Float val)
67{
68 int16_t exponent = 0;
69 auto significand = dccl::decompose_float_format(val, exponent);
70 Float out = dccl::compose_float_format(significand, exponent);
71
72 dccl::dlog.is(dccl::logger::INFO) && dccl::dlog << "round trip " << val << " -> " << significand
73 << " * 2^" << exponent << " -> " << out
74 << std::endl;
75
76 assert(out == val);
77}
78
79template <typename Float> void check_finite_values()
80{
81 using FloatLimits = std::numeric_limits<Float>;
82
83 check_round_trip<Float>(1.0);
84 check_round_trip<Float>(-1.0);
85 check_round_trip<Float>(0.5);
86 check_round_trip<Float>(-0.5);
87 check_round_trip<Float>(2.0);
88 check_round_trip<Float>(1234.5);
89 check_round_trip<Float>(-1234.5);
90
91 check_round_trip(FloatLimits::max());
92 check_round_trip(FloatLimits::lowest());
93 check_round_trip(FloatLimits::min()); // smallest normal
94 check_round_trip(-FloatLimits::min());
95 check_round_trip(FloatLimits::denorm_min()); // smallest subnormal
96 check_round_trip(-FloatLimits::denorm_min());
97 check_round_trip(FloatLimits::epsilon());
98
99 // subnormals share one exponent, so the significand alone distinguishes them
100 int16_t denorm_exp = 0;
101 int16_t denorm_exp2 = 0;
102 auto denorm = dccl::decompose_float_format(FloatLimits::denorm_min(), denorm_exp);
103 auto neg_denorm = dccl::decompose_float_format(-FloatLimits::denorm_min(), denorm_exp2);
104 assert(denorm == 1);
105 assert(neg_denorm == -1);
106 assert(denorm_exp == denorm_exp2);
107
108 // 1.0 is 2^0, so the significand is the implicit bit alone
109 int16_t one_exp = 0;
110 auto one = dccl::decompose_float_format(static_cast<Float>(1.0), one_exp);
111 assert(one > 0);
112 assert(std::ldexp(static_cast<Float>(one), one_exp) == static_cast<Float>(1.0));
113
114 // sign is carried by the significand, not the exponent
115 int16_t pos_exp = 0;
116 int16_t neg_exp = 0;
117 auto pos = dccl::decompose_float_format(static_cast<Float>(3.25), pos_exp);
118 auto neg = dccl::decompose_float_format(static_cast<Float>(-3.25), neg_exp);
119 assert(pos == -neg);
120 assert(pos_exp == neg_exp);
121}
122
123template <std::size_t N> std::bitset<N> bits_of(unsigned long long value)
124{
125 return std::bitset<N>(value);
126}
127
128void check_bitset_arithmetic()
129{
130 constexpr std::size_t N = 16;
131
132 auto one = bits_of<N>(1);
133 dccl::increment(one);
134 assert(one.to_ulong() == 2);
135
136 // increment must carry across the whole width and wrap to zero
137 auto all_ones = bits_of<N>(0xFFFF);
138 dccl::increment(all_ones);
139 assert(all_ones.to_ulong() == 0);
140
141 assert(!dccl::is_negative(bits_of<N>(0x7FFF)));
142 assert(dccl::is_negative(bits_of<N>(0x8000)));
143
144 auto five = bits_of<N>(5);
145 dccl::negate(five);
146 assert(five.to_ulong() == 0xFFFB); // two's complement -5
147 assert(dccl::negated(bits_of<N>(0)).to_ulong() == 0);
148
149 auto acc = bits_of<N>(7);
150 dccl::add_to(acc, bits_of<N>(9));
151 assert(acc.to_ulong() == 16);
152
153 assert(dccl::sum(bits_of<N>(1000), bits_of<N>(2345)).to_ulong() == 3345);
154 assert(dccl::sum(bits_of<N>(0xFFFF), bits_of<N>(1)).to_ulong() == 0); // wraps
155 assert(dccl::difference(bits_of<N>(100), bits_of<N>(58)).to_ulong() == 42);
156
157 assert(dccl::unsigned_geq(bits_of<N>(5), bits_of<N>(5)));
158 assert(dccl::unsigned_geq(bits_of<N>(6), bits_of<N>(5)));
159 assert(!dccl::unsigned_geq(bits_of<N>(4), bits_of<N>(5)));
160
161 assert(dccl::unsigned_quotient(bits_of<N>(100), bits_of<N>(10)).to_ulong() == 10);
162 assert(dccl::unsigned_quotient(bits_of<N>(0), bits_of<N>(7)).to_ulong() == 0);
163 // quotient rounds rather than truncating: 7/2 == 3.5 -> 4
164 assert(dccl::unsigned_quotient(bits_of<N>(7), bits_of<N>(2)).to_ulong() == 4);
165 assert(dccl::unsigned_quotient(bits_of<N>(5), bits_of<N>(2)).to_ulong() == 3);
166
167 assert(dccl::unsigned_product(bits_of<N>(12), bits_of<N>(12)).to_ulong() == 144);
168 assert(dccl::unsigned_product(bits_of<N>(0), bits_of<N>(9999)).to_ulong() == 0);
169 // the double-width result is what keeps a full-range product from overflowing
170 assert(dccl::unsigned_product(bits_of<N>(0xFFFF), bits_of<N>(0xFFFF)).to_ullong() ==
171 0xFFFE0001ULL);
172
173 auto shift = bits_of<N>(8);
174 dccl::rounding_shift_right(shift, 2);
175 assert(shift.to_ulong() == 2);
176
177 // shifting out a set most-significant bit rounds up
178 auto shift_round = bits_of<N>(6);
179 dccl::rounding_shift_right(shift_round, 2);
180 assert(shift_round.to_ulong() == 2);
181
182 auto shift_round_up = bits_of<N>(7);
183 dccl::rounding_shift_right(shift_round_up, 2);
184 assert(shift_round_up.to_ulong() == 2);
185
186 auto shift_round_half = bits_of<N>(3);
187 dccl::rounding_shift_right(shift_round_half, 1);
188 assert(shift_round_half.to_ulong() == 2);
189}
190} // namespace
191
192int main(int argc, char* argv[])
193{
194 bool verbose = false;
195 for (int i = 1; i < argc; ++i)
196 {
197 if (argv[i] && argv[i][0] == '-' && argv[i][1] == 'v' && argv[i][2] == '\0')
198 verbose = true;
199 }
200
201 dccl::dlog.connect(verbose ? dccl::logger::ALL : dccl::logger::WARN_PLUS, &std::cerr);
202
203 check_special_values<float, int32_t>();
204 check_special_values<double, int64_t>();
205
206 check_finite_values<float>();
207 check_finite_values<double>();
208
209 check_bitset_arithmetic();
210
211 std::cout << "all tests passed" << std::endl;
212 return 0;
213}
bool is(logger::Verbosity verbosity, logger::Group group=logger::GENERAL)
Indicates the verbosity of the Logger until the next std::flush or std::endl. The boolean return is u...
Definition logger.h:191
void connect(int verbosity_mask, Slot slot)
Connect the output of one or more given verbosities to a slot (function pointer or similar)
Definition logger.h:213
Dynamic Compact Control Language namespace.
Definition any.h:28
int32_t decompose_float_format(float val, int16_t &exponent)
Definition numeric.cpp:43
float compose_float_format(int32_t significand, int16_t exponent)
Definition numeric.cpp:158