[eigen] Unexpected result type deduction |
[ Thread Index |
Date Index
| More lists.tuxfamily.org/eigen Archives
]
Hi,
while implementing my own fixed point class, I ran into some problems
with Eigen. Not sure whether my code misses to extend Eigen some more to
work correctly or if it is a real bug.
Here is the output from my minimal example code (see attachment),
compiled with revision 10512:c06cfd545b15:
#############
g++ -std=c++11 -g -lm -I/home/mj/opt/include/ -I../ test_eigen.cpp -o
test_eigen
../test_eigen
Input data:
0.09999999962747097 (0xccccccc) Fixed<31>
0.09999999962747097 (0xccccccc) Fixed<31>
0.99989999970421195 (0x7ffcb923) Fixed<31>
0.99989999970421195 (0x7ffcb923) Fixed<31>
Lets perform a single addition, this should yield a Fixed<30>:
1.9997999994084239 (0x7ffcb923) Fixed<30>
Perform the same operation, but colWise (expecting Fixed<30> as type,
like above):
going from: 30bit fraction length to: 31bit. This should not be called.
going from: 30bit fraction length to: 31bit. This should not be called.
0.19999999925494194 (0x19999998) Fixed<31>
-1 (0x80000000) Fixed<31>
This is wrong :(. Expected something close to 2 and got -1. Honoring the
output format is important.
#############
Basically, operator+(Fixed<F> a, Fixed<F>) will return a Fixed<F-1>,
this way, no overflows can happen and it only drops the LSB.
Somehow, the .col(0)+.col(1) expression, forces the result type to be
the same as the parameters type (that is why I cannot put 'explicit'
back into the constructor which can convert between different fraction
lengths, the whole code will not compile then at all :|).
I would expect here, that the result type is actually deducted from the
result type of the operator (with given arguments).
Also we can tell from the output, that the operator+ was successfully
used, but the result got casted to the wrong type.
If someone can shed light on this issue regarding if it's my fault due
to missing glue code, or a real bug, I would be really grateful :).
Thanks,
imp
#include <cstddef>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cmath>
#include <eigen3/Eigen/Dense>
// 32bit fixedpoint class
template <size_t F> class Fixed {
private:
static const int64_t one = (static_cast<int64_t>(1)<<(F));
int32_t value;
public: // getter&setter
int32_t getRawValue() const {
return value;
};
void setRawValue(int64_t x) {
value = x;
};
public: // constructors
explicit Fixed() : value(0) { };
explicit Fixed(double x) {
value = one * x ;
};
template<size_t R> /* explicit */ Fixed(Fixed<R> const &rhs){ // TODO make explicit again.
std::cout << "going from: " << R << "bit fraction length to: " << F << "bit. This should not be called." << std::endl;
value = (one * static_cast<double>(rhs)); // temp fix
};
public: // type conversions
explicit operator double() const {
return (static_cast<double>(value)) / (static_cast<double>(one));
};
};
// output functions
template <size_t F> std::ostream &operator<<(std::ostream &os, const Fixed<F> &f) {
os << std::setprecision(17) << static_cast<double>(f) << " (0x" <<std::hex << f.getRawValue() << std::dec << ") Fixed<" << F << ">";
return os;
};
// math functions
template <size_t F_in_left, size_t F_in_right, size_t F_out = (((F_in_left >= F_in_right)?F_in_right:F_in_left)-1)> Fixed<F_out> operator+(const Fixed<F_in_left> a, const Fixed<F_in_right> b) {
// An addition can yield a new MSB, thus we drop the lowest LSB.
// This way, it cannot overflow, nor do we need to check for saturation.
Fixed<F_out> ret;
ret.setRawValue((((static_cast<int64_t>(a.getRawValue())<<((F_in_left >= F_in_right)?0:F_in_right-F_in_left)) + (static_cast<int64_t>(b.getRawValue())<<((F_in_right >= F_in_left)?0:F_in_left-F_in_right))))>>((F_in_left >= F_in_right?F_in_left-F_in_right:F_in_right-F_in_left)+1));
return ret;
};
// Following https://eigen.tuxfamily.org/dox/TopicCustomizing_CustomScalar.html .
namespace Eigen {
template<size_t F> struct NumTraits<Fixed<F>> : GenericNumTraits<Fixed<F>>
{
typedef Fixed<F> Real;
typedef Fixed<F> NonInteger;
typedef Fixed<F> Literal;
typedef Fixed<F> Nested;
static inline Real epsilon() { return Fixed<F>(0); }
static inline Real dummy_precision() { return Fixed<F>(0); }
static inline int digits10() { return (std::log(2.0)/std::log(10.0)*((32>F)?(32-F-1):0)); } // TODO return value was Real, but the value range doesn't always work, so it's int for now.
static inline Real highest() { return Fixed<F>(0xffffffff); }
static inline Real lowest() { return Fixed<F>(-0xffffffff); }
enum {
IsInteger = 0,
IsSigned = 1,
IsComplex = 0,
RequireInitialization = 1,
ReadCost = 3, // The costs are guessed for now.
AddCost = 20,
MulCost = 50
};
};
};
using T = Fixed<31>;
using cppfixT_t = Eigen::Array<T, 2, 2>;
int main() {
cppfixT_t data;
data << T(0.1), T(0.1),
T(0.9999), T(0.9999); // This row will cause an overflow if we cast the sum later.
std::cout << "Input data:" << std::endl << data << std::endl << std::endl;
std::cout << "Lets perform a single addition, this should yield a Fixed<30>:" << std::endl;
std::cout << data(1, 0)+data(1, 1) << std::endl << std::endl;
std::cout << "Perform the same operation, but colWise (expecting Fixed<30> as type, like above):" << std::endl;
std::cout << data.col(0)+data.col(1) << std::endl;
std::cout << "This is wrong :(. Expected something close to 2 and got -1. Honoring the output format is important." << std::endl;
return 0;
}