Skip to content

Commit 5c4cac7

Browse files
authored
ofMin / ofMax (#7944)
#changelog #math
1 parent d61574d commit 5c4cac7

3 files changed

Lines changed: 153 additions & 0 deletions

File tree

libs/openFrameworks/math/ofMath.h

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <cstdlib>
99
#include <cmath>
10+
#include <algorithm>
1011

1112
/// \file
1213
/// ofMath provides a collection of mathematical utilities and functions.
@@ -158,6 +159,104 @@ float ofMap(float value, float inputMin, float inputMax, float outputMin, float
158159
/// \returns a floating point number in the range [min, max].
159160
float ofClamp(float value, float min, float max);
160161

162+
/// \brief return the smallest of 2 values of same type
163+
///
164+
/// \param a first value to compare
165+
/// \param b second value to compare
166+
167+
template<typename T>
168+
T ofMin(const T & t1, const T & t2) { return std::min(t1, t2); }
169+
170+
/// \brief return the smallest of 2 values of different types
171+
///
172+
/// Leverages 'std::common_type' to efficiently cast the values in compatible types
173+
/// special case for comparisons when signed and unsigned are mixed
174+
/// special case for comparisons when size_t / uint64_t are involved
175+
///
176+
/// \param a first value to compare
177+
/// \param b second value to compare
178+
179+
template<typename T, typename Q>
180+
auto ofMin(const T & t, const Q & q) {
181+
using CommonType = typename std::common_type<T, Q>::type;
182+
if constexpr ((std::is_same_v<T, uint64_t> or std::is_same_v<T, size_t>) && std::is_signed_v<Q>) {
183+
if (q < 0) {
184+
return q;
185+
} else {
186+
return std::min<Q>(static_cast<Q>(t), q);
187+
}
188+
} else if constexpr ((std::is_same_v<Q, uint64_t> or std::is_same_v<Q, size_t>) && std::is_signed_v<T>) {
189+
if (t < 0) {
190+
return t;
191+
} else {
192+
return std::min<T>(t, static_cast<T>(q));
193+
}
194+
} else if constexpr (std::is_signed_v<T> && std::is_unsigned_v<Q>) {
195+
if (t < 0 || q > static_cast<Q>(std::numeric_limits<T>::max())) {
196+
return static_cast<CommonType>(t);
197+
} else {
198+
return std::min(static_cast<CommonType>(t), static_cast<CommonType>(q));
199+
}
200+
} else if constexpr (std::is_signed_v<Q> && std::is_unsigned_v<T>){
201+
if (q < 0 || t > static_cast<T>(std::numeric_limits<Q>::max())) {
202+
return static_cast<CommonType>(q);
203+
} else {
204+
return std::min(static_cast<CommonType>(t), static_cast<CommonType>(q));
205+
}
206+
} else {
207+
return std::min(static_cast<CommonType>(t), static_cast<CommonType>(q));
208+
}
209+
}
210+
211+
/// \brief return the largest of 2 values of same type
212+
///
213+
/// \param a first value to compare
214+
/// \param b second value to compare
215+
216+
template<typename T>
217+
T ofMax(const T & t1, const T & t2) { return std::max(t1, t2); }
218+
219+
/// \brief return the largest of 2 values of different types
220+
///
221+
/// Leverages 'std::common_type' to efficiently cast the values in compatible types
222+
/// special case for comparisons when signed and unsigned are mixed
223+
/// special case for comparisons when size_t / uint64_t are involved
224+
///
225+
/// \param a first value to compare
226+
/// \param b second value to compare
227+
228+
template<typename T, typename Q>
229+
auto ofMax(const T & t, const Q & q) {
230+
using CommonType = typename std::common_type<T, Q>::type;
231+
if constexpr ((std::is_same_v<T, uint64_t> or std::is_same_v<T, size_t>) && std::is_signed_v<Q>) {
232+
if (q < 0) {
233+
return t;
234+
} else {
235+
return std::max<T>(t, static_cast<T>(q));
236+
}
237+
} else if constexpr ((std::is_same_v<Q, uint64_t> or std::is_same_v<Q, size_t>) && std::is_signed_v<T>) {
238+
if (t < 0) {
239+
return q;
240+
} else {
241+
return std::max<Q>(static_cast<Q>(t), q);
242+
}
243+
} else if constexpr (std::is_signed_v<T> && std::is_unsigned_v<Q>) {
244+
if (t < 0) {
245+
return static_cast<CommonType>(q);
246+
} else {
247+
return std::max(static_cast<CommonType>(t), static_cast<CommonType>(q));
248+
}
249+
} else if constexpr (std::is_signed_v<Q> && std::is_unsigned_v<T>){
250+
if (q < 0) {
251+
return static_cast<CommonType>(t);
252+
} else {
253+
return std::max(static_cast<CommonType>(t), static_cast<CommonType>(q));
254+
}
255+
} else {
256+
return std::max(static_cast<CommonType>(t), static_cast<CommonType>(q));
257+
}
258+
}
259+
161260
/// \brief Determine if a number is inside of a giv(float)(en range.
162261
/// \param t The value to test.
163262
/// \param min The lower bound of the range.

tests/math/minmaxTests/addons.make

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ofxUnitTests
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "ofMain.h"
2+
#include "ofxUnitTests.h"
3+
#include "ofAppNoWindow.h"
4+
5+
class ofApp: public ofxUnitTestsApp {
6+
public:
7+
void run(){
8+
// testing the std:: pass-through (same types compared)
9+
ofxTest(ofMin(static_cast<int32_t>(-5),static_cast<int32_t>(5)) == -5, "min) I32");
10+
ofxTest(ofMin(static_cast<uint32_t>(1),static_cast<uint32_t>(5)) == 1, "min) U32");
11+
ofxTest(ofMin(static_cast<char>(3),static_cast<char>(5)) == 3, "min) char");
12+
ofxTest(ofMin(static_cast<size_t>(20),static_cast<size_t>(std::numeric_limits<size_t>::max())) == 20, "min) size_t");
13+
ofxTest(ofMin(static_cast<float>(-5.1),static_cast<float>(5)) == static_cast<float>(-5.1), "min) F32");
14+
ofxTest(ofMin(static_cast<double>(23),static_cast<double>(std::numeric_limits<double>::min())) == std::numeric_limits<double>::min(), "min) U32");
15+
16+
ofxTest(ofMax(static_cast<int32_t>(-5),static_cast<int32_t>(5)) == 5, "max) I32");
17+
ofxTest(ofMax(static_cast<uint32_t>(1),static_cast<uint32_t>(5)) == 5, "max) U32");
18+
ofxTest(ofMax(static_cast<char>(3),static_cast<char>(5)) == 5, "max) char");
19+
ofxTest(ofMax(static_cast<size_t>(20),static_cast<size_t>(std::numeric_limits<size_t>::max())) == std::numeric_limits<size_t>::max(), "max) size_t");
20+
ofxTest(ofMax(static_cast<float>(-5.1),static_cast<float>(5.1)) == static_cast<float>(5.1), "max) F32");
21+
ofxTest(ofMax(static_cast<double>(23),static_cast<double>(std::numeric_limits<double>::max())) == std::numeric_limits<double>::max(), "max) U32");
22+
23+
// testing the heterogenous types, both sides (implementation is mirrored)
24+
ofxTest(ofMin(static_cast<int>(4), static_cast<float>(-4.0)) == -4, "min) I32 vs F32");
25+
ofxTest(ofMin(static_cast<float>(4), static_cast<int>(std::numeric_limits<int>::lowest())) == std::numeric_limits<int>::lowest(), "min) F32 vs I32.lowest");
26+
ofxTest(ofMin(static_cast<int>(4), static_cast<double>(std::numeric_limits<double>::lowest())) == std::numeric_limits<double>::lowest(), "min) I32 vs F64.lowest");
27+
ofxTest(ofMin(static_cast<float>(std::numeric_limits<float>::lowest()), static_cast<double>(std::numeric_limits<double>::lowest())) == std::numeric_limits<double>::lowest(), "min) F32.lowest vs F64.lowest");
28+
ofxTest(ofMin(static_cast<float>(-1), static_cast<uint16_t>(-4.0)) == -1, "min) F32 vs U16");
29+
ofxTest(ofMin(static_cast<float>(-1), static_cast<uint64_t>(-4.0)) == -1, "min) F32 vs U64");
30+
ofxTest(ofMin(static_cast<uint64_t>(-4.0), static_cast<float>(-1) ) == -1, "min) U64 vs F32");
31+
ofxTest(ofMin(static_cast<size_t>(10), static_cast<float>(-1) ) == -1, "min) size_t vs F32");
32+
ofxTest(ofMin(static_cast<size_t>(10), static_cast<float>(11) ) == 10, "min) size_t vs F32");
33+
34+
ofxTest(ofMax(static_cast<int>(4), static_cast<float>(-4.0)) == 4, "max) I32 vs F32");
35+
ofxTest(ofMax(static_cast<float>(4), static_cast<int>(std::numeric_limits<int>::max())) == std::numeric_limits<int>::max(), "max) F32 vs I32.max");
36+
ofxTest(ofMax(static_cast<int>(4), static_cast<double>(std::numeric_limits<double>::max())) == std::numeric_limits<double>::max(), "max) I32 vs F64.max");
37+
ofxTest(ofMax(static_cast<float>(std::numeric_limits<float>::max()), static_cast<double>(std::numeric_limits<double>::max())) == std::numeric_limits<double>::max(), "max) F32.max vs F64.max");
38+
ofxTest(ofMax(static_cast<float>(std::numeric_limits<float>::lowest()), static_cast<double>(std::numeric_limits<double>::lowest())) == std::numeric_limits<float>::lowest(), "max) F32.min vs F64.min");
39+
ofxTest(ofMax(static_cast<float>(1), static_cast<uint16_t>(4.0)) == 4, "max) F32 vs U16");
40+
ofxTest(ofMax(static_cast<float>(10), static_cast<uint64_t>(4.0)) == 10, "max) F32 vs U64");
41+
ofxTest(ofMax(static_cast<uint64_t>(12), static_cast<float>(-1) ) == 12, "max) U64 vs F32");
42+
ofxTest(ofMax(static_cast<size_t>(10), static_cast<float>(1) ) == 10, "max) size_t vs F32");
43+
ofxTest(ofMax(static_cast<size_t>(10), static_cast<float>(11) ) == 11, "max) size_t vs F32");
44+
}
45+
};
46+
47+
int main( ){
48+
ofInit();
49+
auto window = std::make_shared<ofAppNoWindow>();
50+
auto app = std::make_shared<ofApp>();
51+
ofRunApp(window, app);
52+
return ofRunMainLoop();
53+
}

0 commit comments

Comments
 (0)