Fließkommazahlen (float, double,...) reichen nicht immer aus, um gebrochene Zahlen darzustellen.
Festkommazahlen stellen dabei eine Möglichkeit dar. Häufig kommt es vor, dass man Anteile ausdrücken möchte, also Zahlen im Intervall [0, 1] oder [-1, 1], genau das leistet dieses Klassen-Template. Im beigelegten Archiv findet man alles, was gebraucht wird.
Darüber hinaus wird über eine Policy eine Überprüfung auf Laufzeitfehler (overflow/underflow) ermöglicht. Diese kann aber selbstverständlich abgestellt werden
Ein Beispiel:
#include <iostream>
#include "part.h"
using namespace std;
using namespace numerics;
typedef part<unsigned int, double, part_policies::assert_check> part_t;
int main()
{
part_t x = part_t::from_float(0.25);
part_t y = part_t::from_float(0.5);
part_t z = x * y;
cout << z << endl;
z *= 6;
cout << z << endl;
z -= 0.6;
cout << z << endl;
z -= y; // z would be less than 0, assertion fails
}
Alles anzeigen
Der Code des Klassen-Templates:
/*
(C) 2009 Jonathan Schmidt-Dominé
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
To read the text of the GNU Lesser General Public License,
see <http://www.gnu.org/licenses/>.
*/
#ifndef NUMERICS_PART_H
#define NUMERICS_PART_H
#include <cassert>
#include <limits>
// When you don't want to use STL's <iostream>, declare NUMERICS_NO_IO.
#ifndef NUMERICS_NO_IO
#include <iostream>
#endif
#include "operators.h"
namespace numerics
{
namespace part_policies
{
/**
* No checks for overflow and underflow will be performed.
*/
template<class T>
struct no_check
{
typedef typename T::data_type data_type;
inline static void plus(const data_type, const data_type)
{}
inline static void minus(const data_type, const data_type)
{}
template<typename F>
inline static void from_float(const F f)
{}
template<typename F>
inline static void multiply(const data_type, const F)
{}
template<typename F>
inline static void divide(const data_type, const F)
{}
};
/**
* Checks are performed at runtime using assert, iff NDEBUG was not set.
* This makes it simple to search for bugs in calculations.
*/
template<class T>
struct assert_check
{
typedef typename T::data_type data_type;
inline static void plus(const data_type first, const data_type second)
{
assert(T::max() - second >= first);
}
inline static void minus(const data_type first, const data_type second)
{
assert(first - T::min() >= second);
}
template<typename F>
inline static void from_float(const F f)
{
assert(f <= 1);
assert(f >= (T::min() == 0 ? 0 : -1));
}
template<typename F>
inline static void multiply(const data_type first, const F second)
{
assert(T::max() / second >= first);
assert(T::min() / second <= first);
}
template<typename F>
inline static void divide(const data_type first, const F second)
{
assert(F(first) / T::max() <= second);
assert(-F(first) / T::max() <= second);
}
};
};
/**
* This class represents a number from the interval [0, 1] (T is unsigned) or [-1, 1] (T is signed).
* It can be useful when you need high precision for such numbers.
* You can use the instances of this class like normal floating-point numbers.
* Don't forget to use \p typedefs for instances of the class-template.
* @param T - The type used to represent the number. It's typically a signed or an unsigned integer.
* @param Float - For some calclations you'll still need floating-point-numbers. This type will normally be used for internall calculations and checking. You'll typically use double, with an Integer you may lose a precision.
* @param CheckingPolicy - A class like part_policies::no_check or part_policies::assert_check. This policy can check for under- and overflows.
*/
template<typename T, typename Float = double, template<typename> class CheckingPolicy = part_policies::no_check>
class part
{
static T minimum;
public:
T data;
inline static const T max()
{
return std::numeric_limits<T>::max();
}
inline static const T min()
{
return minimum;
}
typedef part<T, Float, CheckingPolicy> self;
typedef T data_type;
typedef CheckingPolicy<self> check;
typedef Float float_type;
part() : data(0)
{
}
explicit part(const T data) : data(data)
{
}
template<typename F>
inline static self from_float(const F f)
{
check::from_float(f);
return self(f * max());
}
inline static self from_float(const Float f)
{
check::from_float(f);
return self(f * max());
}
template<typename F>
inline void set_float(const F f)
{
check::from_float(f);
data = f * max();
}
inline void set_float(const Float f)
{
check::from_float(f);
data = self(f * max());
}
inline self& operator+=(const self s)
{
check::plus(data, s.data);
data += s.data;
return *this;
}
inline self& operator-=(const self s)
{
check::minus(data, s.data);
data -= s.data;
return *this;
}
inline self& operator*=(const self s)
{
data /= (float_type(max()) / s.data);
return *this;
}
template<typename F>
inline F to_float() const
{
return F(data) / max();
}
inline Float to_float() const
{
return Float(data) / max();
}
template<typename U>
inline self& operator*=(const U t)
{
check::multiply(data, t);
data *= t;
return *this;
}
template<typename U>
inline self& operator/=(const U t)
{
check::divide(data, t);
data /= t;
return *this;
}
template<typename U>
inline self& operator+=(const U t)
{
check::from_float(t);
T tmp(t * max());
check::plus(data, tmp);
data += tmp;
return *this;
}
template<typename U>
inline self& operator-=(const U t)
{
check::from_float(t);
T tmp(t * max());
check::minus(data, tmp);
data -= tmp;
return *this;
}
};
template<typename T, typename F, template<typename> class CP>
inline bool operator==(const part<T, F, CP> first, const part<T, F, CP> second)
{
return first.data == second.data;
}
template<typename T, typename F, template<typename> class CP>
inline bool operator<(const part<T, F, CP> first, const part<T, F, CP> second)
{
return first.data < second.data;
}
template<typename T, typename F, template<typename> class CP>
inline F operator/(const part<T, F, CP> first, const part<T, F, CP> second)
{
return F(first.data) / second.data;
}
#ifndef NUMERICS_NO_IO
template<typename T, typename F, template<typename> class CP>
inline std::ostream& operator<<(std::ostream& out, const part<T, F, CP> p)
{
out << p.to_float();
return out;
}
template<typename T, typename F, template<typename> class CP>
inline std::istream& operator>>(std::istream& in, part<T, F, CP>& p)
{
F f;
in >> f;
p.set_float(f);
return in;
}
#endif
template<typename T, typename F, template<typename> class CP>
T part<T, F, CP>::minimum = std::numeric_limits<T>::is_signed ? -std::numeric_limits<T>::max() : 0;
};
#endif
Alles anzeigen
Kommentare währen mir lieb, insbesondere wie es mit impliziten Konvertierungen aussieht. Ich habe soetwas nun vorsichtshalber erst einmal ausgeschlossen. Eine automatische Konvertierung von/nach double wäre aber natürlich möglich.
Viele liebe Grüße
The User