C++ 傅立叶变换浮点问题

C++ 傅立叶变换浮点问题,c++,math,floating-point,fft,C++,Math,Floating Point,Fft,我正在为图像实现一种传统的(也就是说不快的)分离傅里叶变换。我知道,在浮点运算中,等距样本中一个周期的sin或cos之和并不是完全为零,这是常规变换比快速变换更大的问题 该算法适用于二维双阵列,是正确的。反向是在内部完成的(使用非对称公式时通过双符号标志和条件检查),而不是在外部执行共轭。结果几乎100%符合预期,因此这是一个关于细节的问题: 当我执行正向变换时,将对数幅值和角度保存到图像,重新加载它们,然后执行逆变换,我会遇到不同类型的舍入误差,使用不同类型的实现公式: F(u,v)=和(x=

我正在为图像实现一种传统的(也就是说不快的)分离傅里叶变换。我知道,在浮点运算中,等距样本中一个周期的sin或cos之和并不是完全为零,这是常规变换比快速变换更大的问题

该算法适用于二维双阵列,是正确的。反向是在内部完成的(使用非对称公式时通过双符号标志和条件检查),而不是在外部执行共轭。结果几乎100%符合预期,因此这是一个关于细节的问题:

当我执行正向变换时,将对数幅值和角度保存到图像,重新加载它们,然后执行逆变换,我会遇到不同类型的舍入误差,使用不同类型的实现公式:

  • F(u,v)=和(x=0->M-1)和(y=0->N-1)F(x,y)*e^(-i*2*pi*u*x/M)*e^(-i*2*pi*v*y/N)

    f(x,y)=1/M*N*(如上所述)

  • F(u,v)=1/sqrt(M*N)*(如上所述)

    f(x,y)=1/sqrt(M*N)*(如上所述)

  • 第一个是非对称变换对,第二个是对称变换对。对于非对称对,舍入误差在图像的亮点中更大(一些像素被舍入到稍微超出值范围(例如256))。对于对称对,误差更多地出现在图像的恒定中间区域(不超过值范围!)。总的来说,对称对似乎会产生更多的舍入误差

    然后,它还取决于输入:当图像存储在[0255]中时,舍入误差与存储在[0,1]中时不同

    所以我的问题是:如何实现一个最优、最精确的算法(理论上,没有代码):不对称/对称对?[0255]或[0,1]中输入的值范围?如何在将对数结果保存到文件之前线性放大结果

    编辑

    我的算法只是计算分离的不对称或对称DFT公式。使用欧拉恒等式将因子分解为实部和虚部,然后分别展开和汇总为实部和虚部:

    sum_re += f_re * cos(-mode*pi*((2.0*v*y)/N)) - // mode = 1 for forward, -1
              f_im * sin(-mode*pi*((2.0*v*y)/N));  // for inverse transform
    // sum_im permutated in the known way and + instead of -
    
    在我看来,这种将内部cos和sin分组的值应该给出最低的舍入误差(与例如
    cos(-mode*2*pi*v*y/N)相比)
    ),因为不会将显著错误舍入的跨入pi相乘/相除几次,而只是一次。不是吗

    比例因子
    1/M*N
    1/sqrt(M*N)
    在最内层总和之外的每次分离后分别应用。里面更好吗?还是在两次分离结束时完全合并

    为了进行更深入的分析,我退出了
    input->transform->save to file->read from file->transform^-1->output
    工作流,并选择直接进行双精度比较:
    input->transform->transform^-1->output

    这里是实际704x528 8 8位图像的结果(增量=输入和输出的实际部分之间的最大绝对差值):

    • 输入在[0,1]内,不对称公式:δ=2.6609e-13(对应于[0255]范围内的6.785295e-11)
    • 输入insde[0,1]和对称公式:delta=2.65232e-13(对应于[0255]范围的6.763416e-11)
    • 输入在[0255]内,不对称公式:δ=6.74731e-11
    • 输入在[0255]内,对称公式为:δ=6.7871e-11
    这些都不是真正意义上的显著差异,但是,非对称转换的全范围输入效果最好。我认为16位输入的值可能会变差

    但总的来说,我发现我遇到的问题更多的是因为保存到文件(或反向)舍入错误之前的缩放,而不是真正的转换舍入错误


    然而,我很好奇:傅里叶变换最常用的实现是什么:对称还是非对称?输入[0,1]或[0255]通常使用哪个值范围?以及通常以对数刻度显示的光谱:例如,[0,1]输入的不对称变换后的[0,M*N]直接对数刻度为[0255],或线性刻度为[0255*M*N]之前的[0,M*N]?

    刻度会导致舍入误差。因此,解决方案1(缩放一次)优于解决方案2(缩放两次)。类似地,求和后缩放一次比求和前缩放所有对象要好

    您是从
    0
    运行到
    2*N
    还是从
    -N
    运行到
    +N
    ?数学上是一样的,但在后一种情况下,你有额外的精度


    顺便问一下,在
    cos(-mode*stuff)
    mode
    在做什么

    您报告的错误很小,很正常,通常可以忽略。只需缩放结果并将目标间隔之外的任何结果钳制到端点即可

    在FFT的库实现中(即编写供不同应用程序通用的FFT例程,而不是为单个应用程序定制的),很少考虑可伸缩性;该例程通常只返回由算术自然缩放的数据,而不使用额外的乘法运算来调整缩放。这是因为刻度通常与应用无关(例如,无论刻度是什么,都可以找到能量最大的频率),或者刻度可以通过乘法运算分布,并且只执行一次(例如,应用程序不需要在正变换和逆变换中进行缩放,而只需显式缩放一次即可获得相同的效果)。因此,由于通常不需要缩放,因此没有必要将其包含在库例程中

    数据缩放到的目标间隔取决于应用程序


    关于使用什么变换(对数或线性)来显示光谱的问题,我不能给出建议;我不使用可视化光谱。

    如果您使用相同的核心算法,并且只是缩放结果,则