|
template<typename T > |
constexpr T | FixMathPrivate::shiftR (T x, int8_t bits) |
|
constexpr int8_t | FixMathPrivate::sBitsToBytes (int8_t N) |
|
constexpr int8_t | FixMathPrivate::uBitsToBytes (int8_t N) |
|
template<typename T > |
constexpr T | FixMathPrivate::FM_max (T N1, T N2) |
|
template<typename T > |
constexpr T | FixMathPrivate::FM_min (T N1, T N2) |
|
constexpr uint64_t | FixMathPrivate::sFullRange (int8_t N) |
|
constexpr uint64_t | FixMathPrivate::uFullRange (int8_t N) |
|
constexpr uint64_t | FixMathPrivate::rangeAdd (byte NF, byte _NF, uint64_t RANGE, uint64_t _RANGE) |
|
constexpr uint64_t | FixMathPrivate::rangeShift (int8_t N, int8_t SH, uint64_t RANGE) |
|
template<uint64_t value> |
constexpr int8_t | FixMathPrivate::NIcount () |
|
template<typename T > |
constexpr UFix< 0, sizeof(T) *8 > | toUFraction (T val) |
|
template<typename T > |
constexpr UFix< sizeof(T) *8, 0 > | toUInt (T val) |
|
template<uint64_t value> |
constexpr FixMathPrivate::UFixByRange_t< 0, value > | UFixAuto () |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator> (const SFix< NI, NF > &op1, const UFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator> (const UFix< NI, NF > &op1, const SFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator< (const UFix< NI, NF > &op1, const SFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator< (const SFix< NI, NF > &op1, const UFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator== (const SFix< NI, NF > &op1, const UFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator== (const UFix< NI, NF > &op1, const SFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator!= (const SFix< NI, NF > &op1, const UFix< _NI, _NF > &op2) |
|
template<int8_t NI, int8_t NF, int8_t _NI, int8_t _NF> |
constexpr bool | operator!= (const UFix< NI, NF > &op1, const SFix< _NI, _NF > &op2) |
|
template<typename T > |
constexpr SFix< 0, sizeof(T) *8-1 > | toSFraction (T val) |
|
template<typename T > |
constexpr SFix< sizeof(T) *8-1, 0 > | toSInt (T val) |
|
This file implements two fixed point number classes. These numbers can have a fractional part but are actually standard integers under the hood which makes calculations with them efficient on platforms which do not have a FPU like most micro-controllers. These numbers can be signed (SFix) or unsigned (UFix).
A fixed point number has its range defined by the number of bits encoding the integer part (NI in the following) and its precision by the number of bits encoding the fractional part (NF). For UFix types, the integral part can hold values in [0,2^NI-1], for SFix types, the integral part can hold values in [-2^NI,2^NI-1]. The number of bits encoding the fractional can be considered as the precision of the number: given NF, the number of possible values in the [0,1[ range will 2^NF. Hence, given NF, the resolution will be 1/(2^NF).
Under the hood, these types will keep track of the maximum possible value they might hold (this is the RANGE template parameter), and, if only SAFE operations (see below) are used, will automatically adjust there NI and NF to accomodate the result of a operation. It will also try not to promote there internal type when possible, assuFM_ming that you use the complete range of a given type.
The operations possible with these types can be divided into two categories:
- the operations between FixMath types are all safe (aka won't overflow) and are the only one included by default
- the operations between a FixMath and a native C type (int, float) are NOT safe and are not included by default. In order to activate them, you need to
#define FIXMATH_UNSAFE
before including FixMath.h.
Like standard C(++) types, the fixed point numbers defined here are following some rules:
- any fixed type can be converted to another as long as the value can be represented in the destination type. Casting to a bigger type in term of NI and NF is safe, but reducing NI can lead to an overflow if the new type cannot hold the integer value and reducing NF leads to a loss of precision.
- Fixed types can be constructed from and converted to standard C types:
UFix<NI,NF>(T value)
will convert the value
to a UFix
. If T is an integer type the final number will have a fractional part equal to zero. This can be used as a standard type, for example: UFix<8,8> a = 15;
or UFix<8,8> b = 200.25;
- same for
SFix
UFix<NI,NF>::fromRaw(T value)
will set the internal value of the UFix
. For example UFix<7,1>::fromRaw(16);
is actually 8
- same for
SFix
UFix<NI,NF>.toFloat()
returns the value as a float
- same for
SFix
UFix<NI,NF>.asRaw()
returns the internal value
- same for
SFix
- all operations between fixed point number is safe (it won't overflow) and preserve the precision. In particular:
- only addition, subtraction and multiplication are implemented (this is a design choice, see below)
- any operation between a signed and an unsigned leads to a signed number
- resulting numbers will be casted to a type big enough to store the expected values. It follows that it is worth starting with types that are as small as possible to hold the initial value.
- all operations between a fixed point number and a native type (int, float, uint) are not safe. If the resulting value cannot be represented in the fixed point type it will overflow. Only addition, subtraction, multiplication and right/left shift are implemented. These are only accessible activating the
FIXMATH_UNSAFE
set.
- safe right/left shifts, which return the correct value in the correct type are implemented as .sR<shift>() and .sL<shift>() respectively, shift being the shifting amount.
More specifically on the returned types of the operations between fixed point math types:
- Additions:
UFix<NI,NF> + UFix<_NI,_NF>
returns UFix<MAX(NI,_NI)+1,MAX(NF,_NF)>
at worse
SFix<NI,NF> + SFix<_NI,_NF>
returns SFix<MAX(NI,_NI)+1,MAX(NF,_NF)>
at worse
UFix<NI,NF> + SFix<_NI,_NF>
returns SFix<MAX(NI,_NI)+1,MAX(NF,_NF)>
at worse
UFix<NI,NF> + anything_else
(signed or not) returns UFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
SFix<NI,NF> + anything_else
(signed or not) returns SFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
- Subtractions:
UFix<NI,NF> - UFix<_NI,_NF>
returns SFix<MAX(NI,_NI),MAX(NF,_NF)>
at worse
SFix<NI,NF> - SFix<_NI,_NF>
returns SFix<MAX(NI,_NI)+1,MAX(NF,_NF)>
at worse
SFix<NI,NF> - UFix<_NI,_NF>
returns SFix<MAX(NI,_NI)+1,MAX(NF,_NF)>
at worse
UFix<NI,NF> - anything_else
(signed or not) returns UFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
SFix<NI,NF> - anything_else
(signed or not) returns SFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
(-)SFix<NI,NF>
return SFix<NI,NF>
(-)UFix<NI,NF>
return SFix<NI,NF>
- Multiplications:
UFix<NI,NF> * UFix<_NI,_NF>
returns UFix<NI+_NI,NF+_NF>
at worse
UFix<NI,NF> * SFix<_NI,_NF>
returns SFix<NI+_NI,NF+_NF>
at worse
SFix<NI,NF> * SFix<_NI,_NF>
returns SFix<NI+_NI,NF+_NF>
at worse
UFix<NI,NF> * anything_else
(signed or not) returns UFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
SFix<NI,NF> * anything_else
(signed or not) returns SFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
- Shifts:
UFix<NI,NF> .sR<NS>
returns UFix<NI-NS,NF+NS>
UFix<NI,NF> .sL<NS>
returns UFix<NI+NS,NF-NS>
- same for
SFix
UFix<NI,NF> >> N
returns UFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
UFix<NI,NF> << N
returns UFix<NI,NF>
(only available with FIXMATH_UNSAFE
)
- same for
SFix
- Inverse:
- Approximates:
UFix<NI,NF>.invFast()
returns the approximate inverse of the number as UFix<NF,NI>
SFix<NI,NF>.invFast()
returns the approximate inverse of the number as SFix<NF,NI>
UFix<NI,NF>.invFull()
returns the approximate inverse of the number as UFix<NF,2*NI+NF>
SFix<NI,NF>.invFull()
returns the approximate inverse of the number as SFix<NF,2*NI+NF>
UFix<NI,NF>.inv<_NF>()
returns the approximate inverse of the number as UFix<NF,_NF>
SFix<NI,NF>.inv<_NF>()
returns the approximate inverse of the number as SFix<NF,_NF>
- Exact: (when the result can be exactly represented in the destination type)
UFix<NI,NF>.invAccurate()
returns the inverse as UFix<NF,2*NI+NF-1>
SFix<NI,NF>.invAccurate()
returns the inverse as SFix<NF,2*NI+NF-1>
UFix<NI,NF>.invAccurate<_NF>()
returns the inverse as UFix<NF,_NF>
(uses NF+_NF+1 bits internally)
SFix<NI,NF>.invAccurate()<_NF>
returns the inverse as SFix<NF,_NF>
- Conversion (should be preferred over casting, when possible):
UFix<NI,NF>.asSFix()
returns SFix<NI,NF>
SFix<NI,NF>.asUFix()
returns UFix<NI,NF>
UFix<NI,NF>.asFloat()
returns the value as a float
SFix<NI,NF>.asFloat()
returns the value as a float
UFix<NI,NF>.asRaw()
returns the internal value
SFix<NI,NF>.asRaw()
returns the internal value
T.toUFraction()
returns UFix<0,NF>
with NF
the number of bits of T
(uint8_t
leads to NF=8
bits).
T.toSFraction()
returns SFix<0,NF>
with NF
the number of bits of T
(int8_t
leads to NF=7
bits).
T.toUInt()
returns UFix<NI,0>
with NI
the number of bits of T
(uint8_t
leads to NI=8
bits).
T.toSInt()
returns SFix<NI,>
with NI
the number of bits of T
(int8_t
leads to NI=7
bits).
Note on division: The division is not implemented. This is a deliberate choice made for two reasons:
- in contrast with all the other fundamental operations, it is not possible to guarantee that precision will be kept (other operations returns exact results whenever the operands were also exactly represented. Note that this is actually not the case when using normal floating point numbers. The inverse functions can be used to fake a division, by multiplying by the inverse of a number.
- division are usually very slow operations on MCU, hence there usage is discouraged. The ideal way of doing it is to compute the inverse whenever needed and only when needed. In the context of Mozzi for instance, a good way to do it would be to compute needed inverses in
updateControl()
, and use them in updateAudio()
.