代码之家  ›  专栏  ›  技术社区  ›  Paige Ruten

做定点数学最好的方法是什么?[关闭]

  •  44
  • Paige Ruten  · 技术社区  · 16 年前

    我需要加速一个程序为任天堂DS没有一个FPU,所以我需要改变浮点数学(这是模拟和缓慢的)为定点。

    x>8 将定点变量x转换为实际数,然后 x<<8

    9 回复  |  直到 16 年前
        1
  •  51
  •   Evan Teran    7 年前

    你可以试试我的定点课程(最新推出@ https://github.com/eteran/cpp-utilities )

    // From: https://github.com/eteran/cpp-utilities/edit/master/Fixed.h
    // See also: http://stackoverflow.com/questions/79677/whats-the-best-way-to-do-fixed-point-math
    /*
     * The MIT License (MIT)
     * 
     * Copyright (c) 2015 Evan Teran
     * 
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     * 
     * The above copyright notice and this permission notice shall be included in all
     * copies or substantial portions of the Software.
     * 
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */
    
    #ifndef FIXED_H_
    #define FIXED_H_
    
    #include <ostream>
    #include <exception>
    #include <cstddef> // for size_t
    #include <cstdint>
    #include <type_traits>
    
    #include <boost/operators.hpp>
    
    namespace numeric {
    
    template <size_t I, size_t F>
    class Fixed;
    
    namespace detail {
    
    // helper templates to make magic with types :)
    // these allow us to determine resonable types from
    // a desired size, they also let us infer the next largest type
    // from a type which is nice for the division op
    template <size_t T>
    struct type_from_size {
        static const bool is_specialized = false;
        typedef void      value_type;
    };
    
    #if defined(__GNUC__) && defined(__x86_64__)
    template <>
    struct type_from_size<128> {
        static const bool           is_specialized = true;
        static const size_t         size = 128;
        typedef __int128            value_type;
        typedef unsigned __int128   unsigned_type;
        typedef __int128            signed_type;
        typedef type_from_size<256> next_size;
    };
    #endif
    
    template <>
    struct type_from_size<64> {
        static const bool           is_specialized = true;
        static const size_t         size = 64;
        typedef int64_t             value_type;
        typedef uint64_t            unsigned_type;
        typedef int64_t             signed_type;
        typedef type_from_size<128> next_size;
    };
    
    template <>
    struct type_from_size<32> {
        static const bool          is_specialized = true;
        static const size_t        size = 32;
        typedef int32_t            value_type;
        typedef uint32_t           unsigned_type;
        typedef int32_t            signed_type;
        typedef type_from_size<64> next_size;
    };
    
    template <>
    struct type_from_size<16> {
        static const bool          is_specialized = true;
        static const size_t        size = 16;
        typedef int16_t            value_type;
        typedef uint16_t           unsigned_type;
        typedef int16_t            signed_type;
        typedef type_from_size<32> next_size;
    };
    
    template <>
    struct type_from_size<8> {
        static const bool          is_specialized = true;
        static const size_t        size = 8;
        typedef int8_t             value_type;
        typedef uint8_t            unsigned_type;
        typedef int8_t             signed_type;
        typedef type_from_size<16> next_size;
    };
    
    // this is to assist in adding support for non-native base
    // types (for adding big-int support), this should be fine
    // unless your bit-int class doesn't nicely support casting
    template <class B, class N>
    B next_to_base(const N& rhs) {
        return static_cast<B>(rhs);
    }
    
    struct divide_by_zero : std::exception {
    };
    
    template <size_t I, size_t F>
    Fixed<I,F> divide(const Fixed<I,F> &numerator, const Fixed<I,F> &denominator, Fixed<I,F> &remainder, typename std::enable_if<type_from_size<I+F>::next_size::is_specialized>::type* = 0) {
    
        typedef typename Fixed<I,F>::next_type next_type;
        typedef typename Fixed<I,F>::base_type base_type;
        static const size_t fractional_bits = Fixed<I,F>::fractional_bits;
    
        next_type t(numerator.to_raw());
        t <<= fractional_bits;
    
        Fixed<I,F> quotient;
    
        quotient  = Fixed<I,F>::from_base(next_to_base<base_type>(t / denominator.to_raw()));
        remainder = Fixed<I,F>::from_base(next_to_base<base_type>(t % denominator.to_raw()));
    
        return quotient;
    }
    
    template <size_t I, size_t F>
    Fixed<I,F> divide(Fixed<I,F> numerator, Fixed<I,F> denominator, Fixed<I,F> &remainder, typename std::enable_if<!type_from_size<I+F>::next_size::is_specialized>::type* = 0) {
    
        // NOTE(eteran): division is broken for large types :-(
        // especially when dealing with negative quantities
    
        typedef typename Fixed<I,F>::base_type     base_type;
        typedef typename Fixed<I,F>::unsigned_type unsigned_type;
    
        static const int bits = Fixed<I,F>::total_bits;
    
        if(denominator == 0) {
            throw divide_by_zero();
        } else {
    
            int sign = 0;
    
            Fixed<I,F> quotient;
    
            if(numerator < 0) {
                sign ^= 1;
                numerator = -numerator;
            }
    
            if(denominator < 0) {
                sign ^= 1;
                denominator = -denominator;
            }
    
                base_type n      = numerator.to_raw();
                base_type d      = denominator.to_raw();
                base_type x      = 1;
                base_type answer = 0;
    
                // egyptian division algorithm
                while((n >= d) && (((d >> (bits - 1)) & 1) == 0)) {
                    x <<= 1;
                    d <<= 1;
                }
    
                while(x != 0) {
                    if(n >= d) {
                        n      -= d;
                        answer += x;
                    }
    
                    x >>= 1;
                    d >>= 1;
                }
    
                unsigned_type l1 = n;
                unsigned_type l2 = denominator.to_raw();
    
                // calculate the lower bits (needs to be unsigned)
                // unfortunately for many fractions this overflows the type still :-/
                const unsigned_type lo = (static_cast<unsigned_type>(n) << F) / denominator.to_raw();
    
                quotient  = Fixed<I,F>::from_base((answer << F) | lo);
                remainder = n;
    
            if(sign) {
                quotient = -quotient;
            }
    
            return quotient;
        }
    }
    
    // this is the usual implementation of multiplication
    template <size_t I, size_t F>
    void multiply(const Fixed<I,F> &lhs, const Fixed<I,F> &rhs, Fixed<I,F> &result, typename std::enable_if<type_from_size<I+F>::next_size::is_specialized>::type* = 0) {
    
        typedef typename Fixed<I,F>::next_type next_type;
        typedef typename Fixed<I,F>::base_type base_type;
    
        static const size_t fractional_bits = Fixed<I,F>::fractional_bits;
    
        next_type t(static_cast<next_type>(lhs.to_raw()) * static_cast<next_type>(rhs.to_raw()));
        t >>= fractional_bits;
        result = Fixed<I,F>::from_base(next_to_base<base_type>(t));
    }
    
    // this is the fall back version we use when we don't have a next size
    // it is slightly slower, but is more robust since it doesn't
    // require and upgraded type
    template <size_t I, size_t F>
    void multiply(const Fixed<I,F> &lhs, const Fixed<I,F> &rhs, Fixed<I,F> &result, typename std::enable_if<!type_from_size<I+F>::next_size::is_specialized>::type* = 0) {
    
        typedef typename Fixed<I,F>::base_type base_type;
    
        static const size_t fractional_bits = Fixed<I,F>::fractional_bits;
        static const size_t integer_mask    = Fixed<I,F>::integer_mask;
        static const size_t fractional_mask = Fixed<I,F>::fractional_mask;
    
        // more costly but doesn't need a larger type
        const base_type a_hi = (lhs.to_raw() & integer_mask) >> fractional_bits;
        const base_type b_hi = (rhs.to_raw() & integer_mask) >> fractional_bits;
        const base_type a_lo = (lhs.to_raw() & fractional_mask);
        const base_type b_lo = (rhs.to_raw() & fractional_mask);
    
        const base_type x1 = a_hi * b_hi;
        const base_type x2 = a_hi * b_lo;
        const base_type x3 = a_lo * b_hi;
        const base_type x4 = a_lo * b_lo;
    
        result = Fixed<I,F>::from_base((x1 << fractional_bits) + (x3 + x2) + (x4 >> fractional_bits));
    
    }
    }
    
    /*
     * inheriting from boost::operators enables us to be a drop in replacement for base types
     * without having to specify all the different versions of operators manually
     */
    template <size_t I, size_t F>
    class Fixed : boost::operators<Fixed<I,F>> {
        static_assert(detail::type_from_size<I + F>::is_specialized, "invalid combination of sizes");
    
    public:
        static const size_t fractional_bits = F;
        static const size_t integer_bits    = I;
        static const size_t total_bits      = I + F;
    
        typedef detail::type_from_size<total_bits>             base_type_info;
    
        typedef typename base_type_info::value_type            base_type;
        typedef typename base_type_info::next_size::value_type next_type;
        typedef typename base_type_info::unsigned_type         unsigned_type;
    
    public:
        static const size_t base_size          = base_type_info::size;
        static const base_type fractional_mask = ~((~base_type(0)) << fractional_bits);
        static const base_type integer_mask    = ~fractional_mask;
    
    public:
        static const base_type one = base_type(1) << fractional_bits;
    
    public: // constructors
        Fixed() : data_(0) {
        }
    
        Fixed(long n) : data_(base_type(n) << fractional_bits) {
            // TODO(eteran): assert in range!
        }
    
        Fixed(unsigned long n) : data_(base_type(n) << fractional_bits) {
            // TODO(eteran): assert in range!
        }
    
        Fixed(int n) : data_(base_type(n) << fractional_bits) {
            // TODO(eteran): assert in range!
        }
    
        Fixed(unsigned int n) : data_(base_type(n) << fractional_bits) {
            // TODO(eteran): assert in range!
        }
    
        Fixed(float n) : data_(static_cast<base_type>(n * one)) {
            // TODO(eteran): assert in range!
        }
    
        Fixed(double n) : data_(static_cast<base_type>(n * one))  {
            // TODO(eteran): assert in range!
        }
    
        Fixed(const Fixed &o) : data_(o.data_) {
        }
    
        Fixed& operator=(const Fixed &o) {
            data_ = o.data_;
            return *this;
        }
    
    private:
        // this makes it simpler to create a fixed point object from
        // a native type without scaling
        // use "Fixed::from_base" in order to perform this.
        struct NoScale {};
    
        Fixed(base_type n, const NoScale &) : data_(n) {
        }
    
    public:
        static Fixed from_base(base_type n) {
            return Fixed(n, NoScale());
        }
    
    public: // comparison operators
        bool operator==(const Fixed &o) const {
            return data_ == o.data_;
        }
    
        bool operator<(const Fixed &o) const {
            return data_ < o.data_;
        }
    
    public: // unary operators
        bool operator!() const {
            return !data_;
        }
    
        Fixed operator~() const {
            Fixed t(*this);
            t.data_ = ~t.data_;
            return t;
        }
    
        Fixed operator-() const {
            Fixed t(*this);
            t.data_ = -t.data_;
            return t;
        }
    
        Fixed operator+() const {
            return *this;
        }
    
        Fixed& operator++() {
            data_ += one;
            return *this;
        }
    
        Fixed& operator--() {
            data_ -= one;
            return *this;
        }
    
    public: // basic math operators
        Fixed& operator+=(const Fixed &n) {
            data_ += n.data_;
            return *this;
        }
    
        Fixed& operator-=(const Fixed &n) {
            data_ -= n.data_;
            return *this;
        }
    
        Fixed& operator&=(const Fixed &n) {
            data_ &= n.data_;
            return *this;
        }
    
        Fixed& operator|=(const Fixed &n) {
            data_ |= n.data_;
            return *this;
        }
    
        Fixed& operator^=(const Fixed &n) {
            data_ ^= n.data_;
            return *this;
        }
    
        Fixed& operator*=(const Fixed &n) {
            detail::multiply(*this, n, *this);
            return *this;
        }
    
        Fixed& operator/=(const Fixed &n) {
            Fixed temp;
            *this = detail::divide(*this, n, temp);
            return *this;
        }
    
        Fixed& operator>>=(const Fixed &n) {
            data_ >>= n.to_int();
            return *this;
        }
    
        Fixed& operator<<=(const Fixed &n) {
            data_ <<= n.to_int();
            return *this;
        }
    
    public: // conversion to basic types
        int to_int() const {
            return (data_ & integer_mask) >> fractional_bits;
        }
    
        unsigned int to_uint() const {
            return (data_ & integer_mask) >> fractional_bits;
        }
    
        float to_float() const {
            return static_cast<float>(data_) / Fixed::one;
        }
    
        double to_double() const        {
            return static_cast<double>(data_) / Fixed::one;
        }
    
        base_type to_raw() const {
            return data_;
        }
    
    public:
        void swap(Fixed &rhs) {
            using std::swap;
            swap(data_, rhs.data_);
        }
    
    public:
        base_type data_;
    };
    
    // if we have the same fractional portion, but differing integer portions, we trivially upgrade the smaller type
    template <size_t I1, size_t I2, size_t F>
    typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator+(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {
    
        typedef typename std::conditional<
            I1 >= I2,
            Fixed<I1,F>,
            Fixed<I2,F>
        >::type T;
    
        const T l = T::from_base(lhs.to_raw());
        const T r = T::from_base(rhs.to_raw());
        return l + r;
    }
    
    template <size_t I1, size_t I2, size_t F>
    typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator-(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {
    
        typedef typename std::conditional<
            I1 >= I2,
            Fixed<I1,F>,
            Fixed<I2,F>
        >::type T;
    
        const T l = T::from_base(lhs.to_raw());
        const T r = T::from_base(rhs.to_raw());
        return l - r;
    }
    
    template <size_t I1, size_t I2, size_t F>
    typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator*(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {
    
        typedef typename std::conditional<
            I1 >= I2,
            Fixed<I1,F>,
            Fixed<I2,F>
        >::type T;
    
        const T l = T::from_base(lhs.to_raw());
        const T r = T::from_base(rhs.to_raw());
        return l * r;
    }
    
    template <size_t I1, size_t I2, size_t F>
    typename std::conditional<I1 >= I2, Fixed<I1,F>, Fixed<I2,F>>::type operator/(const Fixed<I1,F> &lhs, const Fixed<I2,F> &rhs) {
    
        typedef typename std::conditional<
            I1 >= I2,
            Fixed<I1,F>,
            Fixed<I2,F>
        >::type T;
    
        const T l = T::from_base(lhs.to_raw());
        const T r = T::from_base(rhs.to_raw());
        return l / r;
    }
    
    template <size_t I, size_t F>
    std::ostream &operator<<(std::ostream &os, const Fixed<I,F> &f) {
        os << f.to_double();
        return os;
    }
    
    template <size_t I, size_t F>
    const size_t Fixed<I,F>::fractional_bits;
    
    template <size_t I, size_t F>
    const size_t Fixed<I,F>::integer_bits;
    
    template <size_t I, size_t F>
    const size_t Fixed<I,F>::total_bits;
    
    }
    
    #endif
    

    它被设计成一个接近下降的替代浮动/双打和有一个可选择的精度。它确实使用boost来添加所有必需的数学运算符重载,所以您也需要它(我相信对于这一点,它只是一个头依赖项,而不是库依赖项)。

    顺便说一句,常见用法如下:

    using namespace numeric;
    typedef Fixed<16, 16> fixed;
    fixed f;
    

    唯一真正的规则是数字必须加起来等于系统的本机大小,如8、16、32、64。

        2
  •  32
  •   Antti Kissaniemi    16 年前

    准确地说

    因此, 你应该写一个FixedPoint8类

    它将定点计算的复杂性转移到一个地方,从而使您从许多麻烦中解脱出来。

    FixedPoint8 比如说, typedef FixedPoint<short, 8> FixedPoint8;

    在互联网上可能有一个很好的固定点类-我会从 Boost

        3
  •  9
  •   ryu ryu    16 年前

    你的浮点代码真的使用小数点吗?如果是:

    首先,你必须阅读Randy Yates关于定点数学简介的论文: http://www.digitalsignallabs.com/fp.pdf

    然后,您需要对浮点代码进行“分析”,以确定代码中“关键”点所需的固定值的适当范围,例如U(5,3)=左5位,右3位,无符号。

    此时,您可以应用上面提到的算术规则;这些规则指定如何解释算术运算产生的位。您可以编写宏或函数来执行这些操作。

        4
  •  7
  •   paxdiablo    16 年前

    如果没有特殊的硬件来处理浮点,我根本不会在CPU上使用浮点。我的建议是把所有的数字都当作按特定因子缩放的整数。例如,所有的货币价值都是以美分作为整数,而不是以美元作为浮动单位。例如,0.72表示为整数72。

    乘法稍微复杂一些,因为它需要一个整数乘法,然后再按比例缩小,例如(0.72*2变为72*200变为14400变为144(回缩)变为1.44)。

    这可能需要特殊函数来执行更复杂的数学运算(正弦、余弦等),但即使是这些函数,也可以通过使用查找表来加快速度。示例:由于您使用的是fixed-2表示法,所以在范围(0.0,1](0-99)中只有100个值,而且sin/cos重复出现在这个范围之外,所以您只需要一个100整数查找表。

    干杯, 圣像牌。

        5
  •  6
  •   Jason Sundram Red Alert    13 年前

    如果你可以用一个没有性能损失的类来完成这个任务,那么这就是方法。这在很大程度上取决于编译器及其内联方式。如果使用类会导致性能下降,那么您需要一种更传统的C风格方法。OOP方法将为您提供编译器强制的类型安全性,这是传统实现只能近似实现的。

    @cibyr有一个很好的OOP实现。现在是更传统的。

    要跟踪哪些变量被缩放,需要使用一致的约定。在每个变量名的末尾用记号表示值是否缩放,并编写宏SCALE()和UNSCALE(),扩展到x>8和x<<8。

    #define SCALE(x) (x>>8)
    #define UNSCALE(x) (x<<8)
    
    xPositionUnscaled = UNSCALE(10);
    xPositionScaled = SCALE(xPositionUnscaled);
    

    xPositionScaled = SCALE(xPositionScaled);
    

    这是 匈牙利语应用程序 Joel mentions in this post .

        6
  •  5
  •   Jason Sundram Red Alert    13 年前

    当我第一次遇到固定点数时,我找到了Joe Lemieux的文章, Fixed-point Math in C

    不过,我并没有用他的工会代表来表示定点数字。我大部分时间都有C语言中定点的经验,所以我也没有选择使用类。不过,在大多数情况下,我认为在宏中定义小数位数并使用描述性变量名可以使这项工作变得相当容易。另外,我发现最好使用宏或函数来进行乘法,尤其是除法,否则很快就会得到不可读的代码。

    例如,对于24.8值:

     #include "stdio.h"
    
    /* Declarations for fixed point stuff */
    
    typedef int int_fixed;
    
    #define FRACT_BITS 8
    #define FIXED_POINT_ONE (1 << FRACT_BITS)
    #define MAKE_INT_FIXED(x) ((x) << FRACT_BITS)
    #define MAKE_FLOAT_FIXED(x) ((int_fixed)((x) * FIXED_POINT_ONE))
    #define MAKE_FIXED_INT(x) ((x) >> FRACT_BITS)
    #define MAKE_FIXED_FLOAT(x) (((float)(x)) / FIXED_POINT_ONE)
    
    #define FIXED_MULT(x, y) ((x)*(y) >> FRACT_BITS)
    #define FIXED_DIV(x, y) (((x)<<FRACT_BITS) / (y))
    
    /* tests */
    int main()
    {
        int_fixed fixed_x = MAKE_FLOAT_FIXED( 4.5f );
        int_fixed fixed_y = MAKE_INT_FIXED( 2 );
    
        int_fixed fixed_result = FIXED_MULT( fixed_x, fixed_y );
        printf( "%.1f\n", MAKE_FIXED_FLOAT( fixed_result ) );
    
        fixed_result = FIXED_DIV( fixed_result, fixed_y );
        printf( "%.1f\n", MAKE_FIXED_FLOAT( fixed_result ) );
    
        return 0;
    }
    

    9.0
    4.5
    

    注意,这些宏有各种整数溢出问题,我只想让宏保持简单。这只是一个快速而肮脏的例子,我在C++中如何在C.做这件事,你可以通过操作符重载使事情变得更干净。事实上,你可以很容易地使C代码变得更漂亮。。。

    我想这是一个冗长的说法:我认为使用typedef和宏方法是可以的。只要你清楚哪些变量包含固定值,就不难维护,但它可能不会像C++类那么漂亮。

        7
  •  4
  •   Jason Sundram Red Alert    13 年前

    的原始版本 Tricks of the Game Programming Gurus 有一整章关于实现定点数学。

        8
  •  1
  •   jfm3    16 年前

    无论您决定采用哪种方式(我倾向于使用typedef和一些CPP宏进行转换),您都需要小心地使用一些规则来来回转换。

    你可能会发现你永远不需要来回转换。想象一下整个系统中的一切都是x256。

        9
  •  1
  •   cibyr cibyr    16 年前
    template <int precision = 8> class FixedPoint {
    private:
        int val_;
    public:
        inline FixedPoint(int val) : val_ (val << precision) {};
        inline operator int() { return val_ >> precision; }
        // Other operators...
    };