进行定点数学的最佳方法是什么?
我需要为没有 FPU 的 Nintendo DS 加速一个程序,因此我需要将浮点数学(经过仿真且速度较慢)更改为定点。
我的开始方式是将浮点数更改为整数,每当需要转换它们时,我使用 x>>8 将定点变量 x 转换为实际数字,然后 x<< ;8 转换为定点。 很快我发现不可能跟踪需要转换的内容,而且我还意识到很难更改数字的精度(在本例中为 8)。
我的问题是,我应该如何使这变得更容易并且仍然快速地? 我应该创建一个FixedPoint类,还是只是一个FixedPoint8 typedef或带有一些函数/宏的结构来转换它们,或者其他什么? 我应该在变量名中添加一些内容来显示它是定点的吗?
I need to speed up a program for the Nintendo DS which doesn't have an FPU, so I need to change floating-point math (which is emulated and slow) to fixed-point.
How I started was I changed floats to ints and whenever I needed to convert them, I used x>>8 to convert the fixed-point variable x to the actual number and x<<8 to convert to fixed-point. Soon I found out it was impossible to keep track of what needed to be converted and I also realized it would be difficult to change the precision of the numbers (8 in this case.)
My question is, how should I make this easier and still fast? Should I make a FixedPoint class, or just a FixedPoint8 typedef or struct with some functions/macros to convert them, or something else? Should I put something in the variable name to show it's fixed-point?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
无论您决定采用哪种方式(我倾向于使用 typedef 和一些用于转换的 CPP 宏),您都需要小心地按照一定的规则来回转换。
您可能会发现您永远不需要来回转换。 想象一下整个系统中的所有内容都是 x256。
Whichever way you decide to go (I'd lean toward a typedef and some CPP macros for converting), you will need to be careful to convert back and forth with some discipline.
You might find that you never need to convert back and forth. Just imagine everything in the whole system is x256.
游戏编程大师的技巧的原始版本有一整章关于实现定点数学。
The original version of Tricks of the Game Programming Gurus has an entire chapter on implementing fixed-point math.
更改定点表示通常称为“缩放”。
如果您可以在没有性能损失的情况下完成此任务,那么这就是正确的选择。 它在很大程度上取决于编译器及其内联方式。 如果使用类会降低性能,那么您需要更传统的 C 风格方法。 OOP 方法将为您提供编译器强制的类型安全性,而传统实现仅近似于此。
@cibyr 有一个很好的 OOP 实现。 现在来说说更传统的一种。
要跟踪哪些变量被缩放,您需要使用一致的约定。 在每个变量名称的末尾添加符号以指示该值是否缩放,并编写扩展为 x>>8 和 x<<8 的宏 SCALE() 和 UNSCALE()。
使用如此多的符号似乎是额外的工作,但请注意如何一眼就能看出任何一行是正确的,而无需查看其他行。 例如:
显然是错误的,经检查。
这是 Joel 在这篇文章中提到的 Apps Hungarian 想法的变体。
Changing fixed point representations is commonly called 'scaling'.
If you can do this with a class with no performance penalty, then that's the way to go. It depends heavily on the compiler and how it inlines. If there is a performance penalty using classes, then you need a more traditional C-style approach. The OOP approach will give you compiler-enforced type safety which the traditional implementation only approximates.
@cibyr has a good OOP implementation. Now for the more traditional one.
To keep track of which variables are scaled, you need to use a consistent convention. Make a notation at the end of each variable name to indicate whether the value is scaled or not, and write macros SCALE() and UNSCALE() that expand to x>>8 and x<<8.
It may seem like extra work to use so much notation, but notice how you can tell at a glance that any line is correct without looking at other lines. For example:
is obviously wrong, by inspection.
This is a variation of the Apps Hungarian idea that Joel mentions in this post.
当我第一次遇到定点数时,我找到了 Joe Lemieux 的文章 C 中的定点数学,非常有帮助,并且它确实提出了一种表示定点值的方法。
不过,我最终没有使用他的联合表示来表示定点数。 我主要有 C 定点方面的经验,所以我也没有选择使用类。 但在大多数情况下,我认为在宏中定义分数位数并使用描述性变量名称使得这相当容易使用。 另外,我发现最好有用于乘法(尤其是除法)的宏或函数,否则您很快就会得到不可读的代码。
例如,对于 24.8 值:
写出请
注意,这些宏存在各种整数溢出问题,我只是想让宏保持简单。 这只是我如何在 C 中完成此操作的一个快速而肮脏的示例。在 C++ 中,您可以使用运算符重载使一些东西变得更干净。 实际上,您也可以轻松地使 C 代码变得更漂亮...
我想这是一种冗长的说法:我认为使用 typedef 和宏方法是可以的。 只要您清楚哪些变量包含定点值,维护起来就不难,但它可能不会像 C++ 类那么漂亮。
如果我处于您的位置,我会尝试获取一些分析数据以显示瓶颈所在。 如果它们相对较少,那么就使用 typedef 和宏。 如果您决定需要用定点数学全局替换所有浮点数,那么您可能会更好地使用一个类。
When I first encountered fixed point numbers I found Joe Lemieux's article, Fixed-point Math in C, very helpful, and it does suggest one way of representing fixed-point values.
I didn't wind up using his union representation for fixed-point numbers though. I mostly have experience with fixed-point in C, so I haven't had the option to use a class either. For the most part though, I think that defining your number of fraction bits in a macro and using descriptive variable names makes this fairly easy to work with. Also, I've found that it is best to have macros or functions for multiplication and especially division, or you quickly get unreadable code.
For example, with 24.8 values:
Which writes out
Note that there are all kinds of integer overflow issues with those macros, I just wanted to keep the macros simple. This is just a quick and dirty example of how I've done this in C. In C++ you could make something a lot cleaner using operator overloading. Actually, you could easily make that C code a lot prettier too...
I guess this is a long-winded way of saying: I think it's OK to use a typedef and macro approach. So long as you're clear about what variables contain fixed point values it isn't too hard to maintain, but it probably won't be as pretty as a C++ class.
If I was in your position, I would try to get some profiling numbers to show where the bottlenecks are. If there are relatively few of them then go with a typedef and macros. If you decide that you need a global replacement of all floats with fixed-point math though, then you'll probably be better off with a class.
如果没有特殊的硬件来处理浮点,我根本不会在 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 整数查找表。
干杯,
帕克斯。
I wouldn't use floating point at all on a CPU without special hardware for handling it. My advice is to treat ALL numbers as integers scaled to a specific factor. For example, all monetary values are in cents as integers rather than dollars as floats. For example, 0.72 is represented as the integer 72.
Addition and subtraction are then a very simple integer operation such as (0.72 + 1 becomes 72 + 100 becomes 172 becomes 1.72).
Multiplication is slightly more complex as it needs an integer multiply followed by a scale back such as (0.72 * 2 becomes 72 * 200 becomes 14400 becomes 144 (scaleback) becomes 1.44).
That may require special functions for performing more complex math (sine, cosine, etc) but even those can be sped up by using lookup tables. Example: since you're using fixed-2 representation, there's only 100 values in the range (0.0,1] (0-99) and sin/cos repeat outside this range so you only need a 100-integer lookup table.
Cheers,
Pax.
您的浮点代码实际上使用了小数点吗? 如果是这样:
首先您必须阅读兰迪·耶茨(Randy Yates)关于定点数学简介的论文:
http://www.digitalsignallabs.com/fp.pdf
然后你需要进行“分析”在您的浮点代码上计算出代码中“关键”点所需的适当定点值范围,例如 U(5,3) = 左侧 5 位,右侧 3 位,无符号。
此时,你可以应用上面提到的论文中的算术规则; 这些规则指定如何解释算术运算结果的位。 您可以编写宏或函数来执行操作。
保留浮点版本很方便,以便比较浮点与定点结果。
Does your floating point code actually make use of the decimal point? If so:
First you have to read Randy Yates's paper on Intro to Fixed Point Math:
http://www.digitalsignallabs.com/fp.pdf
Then you need to do "profiling" on your floating point code to figure out the appropriate range of fixed-point values required at "critical" points in your code, e.g. U(5,3) = 5 bits to the left, 3 bits to the right, unsigned.
At this point, you can apply the arithmetic rules in the paper mentioned above; the rules specify how to interpret the bits which result from arithmetic operations. You can write macros or functions to perform the operations.
It's handy to keep the floating point version around, in order to compare the floating point vs fixed point results.
在现代 C++ 实现中,使用简单且精简的抽象(例如具体类)不会造成性能损失。 定点计算恰恰正是使用正确设计的类可以避免大量错误的地方。
因此,您应该编写一个FixedPoint8 类。 彻底测试和调试它。 如果您必须让自己相信其与使用普通整数相比的性能,请对其进行测量。
通过将定点计算的复杂性转移到一个地方,它将为您省去许多麻烦。
如果您愿意,您可以通过将类设为模板并将旧的
FixedPoint8
替换为typedef FixedPoint来进一步提高类的实用性。 FixPoint8;
但在您的目标架构上,这可能不是必需的,因此首先要避免模板的复杂性。互联网上的某个地方可能有一个很好的定点类 - 我会开始从 Boost 库中查找。
In modern C++ implementations, there will be no performance penalty for using simple and lean abstractions, such as concrete classes. Fixed-point computation is precisely the place where using a properly engineered class will save you from lots of bugs.
Therefore, you should write a FixedPoint8 class. Test and debug it thoroughly. If you have to convince yourself of its performance as compared to using plain integers, measure it.
It will save you from many a trouble by moving the complexity of fixed-point calculation to a single place.
If you like, you can further increase the utility of your class by making it a template and replacing the old
FixedPoint8
with, say,typedef FixedPoint<short, 8> FixedPoint8;
But on your target architecture this is not probably necessary, so avoid the complexity of templates at first.There is probably a good fixed point class somewhere in the internet - I'd start looking from the Boost libraries.
您可以尝试我的定点类(最新可用@ https://github.com/eteran/cpp-utilities< /a>)
它被设计为几乎可以替代浮点数/双精度数,并且具有可选择的精度。 它确实利用 boost 来添加所有必要的数学运算符重载,因此您也需要它(我相信为此它只是一个标头依赖项,而不是库依赖项)。
顺便说一句,常见用法可能是这样的:
唯一真正的规则是数字必须加起来等于系统的本机大小,例如 8、16、32、64。
You can try my fixed point class (Latest available @ https://github.com/eteran/cpp-utilities)
It is designed to be a near drop in replacement for floats/doubles and has a choose-able precision. It does make use of boost to add all the necessary math operator overloads, so you will need that as well (I believe for this it is just a header dependency, not a library dependency).
BTW, common usage could be something like this:
The only real rule is that the number have to add up to a native size of your system such as 8, 16, 32, 64.