tatami
C++ API for different matrix representations
Loading...
Searching...
No Matches
arithmetic_utils.hpp
Go to the documentation of this file.
1#ifndef TATAMI_ARITHMETIC_UTILS_HPP
2#define TATAMI_ARITHMETIC_UTILS_HPP
3
4#include <cmath>
5
12namespace tatami {
13
23enum class ArithmeticOperation : char {
24 ADD,
25 SUBTRACT,
26 MULTIPLY,
27 DIVIDE,
28 POWER,
29 MODULO,
30 INTEGER_DIVIDE
31};
32
36// We deliberately use an auto type so as to defer a decision on what the output
37// type should be; an appropriate coercion is left to the caller classes.
38template<ArithmeticOperation op_, bool right_, typename Value_, typename Scalar_>
40 auto left = [&]() {
41 if constexpr(right_) {
42 return val;
43 } else {
44 return scalar;
45 }
46 }();
47 auto right = [&]() {
48 if constexpr(right_) {
49 return scalar;
50 } else {
51 return val;
52 }
53 }();
54
55 if constexpr(op_ == ArithmeticOperation::ADD) {
56 return left + right;
57
58 } else if constexpr(op_ == ArithmeticOperation::MULTIPLY) {
59 return left * right;
60
61 } else if constexpr(op_ == ArithmeticOperation::SUBTRACT) {
62 return left - right;
63
64 } else if constexpr(op_ == ArithmeticOperation::DIVIDE) {
65 // Assume that either Value_ is an IEEE-754 float, or that division by
66 // zero is impossible in this context. We don't apply manual checks
67 // here to avoid performance degradation; we also don't check that the
68 // other operations yield a value that doesn't overflow/underflow, so
69 // it would be odd to make an exception for div-by-zero errors.
70 return left / right;
71
72 } else if constexpr(op_ == ArithmeticOperation::POWER) {
73 return std::pow(left, right);
74
75 } else if constexpr(op_ == ArithmeticOperation::MODULO) {
76 // Based on a floored divide, so some work is necessary to
77 // get the right value when the sign is negative.
78 auto quo = left / right;
79 if constexpr(std::numeric_limits<decltype(quo)>::is_integer) {
80 auto rem = left % right;
81 return rem + (quo < 0 && rem != 0 ? right : 0);
82 } else {
83 auto rem = std::fmod(left, right);
84 return rem + (quo < 0 && rem != 0 ? right : 0);
85 }
86
87 } else if constexpr(op_ == ArithmeticOperation::INTEGER_DIVIDE) {
88 auto out = left / right;
89 if constexpr(std::numeric_limits<decltype(out)>::is_integer) {
90 // Using a floored divide. This little branch should be optimized
91 // away so don't worry too much about it.
92 return out - (out < 0 ? (left % right != 0) : 0);
93 } else {
94 return std::floor(out);
95 }
96 }
97}
98
99// Some of the helpers need to divide by zero to compute the fill value. The
100// only way to guarantee that division by zero is supported is with IEEE
101// floats, otherwise it would be undefined behavior at compile time, and the
102// compiler could do anything, including refusing to compile it. So, we hide
103// any '0/0' behind a constexpr to ensure that the compiler doesn't see it.
104template<ArithmeticOperation op_, bool right_, typename Value_, typename Scalar_>
105constexpr bool has_unsafe_divide_by_zero() {
106 typedef decltype(delayed_arithmetic<op_, right_>(std::declval<Value_>(), std::declval<Scalar_>())) Product;
107 if constexpr(std::numeric_limits<Product>::is_iec559) {
108 return false;
109 }
110
111 // POWER also involves division by zero for negative powers,
112 // but it returns an implementation-defined value; so it's "safe".
113 // - https://en.cppreference.com/w/cpp/numeric/math/pow
114
115 if constexpr(op_ == ArithmeticOperation::DIVIDE) {
116 return true;
117 }
118 if constexpr(op_ == ArithmeticOperation::MODULO) {
119 return true;
120 }
121 if constexpr(op_ == ArithmeticOperation::INTEGER_DIVIDE) {
122 return true;
123 }
124
125 return false;
126}
127
128// COMMENT: if the fill value is visible at a compile time, the coercion to the
129// output type is also visible. If the output type cannot hold the fill value,
130// we could potentially get more compile-time UB. (This mostly concerns NaNs or
131// Infs from divide-by-zero, but could apply to overflows or out-of-range casts
132// to integer types.) To get around this, we assume that the compiler supports
133// the IEC 559 spec, where - according to Annex F.4 of the C standard - the
134// cast from float to integer is merely unspecified, not undefined.
135
139}
140
141#endif
Flexible representations for matrix data.
Definition Extractor.hpp:15
ArithmeticOperation
Definition arithmetic_utils.hpp:23
auto consecutive_extractor(const Matrix< Value_, Index_ > *mat, bool row, Index_ iter_start, Index_ iter_length, Args_ &&... args)
Definition consecutive_extractor.hpp:35