我需要为没有FPU的Nintendo DS加速程序,所以我需要将浮点数学(模拟和慢速)更改为定点.
我是如何开始的,我将浮点数更改为整数,每当我需要转换它们时,我使用x >> 8将定点变量x转换为实际数字,将x << 8转换为定点.很快我发现无法跟踪需要转换的内容,我也意识到很难改变数字的精确度(在这种情况下为8).
我的问题是,我应该如何让这更容易,更快?我应该制作一个FixedPoint类,或者只是一个FixedPoint8 typedef或带有一些函数/宏的结构来转换它们,还是别的什么?我应该在变量名称中添加一些内容来显示它的定点吗?
您可以尝试我的定点课程(最新可用@ 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#include #include // for size_t #include #include #include namespace numeric { template 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 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 B next_to_base(const N& rhs) { return static_cast(rhs); } struct divide_by_zero : std::exception { }; template Fixed divide(const Fixed &numerator, const Fixed &denominator, Fixed &remainder, typename std::enable_if ::next_size::is_specialized>::type* = 0) { typedef typename Fixed::next_type next_type; typedef typename Fixed::base_type base_type; static const size_t fractional_bits = Fixed::fractional_bits; next_type t(numerator.to_raw()); t <<= fractional_bits; Fixed quotient; quotient = Fixed::from_base(next_to_base (t / denominator.to_raw())); remainder = Fixed::from_base(next_to_base (t % denominator.to_raw())); return quotient; } template Fixed divide(Fixed numerator, Fixed denominator, Fixed &remainder, typename std::enable_if::next_size::is_specialized>::type* = 0) { // NOTE(eteran): division is broken for large types :-( // especially when dealing with negative quantities typedef typename Fixed::base_type base_type; typedef typename Fixed::unsigned_type unsigned_type; static const int bits = Fixed::total_bits; if(denominator == 0) { throw divide_by_zero(); } else { int sign = 0; Fixed 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 (n) << F) / denominator.to_raw(); quotient = Fixed::from_base((answer << F) | lo); remainder = n; if(sign) { quotient = -quotient; } return quotient; } } // this is the usual implementation of multiplication template void multiply(const Fixed &lhs, const Fixed &rhs, Fixed &result, typename std::enable_if ::next_size::is_specialized>::type* = 0) { typedef typename Fixed::next_type next_type; typedef typename Fixed::base_type base_type; static const size_t fractional_bits = Fixed::fractional_bits; next_type t(static_cast (lhs.to_raw()) * static_cast (rhs.to_raw())); t >>= fractional_bits; result = Fixed::from_base(next_to_base (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 void multiply(const Fixed &lhs, const Fixed &rhs, Fixed &result, typename std::enable_if::next_size::is_specialized>::type* = 0) { typedef typename Fixed::base_type base_type; static const size_t fractional_bits = Fixed::fractional_bits; static const size_t integer_mask = Fixed::integer_mask; static const size_t fractional_mask = Fixed::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::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 class Fixed : boost::operators > { static_assert(detail::type_from_size::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 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 (n * one)) { // TODO(eteran): assert in range! } Fixed(double n) : data_(static_cast (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 (data_) / Fixed::one; } double to_double() const { return static_cast (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 typename std::conditional = I2, Fixed , Fixed >::type operator+(const Fixed &lhs, const Fixed &rhs) { typedef typename std::conditional< I1 >= I2, Fixed , Fixed >::type T; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); return l + r; } template typename std::conditional = I2, Fixed , Fixed >::type operator-(const Fixed &lhs, const Fixed &rhs) { typedef typename std::conditional< I1 >= I2, Fixed , Fixed >::type T; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); return l - r; } template typename std::conditional = I2, Fixed , Fixed >::type operator*(const Fixed &lhs, const Fixed &rhs) { typedef typename std::conditional< I1 >= I2, Fixed , Fixed >::type T; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); return l * r; } template typename std::conditional = I2, Fixed , Fixed >::type operator/(const Fixed &lhs, const Fixed &rhs) { typedef typename std::conditional< I1 >= I2, Fixed , Fixed >::type T; const T l = T::from_base(lhs.to_raw()); const T r = T::from_base(rhs.to_raw()); return l / r; } template std::ostream &operator<<(std::ostream &os, const Fixed &f) { os << f.to_double(); return os; } template const size_t Fixed::fractional_bits; template const size_t Fixed::integer_bits; template const size_t Fixed::total_bits; } #endif
它被设计成几乎可以替代浮子/双打,并具有可选择的精度.它确实利用boost来添加所有必要的数学运算符重载,所以你也需要它(我相信它只是一个头依赖,而不是库依赖).
顺便说一下,常见用法可能是这样的:
using namespace numeric; typedef Fixed<16, 16> fixed; fixed f;
唯一真正的规则是该数字必须加起来为系统的原生大小,例如8,16,32,64.
在现代C++实现中,使用简单和精简抽象(例如具体类)不会有性能损失.定点计算正是使用正确设计的类可以避免大量错误的地方.
因此,您应该编写一个FixedPoint8类.彻底测试和调试.如果你必须说服自己与使用普通整数相比它的性能,那么测量它.
通过将定点计算的复杂性转移到一个地方,它可以避免许多麻烦.
如果您愿意,可以通过将其作为模板并用旧的替换来进一步提高类的效用FixedPoint8
,比方说,typedef FixedPoint
但是在目标体系结构上这可能不是必需的,因此首先要避免模板的复杂性.
在互联网的某个地方可能有一个很好的定点课 - 我开始从Boost库中寻找.
您的浮点代码是否实际使用了小数点?如果是这样:
首先,您必须阅读Randy Yates关于定点数学简介的论文:http: //www.digitalsignallabs.com/fp.pdf
然后,您需要对浮点代码进行"分析",以找出代码中"关键"点所需的适当范围的定点值,例如U(5,3)=左侧5位,3位在右边,未签名.
此时,您可以在上述论文中应用算术规则; 规则指定如何解释算术运算产生的位.您可以编写宏或函数来执行操作.
为了比较浮点和固定点结果,保持浮点版本是很方便的.
改变定点表示通常称为"缩放".
如果你可以用一个没有性能损失的类来做到这一点,那么这就是你要走的路.它在很大程度上取决于编译器及其内联方式.如果使用类有性能损失,那么您需要更传统的C风格方法.OOP方法将为您提供编译器强制的类型安全性,这是传统实现仅近似的.
@cibyr有一个很好的OOP实现.现在更传统的一个.
要跟踪哪些变量已缩放,您需要使用一致的约定.在每个变量名称的末尾标注以指示值是否缩放,并写入扩展为x >> 8和x << 8的宏SCALE()和UNSCALE().
#define SCALE(x) (x>>8) #define UNSCALE(x) (x<<8) xPositionUnscaled = UNSCALE(10); xPositionScaled = SCALE(xPositionUnscaled);
使用这么多符号似乎是额外的工作,但请注意如何在不查看其他行的情况下一眼就能看出任何行是正确的.例如:
xPositionScaled = SCALE(xPositionScaled);
通过检查显然是错误的.
这是Joel在这篇文章中提到的Apps匈牙利想法的变体.
我不会在没有用于处理它的特殊硬件的情况下在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.
当我第一次遇到定点数时,我发现Joe Lemieux的文章,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)<写出来了
9.0 4.5请注意,这些宏存在各种整数溢出问题,我只是想让宏保持简单.这只是我在C中完成此操作的一个快速而肮脏的例子.在C++中,您可以使用运算符重载使事情更清晰.实际上,你可以很容易地使C代码变得更漂亮......
我想这是一种冗长的说法:我认为使用typedef和macro方法是可以的.只要你清楚哪些变量包含定点值,就不难维护,但它可能不会像C++类那样漂亮.
如果我在你的位置,我会尝试获取一些分析数字来显示瓶颈所在.如果它们相对较少,则使用typedef和宏.如果您决定需要使用定点数学来全局替换所有浮点数,那么您可能会更好地使用类.