代码之家  ›  专栏  ›  技术社区  ›  Thomas Zoechling

避免浮点运算

  •  3
  • Thomas Zoechling  · 技术社区  · 15 年前

    我为iPhone编写了一个小型软件合成器。
    为了进一步调优性能,我用Shark测量了我的应用程序,发现在float/sint16转换中我浪费了大量时间。
    所以我重写了一些部分,通过预先计算返回“准备好使用”sint16示例的查找表来绕过转换。到目前为止这还可以。
    目前,我正试图重写一些过滤器和ADSR信封实现,以仅使用整数算术,但我可以使用一些tipps来执行不带浮点的乘法/除法。
    我的目标是 iPhone canonical format :

    • LPCM
    • 16位整数样本

    在不使用浮点数的情况下,将振幅应用于最终样本的好方法是什么?

    编辑:
    到目前为止我唯一知道的是,我可以用2的幂除以当前样本的右移位。

    inBuffer[frame] = wavetable[i % cycleLengthInSamples] >> 4;
    

    但我想不出任何优雅的方式来创造一个光滑的ADSR信封。

    编辑2: 谢谢你的回答!
    我目前的方法:

    • 带上我所有的ADSR信封值 进入正Sint16范围
    • 乘以wavetable的当前值(将中间值存储为sint32)
    • 将结果右移16

    这似乎有效:)

    4 回复  |  直到 15 年前
        1
  •  4
  •   patros    15 年前

    固定点很好,因为在这种情况下,您使用的是16位。最简单的方法是根据需要的精度乘以10的幂。如果您可以使用32位整数作为中间值,那么您应该能够获得相当高的精度。最后,您可以转换回16位int,舍入或截断您喜欢的。

    编辑: 您需要向左移动,使值变大。将移位结果存储在精度更高的类型中(32位或64位,具体取决于您需要的类型)。 如果使用带符号的类型,简单的移位将不起作用

    当心你是乘还是除两个不动点的数字。乘以结果为(a*n)*(b n)最后你会得到一个 n^2而不是a n.分部是(a) n)/(b) n)是(a/b)而不是(a) n)/b)。这就是为什么我建议使用10的幂,如果你不熟悉固定点,很容易发现你的错误。

    当你完成计算后,你移回右边,回到16位int。 如果你想变漂亮,你也可以在换班前做四舍五入。

    如果你真的对实现有效的固定点感兴趣,我建议你读一些书。 http://www.digitalsignallabs.com/fp.pdf

        2
  •  3
  •   Community CDub    7 年前

    答案 this SO question 在实施方面相当全面。这里有一个比我在那里看到的更多的解释:

    一种方法是将所有数字强制到一个范围内,比如说[-1.0,1.0)。然后,将这些数字映射到范围内[-2^15,(2^15)-1]。例如,

    Half = round(0.5*32768); //16384
    Third = round((1.0/3.0)*32768); //10923
    

    当你把这两个数字相乘时

    Temp = Half*Third; //178962432
    Result = Temp/32768; //5461 = round(1.0/6.0)*32768
    

    最后一行除以32768就是点 Patros 关于需要额外缩放步骤的倍数。如果您显式地编写2^n缩放比例,这就更有意义了:

    x1 = x1Float*(2^15);
    x2 = x2Float*(2^15);
    Temp = x1Float*x2Float*(2^15)*(2^15);
    Result = Temp/(2^15); //get back to 2^N scaling
    

    这就是算术。对于实现,注意两个16位整数的乘法需要一个32位的结果,所以temp应该是32位的。另外,32768在16位变量中是不可表示的,因此请注意编译器将使32位立即生效。正如你已经注意到的,你可以切换到乘/除2的幂,这样你就可以写

    N = 15;
    SInt16 x1 = round(x1Float * (1 << N));
    SInt16 x2 = round(x2Float * (1 << N));
    SInt32 Temp = x1*x2;
    Result = (SInt16)(Temp >> N);
    FloatResult = ((double)Result)/(1 << N);
    

    但是假设[-1,1)范围不正确吗?如果你想限制你的数字,比如说,[-4.0,4.0),你可以使用n=13。然后有1个符号位,在二进制点之前有2个位,在二进制点之后有13个位。它们分别称为1.15和3.13定点分数类型。你用分数来交换净空。

    增加和减少分数类型可以很好地工作,只要你注意饱和度。正如帕特罗斯所说,对于分水岭,缩放实际上取消了。所以你必须这么做

    Quotient = (x1/x2) << N;
    

    或者,为了保持精确性

    Quotient = (SInt16)(((SInt32)x1 << N)/x2); //x1 << N needs wide storage
    

    用整数乘和除正常工作。例如,要除以6,你可以简单地写

    Quotient = x1/6; //equivalent to x1Float*(2^15)/6, stays scaled
    

    如果除以2的幂,

    Quotient = x1 >> 3; //divides by 8, can't do x1 << -3 as Patros pointed out
    

    不过,增加和减少整数并不是很简单。您必须首先查看整数是否适合您的x.y类型,生成等效的分数类型,然后继续。

    我希望这有助于实现这一想法,查看另一个问题中的代码以实现清晰的实现。

        3
  •  1
  •   Robert Harvey    15 年前

    请看一下这个描述快速乘法算法的页面。

    http://www.newton.dep.anl.gov/askasci/math99/math99199.htm

        4
  •  1
  •   Clifford    15 年前

    一般来说,假设您将使用带符号的16.16定点表示。所以32位整数将有一个有符号的16位整数部分和一个16位小数部分。那么我不知道iPhone开发中使用了什么语言(可能是Objective-C?),但是这个例子在C中:

    #include <stdint.h>
    
    typedef fixed16q16_t int32_t ;
    #define FIXED16Q16_SCALE 1 << 16 ;
    
    fixed16q16_t mult16q16( fixed16q16_t a, fixed16q16_t b )
    {
        return (a * b) / FIXED16Q16_SCALE ;
    }
    
    fixed16q16_t div16q16( fixed16q16_t a, fixed16q16_t b )
    {
        return (a * FIXED16Q16_SCALE) / b ;
    }
    

    请注意,以上是一个简单的实现,不提供算术溢出保护。例如,在div16q16()中,为了保持精度,在除法之前进行多次运算,但根据操作数的不同,运算可能会溢出。您可以使用64位中介来克服这个问题。而且除法总是向下取整,因为它使用整数除法。这将提供最佳性能,但可能会影响迭代计算的精度。修复很简单,但会增加开销。

    请注意,当乘以或除以2的恒定功率时,大多数编译器都会发现一些微不足道的优化,并使用移位。但是C并没有定义负整数右移的行为,所以为了安全和可移植性,我把它留给编译器来解决。无论你用什么语言。

    在OO语言中,fixed16q16-u自然是带有运算符重载的类的候选者,因此您可以像使用普通算术类型一样使用它。

    您可能会发现在类型之间转换很有用:

    double fixed16q16_to_double( fixed16q16_t fix )
    {
        return (double)fix / FIXED16Q16_SCALE ;
    }
    
    int fixed16q16_to_int( fixed16q16_t fix )
    {
        // Note this rounds to nearest rather than truncates
        return ((fix + FIXED16Q16_SCALE/2)) / FIXED16Q16_SCALE ;
    }
    
    fixed16q16_t int_to_fixed16q16( int i )
    {
        return i * FIXED16Q16_SCALE ;
    }
    
    fixed16q16_t double_to_fixed16q16( double d )
    {
        return (int)(d * FIXED16Q16_SCALE) ;
    }
    

    这是基础,有可能得到更复杂,并添加三角函数和其他数学函数。

    固定的加法和减法与内置的+和-运算符及其变体一起工作。