• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 The Gemmlowp Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // test_fixedpoint.cc: unit tests covering the fixedpoint/ directory.
16 
17 #define GEMMLOWP_ENABLE_FIXEDPOINT_CONSTANTS_CHECKS
18 
19 #include <algorithm>
20 #include <cmath>
21 #include <random>
22 #include <vector>
23 #include "test.h"
24 
25 #include "../fixedpoint/fixedpoint.h"
26 
27 namespace gemmlowp {
28 
29 namespace {
30 
31 // Explanation of SimdVector type and associated functions
32 // (LoadSimdVector, StoreSimdVector):
33 // The fixedpoint stuff being tested here is generic in an underlying
34 // integer type which may be either scalar (int32_t) or SIMD (e.g.
35 // NEON int32x4_t). We want to write uniform tests that can test
36 // both the scalar and SIMD paths. We achieve this by having this
37 // generic SimdVector abstraction, local to this test.
38 
39 #ifdef GEMMLOWP_NEON
40 using SimdVector = int32x4_t;
41 constexpr std::size_t SimdVectorSize = 4;
LoadSimdVector(const std::int32_t * src)42 SimdVector LoadSimdVector(const std::int32_t* src) { return vld1q_s32(src); }
StoreSimdVector(std::int32_t * dst,SimdVector v)43 void StoreSimdVector(std::int32_t* dst, SimdVector v) { vst1q_s32(dst, v); }
44 #elif defined(GEMMLOWP_SSE4)
45 using SimdVector = __m128i;
46 constexpr std::size_t SimdVectorSize = 4;
47 SimdVector LoadSimdVector(const std::int32_t* src) {
48   return _mm_loadu_si128(reinterpret_cast<const __m128i*>(src));
49 }
50 void StoreSimdVector(std::int32_t* dst, SimdVector v) {
51   _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), v);
52 }
53 #else
54 using SimdVector = std::int32_t;
55 constexpr std::size_t SimdVectorSize = 1;
56 SimdVector LoadSimdVector(const std::int32_t* src) { return *src; }
57 void StoreSimdVector(std::int32_t* dst, SimdVector v) { *dst = v; }
58 #endif
59 
60 // Explanation of UnaryOpBase, its *Op subclasses below, and TestUnaryOp:
61 // Most (though not all) of the fixedpoint functionality being tested
62 // consists of functions taking one fixedpoint value and returning one
63 // fixedpoint value, e.g. "exp" or "tanh". We call them "unary operators".
64 // We factor a lot of testing boilerplate into a common TestUnaryOp function
65 // taking a "unary op" object that fully describes the function to be tested.
66 // These objects inherit UnaryOpBase mostly as a means to share some default
67 // values for some properties.
68 //
69 // An important design element here is that the fixed-point values are passed
70 // around as raw integers (e.g. int32_t or SIMD types such as int32x4_t), not
71 // as higher-level FixedPoint objects. The motivation for this design is 1) to
72 // avoid having to templatize everything in the tIntegerBits parameter of
73 // class FixedPoint, and 2) to allow directly testing low-level functions
74 // operating on raw types (e.g. RoundingDivideByPOT) without needlessly
75 // requiring
76 // wrapping raw values in FixedPoint objects.
77 class UnaryOpBase {
78  public:
79   // Min bound of the input range of this op. For example, an op only handling
80   // nonnegative values would return 0.
MinInput() const81   std::int32_t MinInput() const {
82     return std::numeric_limits<std::int32_t>::min();
83   }
84   // Max bound of the input range of this op. For example, an op only handling
85   // nonpositive values would return 0.
MaxInput() const86   std::int32_t MaxInput() const {
87     return std::numeric_limits<std::int32_t>::max();
88   }
89   // Tolerated difference between actual and reference int32 values.
90   // Note that the corresponding real-numbers tolerance depends on the number
91   // of integer bits of the fixed-point representation of the results of this
92   // op.
93   // For example, for an op returning fixed-point values with 0 integer bits,
94   // the correspondence between real-number values and raw values is
95   // real_number = (2^31) * raw_value.
Tolerance() const96   std::int32_t Tolerance() const { return 0; }
97 };
98 
99 // Op wrapping RoundingDivideByPOT
100 class RoundingDivideByPOTOp final : public UnaryOpBase {
101  public:
RoundingDivideByPOTOp(int exponent)102   RoundingDivideByPOTOp(int exponent) : exponent_(exponent) {}
ReferenceOp(std::int32_t x) const103   std::int32_t ReferenceOp(std::int32_t x) const {
104     const double d = static_cast<double>(x) / (1ll << exponent_);
105     return static_cast<std::int32_t>(std::round(d));
106   }
107   template <typename tRawType>
Op(tRawType x) const108   tRawType Op(tRawType x) const {
109     return RoundingDivideByPOT(x, exponent_);
110   }
111 
112  private:
113   const int exponent_;
114 };
115 
116 // Op wrapping SaturatingRoundingMultiplyByPOT
117 template <int tExponent>
118 class SaturatingRoundingMultiplyByPOTOp final : public UnaryOpBase {
119  public:
ReferenceOp(std::int32_t x) const120   std::int32_t ReferenceOp(std::int32_t x) const {
121     const double d = static_cast<double>(x) * std::pow(2., tExponent);
122     const double clamp_min = std::numeric_limits<std::int32_t>::min();
123     const double clamp_max = std::numeric_limits<std::int32_t>::max();
124     const double clamped = std::min(clamp_max, std::max(clamp_min, d));
125     return static_cast<std::int32_t>(std::round(clamped));
126   }
127   template <typename tRawType>
Op(tRawType x) const128   tRawType Op(tRawType x) const {
129     return SaturatingRoundingMultiplyByPOT<tExponent>(x);
130   }
131 };
132 
133 // Op wrapping exp_on_interval_between_negative_one_quarter_and_0_excl
134 class ExpOnIntervalBetweenNegativeOneQuarterAnd0ExclOp final
135     : public UnaryOpBase {
136  public:
MinInput() const137   std::int32_t MinInput() const { return -(1 << 29); }
MaxInput() const138   std::int32_t MaxInput() const { return 0; }
Tolerance() const139   std::int32_t Tolerance() const { return 500; }
ReferenceOp(std::int32_t x) const140   std::int32_t ReferenceOp(std::int32_t x) const {
141     using F = FixedPoint<std::int32_t, 0>;
142     const double d = ToDouble(F::FromRaw(x));
143     const double e = std::exp(d);
144     return F::FromDouble(e).raw();
145   }
146   template <typename tRawType>
Op(tRawType x) const147   tRawType Op(tRawType x) const {
148     using F = FixedPoint<tRawType, 0>;
149     const F f = F::FromRaw(x);
150     const F e = exp_on_interval_between_negative_one_quarter_and_0_excl(f);
151     return e.raw();
152   }
153 };
154 
155 // Op wrapping exp_on_negative_values
156 template <int tIntegerBits>
157 class ExpOnNegativeValuesOp final : public UnaryOpBase {
158  public:
MaxInput() const159   std::int32_t MaxInput() const { return 0; }
Tolerance() const160   std::int32_t Tolerance() const { return 500; }
ReferenceOp(std::int32_t x) const161   std::int32_t ReferenceOp(std::int32_t x) const {
162     using F = FixedPoint<std::int32_t, tIntegerBits>;
163     using F0 = FixedPoint<std::int32_t, 0>;
164     const double d = ToDouble(F::FromRaw(x));
165     const double e = std::exp(d);
166     return F0::FromDouble(e).raw();
167   }
168   template <typename tRawType>
Op(tRawType x) const169   tRawType Op(tRawType x) const {
170     using F = FixedPoint<tRawType, tIntegerBits>;
171     const F f = F::FromRaw(x);
172     return exp_on_negative_values(f).raw();
173   }
174 };
175 
176 // Op wrapping one_minus_x_over_one_plus_x_for_x_in_0_1
177 class OneMinusXOverOnePlusXForXIn01Op final : public UnaryOpBase {
178  public:
MinInput() const179   std::int32_t MinInput() const { return 0; }
Tolerance() const180   std::int32_t Tolerance() const { return 12; }
ReferenceOp(std::int32_t x) const181   std::int32_t ReferenceOp(std::int32_t x) const {
182     using F = FixedPoint<std::int32_t, 0>;
183     const double d = ToDouble(F::FromRaw(x));
184     const double e = (1 - d) / (1 + d);
185     return F::FromDouble(e).raw();
186   }
187   template <typename tRawType>
Op(tRawType x) const188   tRawType Op(tRawType x) const {
189     using F = FixedPoint<tRawType, 0>;
190     const F f = F::FromRaw(x);
191     return one_minus_x_over_one_plus_x_for_x_in_0_1(f).raw();
192   }
193 };
194 
195 // Op wrapping tanh
196 template <int tIntegerBits>
197 class TanhOp final : public UnaryOpBase {
198  public:
Tolerance() const199   std::int32_t Tolerance() const { return 310; }
ReferenceOp(std::int32_t x) const200   std::int32_t ReferenceOp(std::int32_t x) const {
201     using F = FixedPoint<std::int32_t, tIntegerBits>;
202     using F0 = FixedPoint<std::int32_t, 0>;
203     const double d = ToDouble(F::FromRaw(x));
204     const double e = std::tanh(d);
205     return F0::FromDouble(e).raw();
206   }
207   template <typename tRawType>
Op(tRawType x) const208   tRawType Op(tRawType x) const {
209     using F = FixedPoint<tRawType, tIntegerBits>;
210     const F f = F::FromRaw(x);
211     return tanh(f).raw();
212   }
213 };
214 
215 // Op wrapping one_over_one_plus_x_for_x_in_0_1
216 class OneOverOnePlusXForXIn01Op final : public UnaryOpBase {
217  public:
MinInput() const218   std::int32_t MinInput() const { return 0; }
Tolerance() const219   std::int32_t Tolerance() const { return 6; }
ReferenceOp(std::int32_t x) const220   std::int32_t ReferenceOp(std::int32_t x) const {
221     using F = FixedPoint<std::int32_t, 0>;
222     const double d = ToDouble(F::FromRaw(x));
223     const double e = 1 / (1 + d);
224     return F::FromDouble(e).raw();
225   }
226   template <typename tRawType>
Op(tRawType x) const227   tRawType Op(tRawType x) const {
228     using F = FixedPoint<tRawType, 0>;
229     const F f = F::FromRaw(x);
230     return one_over_one_plus_x_for_x_in_0_1(f).raw();
231   }
232 };
233 
234 // Op wrapping logistic
235 template <int tIntegerBits>
236 class LogisticOp final : public UnaryOpBase {
237  public:
Tolerance() const238   std::int32_t Tolerance() const { return 155; }
ReferenceOp(std::int32_t x) const239   std::int32_t ReferenceOp(std::int32_t x) const {
240     using F = FixedPoint<std::int32_t, tIntegerBits>;
241     using F0 = FixedPoint<std::int32_t, 0>;
242     const double d = ToDouble(F::FromRaw(x));
243     const double e = 1 / (1 + std::exp(-d));
244     return F0::FromDouble(e).raw();
245   }
246   template <typename tRawType>
Op(tRawType x) const247   tRawType Op(tRawType x) const {
248     using F = FixedPoint<tRawType, tIntegerBits>;
249     const F f = F::FromRaw(x);
250     return logistic(f).raw();
251   }
252 };
253 
254 // Tests a given op, on a given list of int32 input values.
255 template <typename tUnaryOpType>
TestUnaryOp(const tUnaryOpType & unary_op,const std::vector<std::int32_t> & testvals_int32)256 void TestUnaryOp(const tUnaryOpType& unary_op,
257                  const std::vector<std::int32_t>& testvals_int32) {
258   Check(0 == (testvals_int32.size() % SimdVectorSize));
259   for (std::size_t i = 0; i < testvals_int32.size(); i += SimdVectorSize) {
260     // First, clamp input int32 values accoding to the MinInput() and MaxInput()
261     // bounds returned by the op.
262     std::int32_t input[SimdVectorSize] = {0};
263     for (std::size_t j = 0; j < SimdVectorSize; j++) {
264       const std::int32_t raw_input = testvals_int32[i + j];
265       input[j] = std::min(unary_op.MaxInput(),
266                           std::max(unary_op.MinInput(), raw_input));
267     }
268     // Compute reference results and check that the actual results on
269     // scalar inputs agree with them, to the Tolerance() returned by the op.
270     std::int32_t reference[SimdVectorSize] = {0};
271     std::int32_t actual_scalar[SimdVectorSize] = {0};
272     for (std::size_t j = 0; j < SimdVectorSize; j++) {
273       reference[j] = unary_op.ReferenceOp(input[j]);
274       actual_scalar[j] = unary_op.Op(input[j]);
275       const std::int64_t diff = static_cast<std::int64_t>(actual_scalar[j]) -
276                                 static_cast<std::int64_t>(reference[j]);
277       Check(std::abs(diff) <= unary_op.Tolerance());
278     }
279     // Check that the actual results on SIMD inputs agree *exactly* with the
280     // actual results on scalar inputs. I.e. SIMD must make absolutely no
281     // difference
282     // to the results, regardless of the fact that both scalar and SIMD results
283     // may differ from the reference results.
284     std::int32_t actual_simd[SimdVectorSize] = {0};
285     StoreSimdVector(actual_simd, unary_op.Op(LoadSimdVector(input)));
286     for (std::size_t j = 0; j < SimdVectorSize; j++) {
287       Check(actual_simd[j] == actual_scalar[j]);
288     }
289   }
290 }
291 
292 template <int tIntegerBits>
test_convert(FixedPoint<std::int32_t,tIntegerBits> x)293 void test_convert(FixedPoint<std::int32_t, tIntegerBits> x) {
294   typedef FixedPoint<std::int32_t, tIntegerBits> F;
295   F y = F::FromDouble(ToDouble(x));
296   Check(y == x);
297 }
298 
299 template <int tIntegerBits_a, int tIntegerBits_b>
test_Rescale(FixedPoint<std::int32_t,tIntegerBits_a> a)300 void test_Rescale(FixedPoint<std::int32_t, tIntegerBits_a> a) {
301   FixedPoint<std::int32_t, tIntegerBits_b> actual = Rescale<tIntegerBits_b>(a);
302   FixedPoint<std::int32_t, tIntegerBits_b> expected =
303       FixedPoint<std::int32_t, tIntegerBits_b>::FromDouble(ToDouble(a));
304   Check(actual == expected);
305 }
306 
307 template <int tIntegerBits_a, int tIntegerBits_b>
test_Rescale(const std::vector<std::int32_t> & testvals_int32)308 void test_Rescale(const std::vector<std::int32_t>& testvals_int32) {
309   for (auto a : testvals_int32) {
310     FixedPoint<std::int32_t, tIntegerBits_a> aq;
311     aq.raw() = a;
312     test_Rescale<tIntegerBits_a, tIntegerBits_b>(aq);
313   }
314 }
315 
316 template <int tIntegerBits_a, int tIntegerBits_b>
test_mul(FixedPoint<std::int32_t,tIntegerBits_a> a,FixedPoint<std::int32_t,tIntegerBits_b> b)317 void test_mul(FixedPoint<std::int32_t, tIntegerBits_a> a,
318               FixedPoint<std::int32_t, tIntegerBits_b> b) {
319   static const int ProductIntegerBits = tIntegerBits_a + tIntegerBits_b;
320   using ProductFixedPoint = FixedPoint<std::int32_t, ProductIntegerBits>;
321   ProductFixedPoint ab;
322   ab = a * b;
323   double a_double = ToDouble(a);
324   double b_double = ToDouble(b);
325   double ab_double = a_double * b_double;
326   ProductFixedPoint expected = ProductFixedPoint::FromDouble(ab_double);
327   std::int64_t diff = std::int64_t(ab.raw()) - std::int64_t(expected.raw());
328   Check(std::abs(diff) <= 1);
329 }
330 
331 template <int tIntegerBits_a, int tIntegerBits_b>
test_mul(const std::vector<std::int32_t> & testvals_int32)332 void test_mul(const std::vector<std::int32_t>& testvals_int32) {
333   for (auto a : testvals_int32) {
334     for (auto b : testvals_int32) {
335       FixedPoint<std::int32_t, tIntegerBits_a> aq;
336       FixedPoint<std::int32_t, tIntegerBits_b> bq;
337       aq.raw() = a;
338       bq.raw() = b;
339       test_mul(aq, bq);
340     }
341   }
342 }
343 
344 template <int tExponent, int tIntegerBits_a>
test_ExactMulByPot(FixedPoint<std::int32_t,tIntegerBits_a> a)345 void test_ExactMulByPot(FixedPoint<std::int32_t, tIntegerBits_a> a) {
346   double x = ToDouble(a) * std::pow(2.0, tExponent);
347   double y = ToDouble(ExactMulByPot<tExponent>(a));
348   Check(x == y);
349 }
350 
351 template <int tExponent, int tIntegerBits_a>
test_ExactMulByPot(const std::vector<std::int32_t> & testvals_int32)352 void test_ExactMulByPot(const std::vector<std::int32_t>& testvals_int32) {
353   for (auto a : testvals_int32) {
354     FixedPoint<std::int32_t, tIntegerBits_a> aq;
355     aq.raw() = a;
356     test_ExactMulByPot<tExponent, tIntegerBits_a>(aq);
357   }
358 }
359 
360 // Make the list of test values to test each op against.
MakeTestValsInt32()361 std::vector<std::int32_t> MakeTestValsInt32() {
362   std::vector<std::int32_t> testvals_int32;
363 
364   for (int i = 0; i < 31; i++) {
365     testvals_int32.push_back((1 << i) - 2);
366     testvals_int32.push_back((1 << i) - 1);
367     testvals_int32.push_back((1 << i));
368     testvals_int32.push_back((1 << i) + 1);
369     testvals_int32.push_back((1 << i) + 2);
370     testvals_int32.push_back(-(1 << i) - 2);
371     testvals_int32.push_back(-(1 << i) - 1);
372     testvals_int32.push_back(-(1 << i));
373     testvals_int32.push_back(-(1 << i) + 1);
374     testvals_int32.push_back(-(1 << i) + 2);
375   }
376   testvals_int32.push_back(std::numeric_limits<std::int32_t>::min());
377   testvals_int32.push_back(std::numeric_limits<std::int32_t>::min() + 1);
378   testvals_int32.push_back(std::numeric_limits<std::int32_t>::min() + 2);
379   testvals_int32.push_back(std::numeric_limits<std::int32_t>::max() - 2);
380   testvals_int32.push_back(std::numeric_limits<std::int32_t>::max() - 1);
381   testvals_int32.push_back(std::numeric_limits<std::int32_t>::max());
382 
383   std::mt19937 random_engine;
384   std::uniform_int_distribution<std::int32_t> uniform_distribution(
385       std::numeric_limits<std::int32_t>::min(),
386       std::numeric_limits<std::int32_t>::max());
387   for (int i = 0; i < 1000; i++) {
388     testvals_int32.push_back(uniform_distribution(random_engine));
389   }
390 
391   // SIMD tests will require the length of testvals_int32 to be a multiple
392   // of SIMD vector size.
393   while (testvals_int32.size() % SimdVectorSize) {
394     testvals_int32.push_back(0);
395   }
396 
397   std::sort(testvals_int32.begin(), testvals_int32.end());
398   return testvals_int32;
399 }
400 
401 }  // end anonymous namespace
402 
403 }  // end namespace gemmlowp
404 
main()405 int main() {
406   using namespace gemmlowp;
407 
408   const std::vector<std::int32_t> testvals_int32 = MakeTestValsInt32();
409 
410   for (int s = 0; s < 32; s++) {
411     TestUnaryOp(RoundingDivideByPOTOp(s), testvals_int32);
412   }
413 
414   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-31>(), testvals_int32);
415   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-30>(), testvals_int32);
416   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-29>(), testvals_int32);
417   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-17>(), testvals_int32);
418   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-16>(), testvals_int32);
419   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-15>(), testvals_int32);
420   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-4>(), testvals_int32);
421   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-3>(), testvals_int32);
422   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-2>(), testvals_int32);
423   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<-1>(), testvals_int32);
424   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<0>(), testvals_int32);
425   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<1>(), testvals_int32);
426   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<2>(), testvals_int32);
427   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<3>(), testvals_int32);
428   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<4>(), testvals_int32);
429   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<15>(), testvals_int32);
430   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<16>(), testvals_int32);
431   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<17>(), testvals_int32);
432   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<29>(), testvals_int32);
433   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<30>(), testvals_int32);
434   TestUnaryOp(SaturatingRoundingMultiplyByPOTOp<31>(), testvals_int32);
435 
436   TestUnaryOp(ExpOnIntervalBetweenNegativeOneQuarterAnd0ExclOp(),
437               testvals_int32);
438   TestUnaryOp(ExpOnNegativeValuesOp<0>(), testvals_int32);
439   TestUnaryOp(ExpOnNegativeValuesOp<1>(), testvals_int32);
440   TestUnaryOp(ExpOnNegativeValuesOp<2>(), testvals_int32);
441   TestUnaryOp(ExpOnNegativeValuesOp<3>(), testvals_int32);
442   TestUnaryOp(ExpOnNegativeValuesOp<4>(), testvals_int32);
443   TestUnaryOp(ExpOnNegativeValuesOp<5>(), testvals_int32);
444   TestUnaryOp(ExpOnNegativeValuesOp<6>(), testvals_int32);
445 
446   TestUnaryOp(OneMinusXOverOnePlusXForXIn01Op(), testvals_int32);
447   TestUnaryOp(TanhOp<0>(), testvals_int32);
448   TestUnaryOp(TanhOp<1>(), testvals_int32);
449   TestUnaryOp(TanhOp<2>(), testvals_int32);
450   TestUnaryOp(TanhOp<3>(), testvals_int32);
451   TestUnaryOp(TanhOp<4>(), testvals_int32);
452   TestUnaryOp(TanhOp<5>(), testvals_int32);
453   TestUnaryOp(TanhOp<6>(), testvals_int32);
454 
455   TestUnaryOp(OneOverOnePlusXForXIn01Op(), testvals_int32);
456   TestUnaryOp(LogisticOp<0>(), testvals_int32);
457   TestUnaryOp(LogisticOp<1>(), testvals_int32);
458   TestUnaryOp(LogisticOp<2>(), testvals_int32);
459   TestUnaryOp(LogisticOp<3>(), testvals_int32);
460   TestUnaryOp(LogisticOp<4>(), testvals_int32);
461   TestUnaryOp(LogisticOp<5>(), testvals_int32);
462   TestUnaryOp(LogisticOp<6>(), testvals_int32);
463 
464   for (auto a : testvals_int32) {
465     FixedPoint<std::int32_t, 4> x;
466     x.raw() = a;
467     test_convert(x);
468   }
469 
470   test_mul<0, 0>(testvals_int32);
471   test_mul<0, 1>(testvals_int32);
472   test_mul<2, 0>(testvals_int32);
473   test_mul<1, 1>(testvals_int32);
474   test_mul<4, 4>(testvals_int32);
475   test_mul<3, 5>(testvals_int32);
476   test_mul<7, 2>(testvals_int32);
477   test_mul<14, 15>(testvals_int32);
478 
479   test_Rescale<0, 0>(testvals_int32);
480   test_Rescale<0, 1>(testvals_int32);
481   test_Rescale<2, 0>(testvals_int32);
482   test_Rescale<4, 4>(testvals_int32);
483   test_Rescale<4, 5>(testvals_int32);
484   test_Rescale<6, 3>(testvals_int32);
485   test_Rescale<13, 9>(testvals_int32);
486 
487   test_ExactMulByPot<0, 0>(testvals_int32);
488   test_ExactMulByPot<0, 4>(testvals_int32);
489   test_ExactMulByPot<1, 4>(testvals_int32);
490   test_ExactMulByPot<3, 2>(testvals_int32);
491   test_ExactMulByPot<-4, 5>(testvals_int32);
492   test_ExactMulByPot<-2, 6>(testvals_int32);
493 
494   std::cerr << "All tests passed." << std::endl;
495 }
496