4

I'm currently implementing a compile-time 3d raster though template metaprogramming.

After implementing the algebraic basics (2d/3d/4d vectors, 3x3/4x4 matrices arithmetic, aabb2d/3d for culling purposes, etc), I noted that integer arithmetic is not good enough for vector transformations. So I started to write a fixed-point implementation:

The library has a base header with declarations of common metafunctions that the algebraic types will implement (To provide an uniform interface). Here is the set of definitions used by the fixed-point implementation:

template<typename T>
struct zero; //Gets the zero value of a type of data. For example, zero<std::integral_constant<int>> returns std::integral_constant<int,0>

template<typename T>
struct one;

/* Algebraic operations: */

template<typename LHS , typename RHS>
struct add;

template<typename LHS , typename RHS>
struct sub;

template<typename LHS , typename RHS>
struct mul;

I decided to use decimal-based fixed-point instead of binary-based, because it allows me to write easilly a decimal-number interface (The decimal alias provided bellow). Here is the implementation of a power metafunction and decimal digit shifting:

template<int base , int exponent>
struct positive_pow : public std::integral_constant<long long int , base * positive_pow<base,exponent-1>::value> {};

template<int base>
struct positive_pow<base,0> : public std::integral_constant<long long int,1> {};

template<int number , int shift>
struct decimal_leftshift : public std::integral_constant<long long int,number * positive_pow<10, shift>::value> {};

template<int number , int shift>
struct decimal_rightshift : public std::integral_constant<long long int,number / positive_pow<10, shift>::value> {};

template<bool CONDITION , int NUMBER , int SHIFT>
struct decimal_shift_chooser
{
    using shifter = decimal_leftshift<NUMBER,SHIFT>;
};

template<int NUMBER , int SHIFT>
struct decimal_shift_chooser<false,NUMBER,SHIFT>
{
    using shifter = decimal_rightshift<NUMBER,-SHIFT>;
};

//This metafunction shifts to one direction or other depending on the sign of the shift count passed:
template<int number , int shift>
struct decimal_shift
{
    using shifter = typename decimal_shift_chooser<( shift >= 0 ) , number , shift>::shifter;
    static const long long int value = shifter::value;
};  

Here is the implementation of the fixed-point type. The internal implementation uses a long long to store the number digits. So I have 64bits, thats is, 19 decimal digits aproximately:

using fpbits = long long int;
using fdcount = unsigned int; //Fractional decimal digits count (Precision)

const fdcount DEFAULT_FRACTIONAL_PRECISION = 8; 

template<fpbits BITS , fdcount PRECISION = DEFAULT_FRACTIONAL_PRECISION>
struct fixed_point
{
    operator float()
    {
        return (float)BITS * std::pow(10.0f,-(float)PRECISION);
    };
};

//An alias to define decimal numbers with default precision:
template<int mantissa , int exponent = 0> // MANTISSA x 10^EXPONENT
using decimal = fixed_point<decimal_shift<mantissa , DEFAULT_FRACTIONAL_PRECISION + exponent>::value>; 

/* Previously defined common metafunctions implementation */

template<fpbits BITS , fdcount PRECISION>
struct zero<fixed_point<BITS,PRECISION>> : public fixed_point<0,PRECISION> {};

template<fpbits BITS , fdcount PRECISION>
struct one<fixed_point<BITS,PRECISION>> : public fixed_point<decimal_leftshift<1,PRECISION>::value,PRECISION> {};


template<fpbits BITS1 , fdbits BITS2 , fbcount PRECISION>
struct add<fixed_point<BITS1,PRECISION> , fixed_point<BITS2,PRECISION>> : public fixed_point<BITS1+BITS2 , PRECISION> {};

template<fpbits BITS1 , fdbits BITS2 , fbcount PRECISION>
struct sub<fixed_point<BITS1,PRECISION> , fixed_point<BITS2,PRECISION>> : public fixed_point<BITS1-BITS2 , PRECISION> {};

template<fpbits BITS1 , fdbits BITS2 , fbcount PRECISION>
struct mul<fixed_point<BITS1,PRECISION> , fixed_point<BITS2,PRECISION>> : public fixed_point<decimal_rightshift<BITS1*BITS2,PRECISION>::value , PRECISION> {};

As I pointed out, the implementation has 19 decimal digits. So if we use 8 decimal digits precision and we multiply pi by two, the result can be represented, right? Like in this example:

using pi = decimal<3141592 , -6>; //3141592 x 10^-6 (3,141592) This fits in our 8 precision implementation.
using pi_2 = mul<pi,decimal<2>>; //pi*2 is 314159200 * 200000000 = 62831840000000000 >> 8. 
                                 //The inmediate result of the product fits in a 
                                 //long long (Has 17 decimal digits), so no problem?

int main()
{
    std::cout << "pi: " << pi() << std::endl;
    std::cout << "2*pi: " << pi_2() << std::endl;
}

But this prints:

pi: 3,14159
pi*2: -1e-07

ご覧のとおり、結果はごくわずかな負の数なので、計算のどこかに符号付き整数のオーバーフローがあると思います。

問題はどこだ?これは整数のオーバーフローですか?もしそうなら、どこでどのように修正できますか?

完全な実行例を次に示します

4

2 に答える 2

4

パラメータdecimal_rightshiftintあり、それに渡しますBITS1*BIST2intメタプログラム内のeveryを に置き換えるとlong long int、すべてが機能するはずです。

于 2013-08-03T14:32:07.120 に答える
1

ところで、あなたoneは間違っています。正しい実装は次のとおりです。

template<fpbits BITS , fbcount PRECISION>
struct one<fixed_point<BITS,PRECISION>> : public fixed_point<decimal_leftshift<1, PRECISION>::value, PRECISION> {};

つまり、想定oneは実際には値 1 であるはずです。

于 2013-08-03T14:57:17.707 に答える