Skip to content

Commit d1d2737

Browse files
committed
major redesign of floating point comparison
1 parent a08bb5a commit d1d2737

File tree

6 files changed

+1026
-243
lines changed

6 files changed

+1026
-243
lines changed

changes.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Changes
22
=======
33

4+
0.7.6
5+
* major redesign of floating point comparison
6+
47
0.7.5.3
58
* added an untag function to simplify tag_cast'ing to an untagged type
69

math.hpp

Lines changed: 301 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,36 @@ namespace unlib {
2020

2121
namespace detail {
2222

23-
template<typename U, typename S, typename V, typename T, std::intmax_t N, std::intmax_t D>
24-
auto pow(const quantity<U,S,T,V>& q, const std::ratio<N,D>) {
25-
return pow_quantity_t<quantity<U,S,T,V>, std::ratio<N,D>>{ std::pow( static_cast<double>(q.get()), static_cast<double>(N)/D ) };
23+
template<typename Float1, typename T>
24+
using if_float1_pt_t = typename std::enable_if< is_floating_point<Float1>::value, T >::type;
25+
template<typename Float1, typename Float2, typename T>
26+
using if_float2_pt_t = typename std::enable_if< is_floating_point<Float1>::value
27+
and is_floating_point<Float2>::value, T >::type;
28+
29+
template<typename D, typename S, typename V, typename T, std::intmax_t Nom, std::intmax_t Den>
30+
auto pow(const quantity<D,S,T,V>& q, const std::ratio<Nom,Den>) {
31+
return pow_quantity_t<quantity<D,S,T,V>, std::ratio<Nom,Den>>{ std::pow( static_cast<double>(q.get()), static_cast<double>(Nom)/Den ) };
2632
}
2733

2834
template<typename U, typename S, typename V, typename T>
2935
auto pow(const quantity<U,S,T,V>& q, const std::ratio<1,2>) {
3036
return pow_quantity_t<quantity<U,S,T,V>,std::ratio<1,2>>(std::sqrt(q.get()));
3137
}
3238

39+
template<typename TolTag, typename F, typename>
40+
struct tolerance_aux {
41+
using tolerance_tag_type = TolTag;
42+
using value_type = F;
43+
value_type v;
44+
};
45+
46+
struct tolerance_val_tag;
47+
struct tolerance_nom_tag;
48+
struct tolerance_frc_tag;
49+
50+
template<typename V, typename S=scale_t<1>>
51+
using fraction = quantity<dimensionless, S, V, no_tag>;
52+
3353
}
3454

3555
/**
@@ -113,6 +133,284 @@ template<typename U, typename S, typename V, typename T>
113133
auto cbrt(quantity<U,S,T,V> q) {return pow<std::ratio<1,3>>(q);}
114134
/** @} */
115135

136+
/**
137+
* @brief Provide the minimum of two quantities
138+
*
139+
* @param lhs, rhs quantities to compare
140+
*
141+
* @return minimum value of @p lhs and @p rhs
142+
*/
143+
template<typename D, typename F, typename S1, typename S2, typename T>
144+
auto min(const unlib::quantity<D,S1,F,T> lhs, const unlib::quantity<D,S2,F,T> rhs) {
145+
using std::min;
146+
return unlib::quantity<D,S1,F,T>{min(lhs.get(), rhs.template get_scaled<S1>())};
147+
}
148+
149+
/**
150+
* @brief Provide the maximum of two quantities
151+
*
152+
* @param lhs, rhs quantities to compare
153+
*
154+
* @return maximum value of @p lhs and @p rhs
155+
*/
156+
template<typename D, typename F, typename S1, typename S2, typename T>
157+
auto max(const unlib::quantity<D,S1,F,T> lhs, const unlib::quantity<D,S2,F,T> rhs) {
158+
using std::max;
159+
return unlib::quantity<D,S1,F,T>{max(lhs.get(), rhs.template get_scaled<S1>())};
160+
}
161+
162+
/**
163+
* @{
164+
*
165+
* @brief Floating point comparisons
166+
*
167+
* Due to the limitations of floating point precision, more often than
168+
* never values which, mathematically, ought to be equal, are in fact not
169+
* totally equal. Therefore, floating point comparisons should be done with
170+
* a certain tolerance.
171+
*
172+
* This provides a framework for comparing floating point types and
173+
* quantities of floating point types. The tolerance can either be an
174+
* absolute value, or a fraction of a nominal value. For example, in order to
175+
* test whether two masses are equal, the tolerance could be given either as
176+
* an absolute value (10mg) or as a fraction (0.1%) of a nominal weight (1t).
177+
* The library provides default values for the nominal and the weight, which
178+
* can be used, but this must be indicated explicitly.
179+
*
180+
* The defaults are accessed via traits which can be specialized by users in
181+
* order to provide their own defaults.
182+
*/
183+
184+
constexpr double tolerance_default_nominal = 1.; /**< default tolerance nominal for is_near() etc. */
185+
constexpr double tolerance_default_fraction = 0.0001; /**< default tolerance fraction for is_near() etc. */
186+
187+
/**
188+
* @{
189+
*
190+
* @brief wrapper for tolerance values
191+
*
192+
* In order to distinguish between tolerance values, nominals, and fractions
193+
* in floating point comparisons, those are wrapped in these types.
194+
*
195+
* @note These are the result types of @ref tolerance_value(), @ref tolerance_nominal(),
196+
* and @ref tolerance_fraction(). Users should not have to deal with them
197+
* directly.
198+
*/
199+
template<typename V> using tolerance_val = detail::tolerance_aux<detail::tolerance_val_tag,V,V>;
200+
template<typename V> using tolerance_nom = detail::tolerance_aux<detail::tolerance_nom_tag,V,V>;
201+
template<typename V, typename Q> using tolerance_frc = detail::tolerance_aux<detail::tolerance_frc_tag,V,Q>;
202+
/** @} */
203+
204+
/**
205+
* @{
206+
*
207+
* @brief tolerance value calculations
208+
*
209+
* A tolerance value can be calculated by multiplying a nominal and a fraction.
210+
*
211+
* @param n tolerance nominal value
212+
* @param f tolerance fraction value
213+
*
214+
* @return tolerance value
215+
*/
216+
template<typename U, typename S, typename SF, typename V, typename T, typename Q>
217+
inline constexpr
218+
tolerance_val<quantity<U,S,V,T>> operator*(tolerance_nom<quantity<U,S,V,T>> n, tolerance_frc<detail::fraction<V,SF>,Q> f)
219+
{return tolerance_val<quantity<U,S,V,T>>{n.v * f.v.template get_scaled<no_scaling>()};}
220+
221+
template<typename F, typename SF, typename Q>
222+
inline constexpr
223+
tolerance_val<F> operator*(tolerance_nom<F> n, tolerance_frc<detail::fraction<F,SF>,Q> f)
224+
{return tolerance_val<F>{n.v * f.v.template get_scaled<no_scaling>()};}
225+
/** @} */
226+
227+
/**
228+
* @{
229+
* @brief tolerance traits
230+
*
231+
* These provide the necessary functions to obtain default tolerance values,
232+
* nominals, and fractions.
233+
*
234+
* @tparam F Floating point type.
235+
*/
236+
template<typename F>
237+
struct tolerance_traits {
238+
using float_t = detail::if_float1_pt_t<F,F>;
239+
using fract_t = detail::fraction<float_t>;
240+
241+
static constexpr auto value () {return nominal() * fraction();}
242+
static constexpr auto nominal () {return tolerance_nom<float_t >{ static_cast<float_t>(tolerance_default_nominal ) };}
243+
static constexpr auto fraction() {return tolerance_frc<fract_t,float_t>{fract_t{static_cast<float_t>(tolerance_default_fraction)}};}
244+
};
245+
246+
/** specialization for quantities of floating point types */
247+
template<typename U, typename S, typename F, typename T>
248+
struct tolerance_traits<quantity<U,S,F,T>> {
249+
using float_t = detail::if_float1_pt_t<F,F>;
250+
using quant_t = quantity<U,S,float_t,T>;
251+
using fract_t = detail::fraction<float_t>;
252+
253+
static constexpr auto value () {return nominal() * fraction();}
254+
static constexpr auto nominal () {return tolerance_nom<quant_t >{quant_t{tolerance_traits<float_t>::nominal ().v}};}
255+
static constexpr auto fraction() {return tolerance_frc<fract_t,quant_t>{ tolerance_traits<float_t>::fraction().v };}
256+
};
257+
/** @} */
258+
259+
/**
260+
* @{
261+
*
262+
* @brief create a tolerance value, nominal, or fraction
263+
*
264+
* These function turn a value into a tolerance value, nominal, or fraction.
265+
*
266+
* @param val value to turn into a tolerance value, nominal, or fraction
267+
*
268+
* @return tolerance value, nominal, or fraction
269+
*/
270+
template<typename T> constexpr auto tolerance_value (T val) {return tolerance_val<T >{val};}
271+
template<typename T> constexpr auto tolerance_nominal (T nom) {return tolerance_nom<T >{nom};}
272+
template<typename T, typename F> constexpr auto tolerance_fraction(F frc) {return tolerance_frc<F,T>{frc};}
273+
/** @} */
274+
275+
/**
276+
* @{
277+
*
278+
* @brief get the default tolerance value, nominal, or fraction
279+
*
280+
* These function return the tolerance value, nominal, or fraction for a
281+
* given type.
282+
*
283+
* @return default tolerance value, nominal, or fraction
284+
*/
285+
template<typename T> constexpr auto tolerance_value () {return tolerance_traits<T>::value ();}
286+
template<typename T> constexpr auto tolerance_nominal () {return tolerance_traits<T>::nominal ();}
287+
template<typename T> constexpr auto tolerance_fraction() {return tolerance_traits<T>::fraction();}
288+
/** @} */
289+
290+
namespace detail {
291+
292+
template<typename V, typename F, typename Q> constexpr auto get_tol_val(tolerance_nom<V> n, tolerance_frc<F,Q> f) {return n * f;}
293+
template<typename V, typename F, typename Q> constexpr auto get_tol_val(tolerance_frc<F,Q> f, tolerance_nom<V> n) {return get_tol_val(n,f);}
294+
template<typename V> constexpr auto get_tol_val(tolerance_aux<tolerance_val_tag,V,V> v) {return v;}
295+
template<typename V> constexpr auto get_tol_val(tolerance_aux<tolerance_nom_tag,V,V> n) {return get_tol_val(n, tolerance_traits<V>::fraction());}
296+
template<typename F, typename Q> constexpr auto get_tol_val(tolerance_aux<tolerance_frc_tag,F,Q> f) {return get_tol_val(f, tolerance_traits<Q>::nominal ());}
297+
298+
template<typename T1, typename T2, typename Tol> constexpr bool is_near_impl (T1 lval, T2 rval, tolerance_val<Tol> tol) {using std::abs; return abs(lval-rval) <= abs(tol.v);}
299+
template<typename T1, typename T2, typename Tol> constexpr bool is_smaller_impl(T1 lval, T2 rval, tolerance_val<Tol> tol) {using std::abs; return not is_near_impl(lval,rval,tol) and lval<rval;}
300+
template<typename T1, typename T2, typename Tol> constexpr bool is_greater_impl(T1 lval, T2 rval, tolerance_val<Tol> tol) {using std::abs; return not is_near_impl(lval,rval,tol) and lval>rval;}
301+
302+
template<typename T1, typename T2, typename Aux> constexpr detail::if_float2_pt_t<T1,T2,bool> is_near (T1 lval, T2 rval, Aux tol) {return is_near_impl (lval, rval, get_tol_val(tol));}
303+
template<typename T1, typename T2, typename Aux> constexpr detail::if_float2_pt_t<T1,T2,bool> is_smaller(T1 lval, T2 rval, Aux tol) {return is_smaller_impl(lval, rval, get_tol_val(tol));}
304+
template<typename T1, typename T2, typename Aux> constexpr detail::if_float2_pt_t<T1,T2,bool> is_greater(T1 lval, T2 rval, Aux tol) {return is_greater_impl(lval, rval, get_tol_val(tol));}
305+
template<typename T1, typename T2, typename Aux1, typename Aux2> constexpr detail::if_float2_pt_t<T1,T2,bool> is_near (T1 lval, T2 rval, Aux1 tol1, Aux2 tol2) {return is_near_impl (lval, rval, get_tol_val(tol1,tol2));}
306+
template<typename T1, typename T2, typename Aux1, typename Aux2> constexpr detail::if_float2_pt_t<T1,T2,bool> is_smaller(T1 lval, T2 rval, Aux1 tol1, Aux2 tol2) {return is_smaller_impl(lval, rval, get_tol_val(tol1,tol2));}
307+
template<typename T1, typename T2, typename Aux1, typename Aux2> constexpr detail::if_float2_pt_t<T1,T2,bool> is_greater(T1 lval, T2 rval, Aux1 tol1, Aux2 tol2) {return is_greater_impl(lval, rval, get_tol_val(tol1,tol2));}
308+
309+
}
310+
311+
/** @{
312+
* @note These algorithms take tolerances. As tolerances, either an absolute
313+
* tolerance value (created via @ref tolerance_value()) or a tolerance
314+
* nominal (created via @ref tolerance_nominal()) and a tolerance
315+
* fraction (created via @ref tolerance_fraction()) can be passed.
316+
* Either the nominal or the fraction can also be omitted, in which
317+
* case the default is obtained via the @ref tolerance_traits.
318+
*
319+
* @note Tolerances are inclusive. That is, two values which differ exactly by
320+
* the tolerance value are considered equal.
321+
*
322+
* @note These algorithms will always use the `abs()` value of the tolerance,
323+
* so the sign of the tolerance values will be ignored.
324+
*
325+
*/
326+
327+
/** @{
328+
* @brief compare two floating point values
329+
*
330+
* This compares two floating point values or two quantities of floating point
331+
* values.
332+
*
333+
* @param lval left value to compare
334+
* @param rval right value to compare
335+
* @param tol, tol1, tol2 tolerance
336+
*
337+
* @note The quantities do not need to be of the same scale, but they need to
338+
* have the same unit, value type, and tag.
339+
*/
340+
/**
341+
* @{
342+
* @return true if both quantities are equal within the given tolerance.
343+
*/
344+
template<typename F1, typename F2, typename TT, typename TF, typename X>
345+
bool is_near (F1 lval, F2 rval, detail::tolerance_aux<TT,TF,X> tol) {return detail::is_near (lval, rval, tol);}
346+
template<typename F1, typename F2, typename TT1, typename TF1, typename TT2, typename TF2, typename X1, typename X2>
347+
bool is_near (F1 lval, F2 rval, detail::tolerance_aux<TT1,TF1,X1> tol1, detail::tolerance_aux<TT2,TF2,X2> tol2) {return detail::is_near (lval, rval, tol1, tol2);}
348+
/** @} */
349+
/**
350+
* @{
351+
* @return true if both quantities are not equal within the given tolerance
352+
* and the left value is smaller than the right value.
353+
*/
354+
template<typename F1, typename F2, typename TT1, typename TF1, typename TT2, typename TF2, typename X1, typename X2>
355+
bool is_smaller(F1 lval, F2 rval, detail::tolerance_aux<TT1,TF1,X1> tol1, detail::tolerance_aux<TT2,TF2,X2> tol2) {return detail::is_smaller(lval, rval, tol1, tol2);}
356+
template<typename F1, typename F2, typename TT, typename TF, typename X>
357+
bool is_smaller(F1 lval, F2 rval, detail::tolerance_aux<TT,TF,X> tol) {return detail::is_smaller(lval, rval, tol);}
358+
/** @} */
359+
/**
360+
* @{
361+
* @return true if both quantities are not equal within the given tolerance
362+
* and the left value is greater than the right value.
363+
*/
364+
template<typename F1, typename F2, typename TT, typename TF, typename X>
365+
bool is_greater(F1 lval, F2 rval, detail::tolerance_aux<TT,TF,X> tol) {return detail::is_greater(lval, rval, tol);}
366+
template<typename F1, typename F2, typename TT1, typename TF1, typename TT2, typename TF2, typename X1, typename X2>
367+
bool is_greater(F1 lval, F2 rval, detail::tolerance_aux<TT1,TF1,X1> tol1, detail::tolerance_aux<TT2,TF2,X2> tol2) {return detail::is_greater(lval, rval, tol1, tol2);}
368+
/** @} */
369+
/** @} */
370+
371+
/** @{
372+
* @brief compare a floating point value to zero
373+
*
374+
* This compares a floating point value, or a quantity of a floating point
375+
* value, with zero.
376+
*
377+
* @param val value to compare
378+
* @param tol, tol1, tol2 tolerance
379+
*
380+
*/
381+
/* @{
382+
* @return true if the quantity is equal to zero within the tolerance
383+
* given
384+
*/
385+
template<typename F, typename TT, typename TF, typename X>
386+
bool is_near_zero (F val, detail::tolerance_aux<TT,TF,X> tol) {return detail::is_near (val,F{}, tol);}
387+
template<typename F, typename TT1, typename TF1, typename TT2, typename TF2, typename X1, typename X2>
388+
bool is_near_zero (F val, detail::tolerance_aux<TT1,TF1,X1> tol1, detail::tolerance_aux<TT2,TF2,X2> tol2) {return detail::is_near (val,F{}, tol1, tol2);}
389+
/** @} */
390+
/* @{
391+
* @return true if the quantity is smaller than zero within the tolerance
392+
* given
393+
*/
394+
template<typename F, typename TT, typename TF, typename X>
395+
bool is_smaller_zero(F val, detail::tolerance_aux<TT,TF,X> tol) {return detail::is_smaller(val,F{}, tol);}
396+
template<typename F, typename TT1, typename TF1, typename TT2, typename TF2, typename X1, typename X2>
397+
bool is_smaller_zero(F val, detail::tolerance_aux<TT1,TF1,X1> tol1, detail::tolerance_aux<TT2,TF2,X2> tol2) {return detail::is_smaller(val,F{}, tol1, tol2);}
398+
/** @} */
399+
/* @{
400+
* @return true if the quantity is greater than zero within the tolerance
401+
* given
402+
*/
403+
template<typename F, typename TT, typename TF, typename X>
404+
bool is_greater_zero(F val, detail::tolerance_aux<TT,TF,X> tol) {return detail::is_greater(val,F{}, tol);}
405+
template<typename F, typename TT1, typename TF1, typename TT2, typename TF2, typename X1, typename X2>
406+
bool is_greater_zero(F val, detail::tolerance_aux<TT1,TF1,X1> tol1, detail::tolerance_aux<TT2,TF2,X2> tol2) {return detail::is_greater(val,F{}, tol1, tol2);}
407+
/** @} */
408+
/** @} */
409+
410+
/** @} */
411+
412+
/** @} */
413+
116414
}
117415

118416
#endif //UNLIB_MATH_HPP

0 commit comments

Comments
 (0)