pandorafms/extras/anytermd/libpbe/include/fixed.hh

398 lines
14 KiB
C++

// include/fixed.hh
// This file is part of libpbe; see http://svn.chezphil.org/libpbe/
// It is also offered as a candidate for inclusion in Boost, http://boost.org/
// (C) 2007 Philip Endecott
// This file is distributed under the terms of the Boost Software License v. 1.0,
// see http://www.boost.org/LICENSE_1_0.txt.
// Introduction
// ------------
// This file provides class templates 'fixed' and 'ufixed' which implement signed and
// unsigned fixed-point arithmetic. For background information about fixed-point
// arithmetic, see for example this Wikipedia article:
// http://en.wikipedia.org/wiki/Fixed-point_arithmetic. Note in particular that this
// is binary arithmetic, not binary coded decimal.
//
// The author's requirement for fixed-point arithmetic is based on these
// characteristics:
//
// - Comparing a 32-bit fixed-point value and a 32-bit single-precision
// floating-point value, the fixed-point value has about 8 bits more precision
// because it does not "waste" bits representing the exponent. In the case of
// latitude and longitude values this makes the difference between a precision of
// hundreds of metres and ones of metres.
//
// - Certain operations that directly operate on the bit patterns of values are much
// simpler to carry out on fixed-point values than on floating-point values. (For
// example, the bit interleaving required for Z-curves.)
//
// Other characteristics that are often beneficial include:
//
// - Computation is faster on systems that have no floating-point hardware, e.g.
// embedded systems.
//
// - The precision of the values is well-defined and predictable.
//
//
// Quick Start
// -----------
//
// Here's a very quick illustration of how the class can be used:
//
// #include <fixed.hpp>
//
// void test() {
// fixed<15,16> f1, f2; // Signed fixed-point values with 15 bits of integer part
// // and 16 bits of fractional part.
// f1 = 1.2345; // Conversion from floating point.
// f2 = f1 - 2; // Mixed arithmetic with integers.
// f2 = f1 / f2; // Arithmetic on fixed-point values.
// }
//
//
// Primary Objective
// -----------------
//
// The primary objective of this implementation is zero performance overhead compared
// to using integer arithmetic with manually-inserted scaling. This is because
// performance is important in many applications of fixed-point arithmetic and it
// really isn't very hard to do ad-hoc fixed-point arithmetic using integers and
// shifts. Users will therefore not be attracted to this library unless it can offer
// this feature.
//
//
// Secondary Objectives
// --------------------
//
// Secondary objectives are:
//
// - To behave in as many respects as possible in the same was as the built-in
// numeric types (integer and floating-point). The motivation for this is to give
// the user the behaviour that they are already familiar with from those types.
//
//
// Hardware Support
// ----------------
//
// The objective of this implementation is to work efficiently with conventional
// processors' integer arithmetic instructions. It is worth noting, however, that
// some processors have hardware support for fixed-point operations. These include:
//
// [Hmm, no MMX doesn't seem to have any explicit fixed-point support]
//
// - DSP processors.
// - Intel's MMX instruction set (also implemented in other x86-compatible
// processors).
// - The PowerPC's AltiVec instruction set.
// - The ARM Piccolo instruction set.
//
// Support for any of these instruction sets would require either use of
// architecture-specific assembly language or a very smart compiler. These are
// considered outside the scope of the current work; however, it is worthwhile to see
// what these instruction sets offer in order to avoid unintentionally implementing
// something that is incompatible.
//
// For example, it seems that these instruction sets could more easily support
// fixed<16,15> than fixed<15,16>. It may be that the user would be happy with
// either of these formats. So we should consider offering a way to get the "best"
// format with at least a certain number of bits of integer and fractional parts for
// a particular instruction set.
//
//
// Related Work
// ------------
//
// "Embedded C" (see ISO/IEC [draft] technical report 18037:2004) proposes fixed
// point types for C. It defines
// new keywords _Fract and _Accum which modify the built-in short, int and long
// types; _Fract makes the value entirely fractional while _Accum gives it 4
// integer bits.
//
// It requires saturating arithmetic (controlled by an _Sat keyword or a #pragma),
// and does not require wrap-around behaviour.
//
// Fixed-point constants can be written using letter suffixes, e.g. 3.1415uhk.
//
// It looks very much as if this proposal is motivated by one particular category of
// applications (i.e. digital signal processing). The requirement for saturating
// arithmetic imposes a significant overhead on processors that do not have support
// for it, and the lack of support for values with more than 4 integer bits makes it
// useless for the author's latitude/longitude application.
//
//
// Getting the High part of a multiply result
// ------------------------------------------
//
// MMX has "packed multiply high" instruction.
//
//
// Meaning of ++ and --
// --------------------
//
// It could be argued that the meaning of ++ and -- is unclear: do they add/subtract
// 1, or do they add/subtract the type's delta? (...there is an example of something
// where they do delta...). I note, however, that the built-in floating point types
// add and subtract 1, as does the proposed "Embedded C" fixed-point type.
// Therefore, ++ and -- are defined to do that for fixed and ufixed.
//
//
// Fixed Point Constants
// ---------------------
//
// It would be ideal if code such as
//
// const ufixed<2,14> pi = 3.1415926;
//
// could have no run-time overhead. To be investigated...
//
//
// Result Types
// ------------
//
//
// Free Functions
// --------------
//
// embedded C has...
// ...names are crytic due to absence of template/overloading in C.
//
//
// Overflow and Saturation
// -----------------------
//
// Users of fixed-point arithmetic sometimes want to detect overflow, or for
// operations to saturate when overflow would occur. However, users of
// floating-point and integer arithmetic also sometimes want these features, but they
// are not provided by the built-in C/C++ types. The approach taken in this
// implementation is to follow the behaviour of the built-in types, i.e. to not
// handle overflow; values will wrap-around.
//
// Hardware overhead of saturation; MMX has saturating instructions.
//
//
// Division By Zero
// ----------------
//
// compare with int and float...
//
// embedded C fixed is undefined
//
//
// Formatted input/output
// ----------------------
//
// ... implemented by conversion to floating point? ...
//
//
// 'Math library' functions
// ------------------------
//
// A set of functions similar to those provided by the standard library for integer
// and floating-point types is provided. The skeleton for these functions was
// provided by ......
//
// In the case of ....., tables of constants are required. Different tables are
// required for each fixed point type. By default, the approach taken is to compute
// these tables when the functions are first used [note that the use of static
// variables may not be thread safe]. This could impose an unacceptable delay in
// some applications; in this case, the preprocessor symbol ... should be defined,
// and the included program ... should be run once for each type to generate static
// tables. Simply include the output of this program in your build.
//
//
// bigint
// ------
//
//
// Benchmarks
// ----------
//
// The following results are based on the benchmark programs in the ... directory.
// There are:
//
// - sort: generates a std::vector of xxxx pseudo-random values and sorts them
// using std::sort. This measures comparison performance, which should be of the
// same order as addition and subtraction.
//
// - filt: generates a vector of pseudo-random values and filters them using an FIR
// (finite impulse response) filter. The filter coefficients are also generated
// pseudo-randomly. This measures primarily multiply performance.
//
// In each case the performance is measured for fixed-point, floating-point and
// integer types with manually-inserted shifts.
//
// The following platforms have been tested:
//
// - NSLU2. (See http://nslu2-linux.org/). This system has a 266 MHz Intel IXP420
// XScale ARM processor, which has no floating point hardware. The compiler used was
// g++ verson 4.1.2.
//
// - The author's desktop computer, which as a 1 GHz VIA C3 (x86-compatible)
// processor. The compiler used was also g++ version 4.1.2.
//
// Note to Boost people: I will change all the 'libpbe' identifiers to 'boost'
// when this is formally proposed for review.
#ifndef libpbe_fixed_hh
#define libpbe_fixed_hh
#include <cmath> // for ldexp*
#include "./integer.hpp" // Copy of boost::integer to which I've added 64-bit support.
// I expect this functionality to be added to the official
// version before this code is reviewed.
#include <boost/integer/static_min_max.hpp>
#include <boost/operators.hpp>
namespace pbe {
// This helper function exists mainly to avoid warnings about negative shifts.
template <int amt, typename T>
T shift(T val) {
if (amt>0) {
unsigned int u_amt = amt;
return val>>u_amt;
} else {
unsigned int u_amt = -amt;
return val<<u_amt;
}
}
template <int WB, int FB, typename VAL_T = typename boost::int_t<WB+FB+1>::least>
// WB = Whole Bits
// FB = Fraction Bits
// VAL_T = type to use for implementation
class fixed:
boost::totally_ordered<fixed<WB,FB,VAL_T> >,
boost::additive<fixed<WB,FB,VAL_T> >,
boost::bitwise<fixed<WB,FB,VAL_T> >,
boost::unit_steppable<fixed<WB,FB,VAL_T> >
{
// Question: do we want to expose the implementation type and value?
// In my application I have found it useful to do so.
//private:
public:
typedef VAL_T val_t;
val_t val;
public:
fixed() {}
fixed(const fixed& x): val(x.val) {}
template <int X_WB, int X_FB, typename X_VAL_T>
fixed(const fixed<X_WB, X_FB, X_VAL_T>& x):
val(shift<X_FB-FB>(x.val))
//(X_FB<FB)
//?(x.val<<(FB-X_FB))
//:(x.val>>(X_FB-FB)))
{}
fixed(int x): val(x<<FB) {}
fixed(float x): val(static_cast<val_t>(ldexpf(x,FB))) {}
fixed(double x): val(static_cast<val_t>(ldexp (x,FB))) {}
operator float() const { return ldexpf(static_cast<double>(val),-FB); }
operator double() const { return ldexp (static_cast<double>(val),-FB); }
bool operator<(const fixed& x) const { return val<x.val; }
bool operator==(const fixed& x) const { return val==x.val; }
fixed& operator+=(const fixed& x) { val+=x.val; return *this; }
fixed& operator-=(const fixed& x) { val-=x.val; return *this; }
fixed& operator|=(const fixed& x) { val|=x.val; return *this; }
fixed& operator&=(const fixed& x) { val&=x.val; return *this; }
fixed& operator^=(const fixed& x) { val^=x.val; return *this; }
fixed& operator++() { return (*this) += 1; } // Hmm, maybe not wanted
fixed& operator--() { return (*this) -= 1; } // ditto.
fixed operator-() { fixed f = *this; f.val = -f.val; return f; }
template <int XWB, int XFB, typename X_VAL_T>
fixed& operator*=(const fixed<XWB,XFB,X_VAL_T>& x) { (*this) = (*this) * x; return *this; }
template <int XWB, int XFB, typename X_VAL_T>
fixed& operator/=(const fixed<XWB,XFB,X_VAL_T>& x) { (*this) = (*this) / x; return *this; }
fixed& operator*=(float x) { (*this) = static_cast<float>(*this) * x; return *this; }
fixed& operator/=(float x) { (*this) = static_cast<float>(*this) / x; return *this; }
};
// I don't know how to choose the VAL_T for the results of these operations.
// I.e. if the operands use safe_int then the result should also use safe_int.
template <int XWB, int XFB, typename X_VAL_T, int YWB, int YFB, typename Y_VAL_T>
pbe::fixed<boost::static_unsigned_max<XWB,YWB>::value, boost::static_unsigned_max<XFB,YFB>::value>
operator+(pbe::fixed<XWB,XFB,X_VAL_T> x, pbe::fixed<YWB,YFB,Y_VAL_T> y) {
typedef pbe::fixed<boost::static_unsigned_max<XWB,YWB>::value, boost::static_unsigned_max<XFB,YFB>::value> result_t;
result_t x_(x);
result_t y_(y);
return x_ + y_;
}
template <int XWB, int XFB, typename X_VAL_T, int YWB, int YFB, typename Y_VAL_T>
pbe::fixed<boost::static_unsigned_max<XWB,YWB>::value, boost::static_unsigned_max<XFB,YFB>::value>
operator-(pbe::fixed<XWB,XFB,X_VAL_T> x, pbe::fixed<YWB,YFB,Y_VAL_T> y) {
typedef pbe::fixed<boost::static_unsigned_max<XWB,YWB>::value, boost::static_unsigned_max<XFB,YFB>::value> result_t;
result_t x_(x);
result_t y_(y);
return x_ - y_;
}
template <int XWB, int XFB, typename X_VAL_T, int YWB, int YFB, typename Y_VAL_T>
pbe::fixed<XWB+YWB,XFB+YFB>
operator*(pbe::fixed<XWB,XFB,X_VAL_T> x, pbe::fixed<YWB,YFB,Y_VAL_T> y) {
typedef pbe::fixed<XWB+YWB,XFB+YFB> result_t;
result_t r;
r.val = static_cast<typename result_t::val_t>(x.val) * y.val;
return r;
}
template <int XWB, int XFB, typename X_VAL_T, int YWB, int YFB, typename Y_VAL_T>
pbe::fixed<XWB+YFB,XFB+YWB>
operator/(pbe::fixed<XWB,XFB,X_VAL_T> x, pbe::fixed<YWB,YFB,Y_VAL_T> y) {
pbe::fixed<XWB+YFB,XFB+YWB> r;
r.val = x.val;
r.val <<= (YWB+YFB);
r.val /= y.val;
return r;
}
template <int XWB, int XFB, typename X_VAL_T>
float operator*(pbe::fixed<XWB,XFB,X_VAL_T> x, float y) {
// fixed * float is done using floating-point. Right choice?
return static_cast<float>(x) * y;
}
template <int XWB, int XFB, typename X_VAL_T>
float operator/(pbe::fixed<XWB,XFB,X_VAL_T> x, float y) {
// Ditto.
return static_cast<float>(x) / y;
}
// Also planned: an unsigned version.
};
#endif