如何进行定点数运算最佳?

57

我需要加速一款适用于没有FPU的Nintendo DS平台的程序,所以我需要将浮点数运算(模拟并且速度较慢)改为定点数运算。

我的开始方法是将浮点数改为整数,并在需要转换时使用x>>8将定点变量x转换为实际数字,使用x<<8将其转换为定点数。很快我发现无法跟踪哪些数需要转换,而且也意识到难以更改数字的精度(在这种情况下为8)。

我的问题是,应该如何使这个过程更简单而又快速?我应该创建一个FixedPoint类,还是只创建一个FixedPoint8 typedef或struct,带有一些函数/宏来转换它们,或者其他什么方式?我应该在变量名称中加入一些内容以显示它是定点数吗?


告诉我们您使用浮点数的目的可能会有所帮助。 - Brad Gilbert
CNL提供了一种类型,与此描述相匹配:scaled_integer<int, power<-8>>。(示例 - John McFarlane
9个回答

56
你可以尝试使用我的固定点类(最新版本可在https://github.com/eteran/cpp-utilities获取)。
// From: https://github.com/eteran/cpp-utilities/edit/master/Fixed.h
// See also: https://dev59.com/pnVD5IYBdhLWcg3wHn2d
/*
 * 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添加了所有必要的数学运算符重载,因此您还需要安装boost(我认为这只是一个头文件依赖项,而不是库依赖项)。

顺便说一句,常见的用法可能类似于这样:

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

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


31
不错的库。我认为如果您将许可证改为公共领域,那么下载量会增加。这是其中一件事情,在文档中提到库并不值得耗费精力去涉及公司法务团队等来确定我们是否可以使用这个库。依我的看法,结果可能因人而异。 - user188012
2
由于位数必须相加,因此我认为在那一点上,分数位数甚至不需要作为模板参数。无论如何,我喜欢您的类型提升方法,使用您的type_from_size结构;看起来非常有效。 - leetNightshade
1
@EvanTeran 为什么不将此内容创建为github gist呢? - bobobobo
2
@bobobobo:不错的想法,我可能会这么做 :-) - Evan Teran
5
@bobobobo,终于等到这一天了,我已经将我的大部分实用代码上传至 Github :-) https://github.com/eteran/cpp-utilities - Evan Teran
显示剩余6条评论

37
在现代C++实现中,使用简单而精简的抽象(例如具体类)不会导致性能损失。固定点计算是一个需要使用适当设计的类来避免许多错误的地方。
因此,你应该编写一个FixedPoint8类。彻底测试和调试它。如果你需要自己比较其与使用普通整数的性能,请进行测量。
将固定点计算的复杂性移动到一个单独的位置可以避免很多麻烦。
如果你愿意,可以通过使其成为一个模板并用typedef FixedPoint<short, 8> FixedPoint8;替换旧的FixedPoint8,进一步增加类的效用。但在目标架构上,这可能不是必要的,因此最好先避免模板的复杂性。
互联网上可能有一个好的固定点类 - 我会从Boost库开始寻找。

2
+1 我现在在一家游戏公司工作,我们已经开发了一些NDS游戏,这确实是你应该做的事情。不过目前还没有适用于此的boost库/类,所以最后一句话可能(目前)不是一个好建议。 - Klaim
使用固定点加上某种向量化(如SSE)怎么样?在所有位操作周围添加一层抽象可能很好,但我宁愿使用一组C风格的#define宏来操作数字,以便稍后将它们馈送到SSE内部函数中。 - neuviemeporte
我认为NDS处理器没有NEON(向量化)功能。 - Mooing Duck

11

你的浮点数代码是否实际使用了小数点?如果是:

首先,你需要阅读Randy Yates的《Intro to Fixed Point Math》论文: http://www.digitalsignallabs.com/fp.pdf

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

此时,你可以应用上述论文中的算术规则;这些规则指定如何解释算术运算结果中的位。你可以编写宏或函数来执行这些操作。

保留浮点版本很方便,以便比较浮点和固定点结果。


8
当我第一次接触到定点数时,我发现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++类那样漂亮。
如果我处于您的位置,我会尝试获取一些分析数据以显示瓶颈所在的位置。如果瓶颈相对较少,则使用typedef和宏。如果您决定需要全局替换所有浮点数为定点数学,则最好使用类。

1
链接已经失效,也许这是正确的链接:http://www.eetimes.com/author.asp?doc_id=1287491? - Danijel

8
我不建议在没有专门处理浮点数的硬件的CPU上使用浮点数。我的建议是将所有数字视为按特定因子缩放的整数。例如,所有货币值都以整数分为单位,而不是以浮点数美元为单位。例如,0.72表示为整数72。
然后,加法和减法变成了非常简单的整数操作,例如(0.72 + 1变成72 + 100变成172变成1.72)。
乘法稍微复杂一些,因为它需要一个整数乘法,然后是一个缩放回来的过程,例如(0.72 * 2变成72 * 200变成14400变成144(缩放回来)变成1.44)。
这可能需要执行更复杂的数学运算(正弦,余弦等)的特殊函数,但即使是这些也可以通过使用查找表来加速。例如:由于您正在使用固定-2表示,范围内仅有100个值(0.0,1](0-99),sin/cos在此范围外重复,因此您只需要一个包含100个整数的查找表。
祝好, Pax。

4
哈哈哈。还记得你在回答中签名为“Cheers, Pax”的时候吗?当时的你很新。 - bobobobo

6

将定点表示法更改通常称为“缩放”。

如果您可以在没有性能损失的情况下使用类来完成此操作,那么这就是正确的方法。这严重依赖于编译器以及它如何进行内联。如果使用类会有性能损失,则需要采用更传统的C风格方法。面向对象的方法将为您提供编译器强制执行的类型安全性,而传统实现则只是近似实现。

@cibyr有一个很好的面向对象的实现。现在是更传统的方法。

为了跟踪哪些变量被缩放,您需要使用一致的约定。在每个变量名称的末尾做出注释,以指示该值是否已缩放,并编写宏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在这篇文章中提到


我不喜欢SCALE和UNSCALE,因为对于乘法而言:xPositionScaled = SCALE(SCALE(xPositionUnscaled * multUnscaled)); - Mooing Duck
缩放/反缩放不是很好的名称,但对于定点数,使用这样的命名约定非常重要,特别是当您使用多个M.N格式时。我建议附加精确的M.N格式,例如speed_fraction_F7_25 = fix_udiv(25, speed_percent << 25, 100 << 25); squared_speed_F7_25 = fix_umul(25, speed_fraction_F7_25, speed_fraction_F7_25); tmp1_F7_25 = fix_umul(25, squared_speed_F7_25, SQRT_3_F7_25); tmp2_F20_12 = fix_umul(12, tmp.F7_25 >> (25-12), motor_volt << 12);/100最好作为*0.01执行)。是的,它相当冗长,但让您完全掌控。 - hlovdal

5

甜蜜,我拥有的少数几本书之一!...不,等等,我还有《WINDOWS游戏编程大师的技巧》。虽然不是整章,但它确实有一些关于定点数学的有用页面。 - Gavin
好久没看到那本书了,我曾经拥有它 :-) - Simon Bosley

2
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...
};

0
无论你决定采用哪种方式(我倾向于使用typedef和一些CPP宏进行转换),你都需要小心谨慎地进行前后转换。
也许你永远不需要来回转换。只要想象整个系统中的所有内容都是x256即可。

编写这个类的目的是通过类型安全来消除那种需要纪律的要求。 - John McFarlane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接