做定点数学的最佳方法是啥? [关闭]
Posted
技术标签:
【中文标题】做定点数学的最佳方法是啥? [关闭]【英文标题】:What's the best way to do fixed-point math? [closed]做定点数学的最佳方法是什么? [关闭] 【发布时间】:2008-09-17 03:32:07 【问题描述】:我需要为没有 FPU 的 Nintendo DS 加速程序,因此我需要将浮点数学(模拟且速度慢)更改为定点。
我是如何开始的,我将浮点数更改为整数,并且每当我需要转换它们时,我使用 x>>8 将定点变量 x 转换为实际数字和 x 转换为定点。很快我发现无法跟踪需要转换的内容,并且我还意识到很难更改数字的精度(在本例中为 8)。
我的问题是,我应该如何使这更容易并且仍然快速?我应该制作一个 FixedPoint 类,还是只制作一个 FixedPoint8 typedef 或带有一些函数/宏的结构来转换它们,或者其他什么?我应该在变量名中添加一些东西以显示它是定点的吗?
【问题讨论】:
告诉我们您使用花车的目的可能会有所帮助。 CNL 提供了与此描述匹配的类型:scaled_integer<int, power<-8>>
。 (example)
【参考方案1】:
你可以试试我的定点类(最新可用@https://github.com/eteran/cpp-utilities)
// From: https://github.com/eteran/cpp-utilities/edit/master/Fixed.h
// See also: http://***.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。
【讨论】:
不错的库。我认为如果您获得公共领域的许可,您会增加下载次数。这是在文档中提及库的要求不值得让内部法律团队等来寻找我们是否可以使用这个库的事情。恕我直言。 YMMV等 由于位数必须加起来,我觉得在这一点上小数位数甚至不需要作为模板参数。无论如何,我喜欢你的类型提升方法,使用你的 type_from_size 结构;似乎工作得很好。 @EvanTeran 为什么不创建一个github gist呢? @bobobobo:不错的主意,我可以这样做:-) @bobobobo,好久不见,但我终于把我的大部分实用程序代码上传到了 github :-) github.com/eteran/cpp-utilities【参考方案2】:在现代 C++ 实现中,使用简单和精简的抽象(例如具体类)不会降低性能。定点计算精确地是使用适当设计的类可以避免大量错误的地方。
因此,您应该编写一个 FixedPoint8 类。彻底测试和调试它。如果您必须说服自己与使用纯整数相比它的性能,请测量它。
将定点计算的复杂性转移到一个地方,可以省去很多麻烦。
如果您愿意,您可以通过将其作为模板并将旧的 FixedPoint8
替换为 typedef FixedPoint<short, 8> FixedPoint8;
来进一步增加类的实用性。但是在您的目标架构上,这可能不是必需的,因此请避免复杂性首先是模板。
互联网上的某个地方可能有一个很好的定点类 - 我会从 Boost 库开始查找。
【讨论】:
+1 我在一家游戏公司工作,推出了一些 NDS 游戏,这确实是你应该做的。然而,目前还没有一个 boost 库/类,所以最后几句话可能不是(还)好的建议。 如何使用定点和某种矢量化(如 SSE)?围绕所有位旋转的抽象层可能很好,但我宁愿使用一组 C 风格的#define 宏来操作数字,以便以后能够将它们提供给 SSE 内在函数。 我认为 NDS 处理器没有 NEON(矢量化)。【参考方案3】:您的浮点代码是否真的使用小数点?如果是这样:
首先,您必须阅读 Randy Yates 关于定点数学简介的论文: http://www.digitalsignallabs.com/fp.pdf
然后您需要对浮点代码进行“分析”,以找出代码中“关键”点所需的定点值的适当范围,例如U(5,3) = 左边 5 位,右边 3 位,无符号。
此时,您可以应用上述论文中的算术规则;规则指定如何解释算术运算产生的位。您可以编写宏或函数来执行这些操作。
保留浮点版本很方便,以便比较浮点与定点的结果。
【讨论】:
【参考方案4】:如果没有特殊的硬件来处理它,我根本不会在 CPU 上使用浮点。我的建议是将所有数字视为缩放到特定因子的整数。例如,所有货币值都以整数表示的美分,而不是浮点数表示的美元。例如,0.72 表示为整数 72。
加法和减法是一个非常简单的整数运算,例如(0.72 + 1 变为 72 + 100 变为 172 变为 1.72)。
乘法稍微复杂一些,因为它需要一个整数乘法,然后是一个缩减,例如(0.72 * 2 变成 72 * 200 变成 14400 变成 144(缩减)变成 1.44)。
这可能需要特殊函数来执行更复杂的数学运算(正弦、余弦等),但即使是那些也可以通过使用查找表来加速。示例:由于您使用的是固定 2 表示,因此 (0.0,1] (0-99) 范围内只有 100 个值,并且 sin/cos 在此范围之外重复,因此您只需要一个 100 整数查找表。
干杯, 人。
【讨论】:
哈哈哈。请记住,当您使用 Cheers, Pax 签署答案时。你是新人。【参考方案5】:更改定点表示通常称为“缩放”。
如果您可以在没有性能损失的类上做到这一点,那么这就是要走的路。它在很大程度上取决于编译器及其内联方式。如果使用类会降低性能,那么您需要更传统的 C 风格方法。 OOP 方法将为您提供编译器强制的类型安全性,而传统的实现只能接近这一点。
@cibyr 有一个很好的 OOP 实现。现在是更传统的。
要跟踪缩放了哪些变量,您需要使用一致的约定。在每个变量名称的末尾做一个符号来表示该值是否被缩放,并编写宏 SCALE() 和 UNSCALE() 扩展为 x>>8 和 x
#define SCALE(x) (x>>8)
#define UNSCALE(x) (x<<8)
xPositionUnscaled = UNSCALE(10);
xPositionScaled = SCALE(xPositionUnscaled);
使用这么多符号似乎是一项额外的工作,但请注意如何在不查看其他行的情况下一眼看出任何行是正确的。例如:
xPositionScaled = SCALE(xPositionScaled);
显然是错误的,通过检查。
这是 Apps Hungarian 理念的变体,Joel mentions in this post。
【讨论】:
我不喜欢 SCALE 和 UNSCALE,因为对于乘法:xPositionScaled = SCALE(SCALE(xPositionUnscaled * multUnscaled));
Scale/unscale 不是很好的名称,但使用这样的命名约定对于定点数非常重要,尤其是当您使用多种 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
)。是的,它非常冗长,但让您可以完全控制。【参考方案6】:
当我第一次遇到定点数时,我发现 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 和宏。但是,如果您决定需要使用定点数学全局替换所有浮点数,那么使用类可能会更好。
【讨论】:
链接失效了,可能是这个:eetimes.com/author.asp?doc_id=1287491?【参考方案7】:Tricks of the Game Programming Gurus 的原始版本有一整章是关于实现定点数学的。
【讨论】:
很好,我拥有的为数不多的几本书之一! ...不,等等,我有“WINDOWS 游戏编程大师的技巧”。不再是一整章,但它确实有一些关于定点数学的有用页面。 好几年没看过那本书了,以前我还拥有它 :-)【参考方案8】: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...
;
【讨论】:
【参考方案9】:无论您决定采用哪种方式(我倾向于使用 typedef 和一些 CPP 宏进行转换),您都需要小心谨慎地来回转换。
您可能会发现您永远不需要来回转换。想象一下整个系统中的一切都是 x256。
【讨论】:
编写该类的目的是通过类型安全来消除该规则。以上是关于做定点数学的最佳方法是啥? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章