[英]Loss of double precision C#
我在使用double时遇到精度损失,我似乎找不到丢失精度的地方。 我正在写一个软件合成器,由于某种原因,振荡器(声波发生器)的输入频率会在此过程中的某个地方严重混叠。 振荡器应该产生一个三角波形,并且正确的频率被传递给该方法,但是当结果在扬声器中播放时,我可以清楚地听到声音突然跳到不同的频率。
我正在使用NAudio(http://naudio.codeplex.com/),并且我要生成的每个样本都会运行一次此方法。
这是代码:
double counter = 0;
int samplecounter = 0;
public double GetNextTriangle(double frequency)
{
double samplesperwave = (double)Parent.WaveFormat.SampleRate / frequency;
double length = (double)samplecounter / samplesperwave;
if (length.CompareTo(0.25) == -1)
{
counter = length * 4.0;
}
else if (length.CompareTo(0.75) == -1)
{
counter = 1.0 - (length - 0.25) * 4.0;
}
else
{
counter = -1.0 + (length - 0.75) * 4.0;
}
samplecounter++;
samplecounter = samplecounter > samplesperwave ? 0 : samplecounter;
return counter;
}
提前致谢! //垫子
您的问题不是精度之一。 问题是您的函数没有定义三角波。 每次将samplecounter
重置为0时,该函数的下一个返回值samplecounter
0。将下一个舍入长度设置为0,则执行第一个if分支,并将counter设置为0。
遇到这样的问题时,应绘制输出。 如果这样做,您将立即发现您的函数不会产生三角波形。 这是一些执行的代码:
static double GetNextTriangle(double frequency)
{
double samplesperwave = Parent.WaveFormat.SampleRate / frequency;
double t = samplecounter / samplesperwave;
counter = 1.0 - 4.0 * Math.Abs(Math.Round(t - 0.25) - (t - 0.25));
samplecounter++;
return counter;
}
再次强调,您必须对代码的输出进行绘图和可视化以深入了解其行为。
为了尝试更好地了解问题所在,我们将数据可视化。 这是2 Hz的波形的两个周期,每秒41个采样:
样本20周围出现了一个点。样本似乎未正确拟合三角波。
让我们回顾一下三角波的数学原理。 您正在做的是在离散点采样连续函数。 首先,以赫兹定义一个频率为f
(或1/T
)的连续三角波:
tri(t) = { 4*f*t, 0 <= t < T/4 }
= { -4*f*(t - T/2), T/4 <= t < 3*T/4 }
= { 4*f*(t - T), 3*T/4 <= t < T }
= tri(t - T) [it's periodic!]
现在,您要对该连续函数进行采样。 因此,您可以定义每秒采样中的采样率s
(或1/U
)。 现在,第n
个样本将只是tri(n*U)
:
tri[n] = tri(n*U)
= { 4*f*n*U, 0 <= n*U < T/4 }
= { -4*f*(n*U - T/2), T/4 <= n*U < 3*T/4 }
= { 4*f*(n*U - T), 3*T/4 <= n*U < T }
让我们通过定义归一化周期P = T/U
和归一化频率F = f/s = U/T
对其进行清理:
tri[n] = { 4*F*n, 0 <= n < P/4 }
= { -4*F*(n - P/2), P/4 <= n < 3*P/4 }
= { 4*F*(n - P), 3*P/4 <= n < P }
现在我们得到:
不必担心提示中明显的“斑点”; 他们是意料之中的,您不应该避免它们。
这是代码:
public static double GetNextTriangle(int sample, double frequency, double sampleRate)
{
double T = 1d / frequency;
double U = 1d / sampleRate;
double P = T / U;
double F = U / T;
double n = (double)sample;
n %= P; // restrict n to the domain [0, P)
if ((n >= 0) && (n < (P / 4d)))
{
return 4d * F * n;
}
else if ((n >= (P / 4d)) && (n < (3d * P / 4d)))
{
return -4d * F * (n - (P / 2d));
}
else // if ((n >= (3d * P / 4d)) && (n < P))
{
return 4d * F * (n - P);
}
}
此处的浮点运算条件良好。 但是,如果1 / frequency不是1 / sampleRate的倍数,则可能会遇到与采样有关的别名问题。
如果可以,请尝试使用此matlab代码
sampleRate=5000;
frequency=700;
sampleperwave=sampleRate/frequency;
samplecounter=0:floor(sampleperwave);
samplecounter=repmat(samplecounter,1,5);
length=samplecounter/sampleperwave;
wave=-1+4*(length-0.75);
wave(length<0.75)=1-4*(length(length<0.75)-0.25);
wave(length<0.25)=4*length(length<0.25);
figure; stem(wave); hold on; plot(wave,'r')
您可以尝试将samplecounter声明为double并以模为单位递增
samplecounter++;
samplecounter = samplecounter % samplesperwave;
或回到Matlab
samplecounter=0:length(samplecounter)-1;
samplecounter=rem(samplecounter,sampleperwave);
len=samplecounter/sampleperwave;
wave=-1+4*(len-0.75);
wave(len<0.75)=1-4*(len(len<0.75)-0.25);
wave(len<0.25)=4*len(len<0.25);
figure; stem(wave); hold on; plot(wave,'r')
整个方法似乎是错误的。 我会选择这样的东西:
public class RectWave
{ // triangular wave in range [0,1] with given frequency
public double GetNextSample()
{
if (_ptr == _wave.Length)
_ptr = 0;
return _wave[_ptr++];
}
public RectWave(int sampleRate = 441000, int period = 20)
{
_sampleRate = sampleRate;
_period = period;
_wave = new double[(int)(_sampleRate * _period / 1000.0)]; // one cycle
_ptr = 0;
BuildWave();
}
private void BuildWave()
{
double dt = 1000.0 / _sampleRate; // in msec
double slope = 1.0 / (_period / 2.0); // 1.0 => peek
for (int i=0; i <= _wave.Length/2; ++i)
{
_wave[i] = _wave[_wave.Length - i - 1] = i * dt * slope;
}
}
private int _sampleRate; // in Hz
private int _period; // in msec
private double[] _wave; // the data
private int _ptr; // current sample
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.