【发布时间】:2018-02-15 00:28:02
【问题描述】:
我的函数实现的实验结果存在一些问题,我希望其他人验证我使用的函数在逻辑上是合理的。
上下文:对于某个编程/数学问题,我需要计算一个连续间隔的平均值,精确到小数点后 10 位。该函数相当复杂并且涉及两个维度,因此我更愿意自己执行求和近似而不是计算连续平均值(因此必须在两个维度上对函数进行积分)。
为了帮助我,我一直在研究 Rust 中的一组近似函数。他们使用黎曼和、梯形法则或辛普森法则,计算函数 f 在区间 a..b 上的离散平均值,并使用固定增量 delta,具体取决于实现:
pub fn riemann_avg<F>(a: f64, b: f64, delta: f64, f: F) -> f64
where
F: Fn(f64) -> f64
{
let n = ((b - a) / delta) as usize;
(0..n)
.map(|i| (i as f64) * delta)
.map(f)
.sum::<f64>() / (n as f64)
}
pub fn trapezoidal_avg<F>(a: f64, b: f64, delta: f64, f: F) -> f64
where
F: Fn(f64) -> f64
{
let n = ((b - a) / delta) as usize;
(0..n)
.map(|i| (i as f64) * delta)
.map(f)
.collect::<Vec<f64>>()
.windows(2)
.map(|xs| xs[0] + xs[1])
.sum::<f64>() / (2.0 * (n as f64))
}
pub fn simpson_avg<F>(a: f64, b: f64, delta: f64, f: F) -> f64
where
F: Fn(f64) -> f64
{
let n = ((b - a) / delta) as usize;
(0..n)
.map(|i| (i as f64) * delta)
.map(f)
.collect::<Vec<f64>>()
.windows(3)
.map(|xs| xs[0] + 4.0 * xs[1] + xs[2])
.sum::<f64>() / (6.0 * (n as f64))
}
(旁注:我上面写的simpson_avg 函数实际上是辛普森规则的两个偏移应用的平均值,因为它使程序不那么复杂。目前,我不相信这是问题。)
我使用多个函数x.atan()、x.powi(4)、x 测试了每种方法的误差收敛性,使delta 接近于零,积分范围从0.0 到1.0。
delta riemann_err trap_err simpson_err
(smaller is better)
x.atan():
0.1000000000 0.0396869230 0.0763276780 0.1136616747
0.0100000000 0.0039311575 0.0078330229 0.0117430951
0.0010000000 0.0003927407 0.0007851897 0.0011777219
0.0001000000 0.0000392703 0.0000785377 0.0001178060
0.0000100000 0.0000073928 0.0000113197 0.0000152467
0.0000010000 0.0000003927 0.0000007854 0.0000011781
0.0000001000 0.0000000393 0.0000000785 0.0000001178
x.powi(4):
0.1000000000 0.0466700000 0.0794750000 0.1081733333
0.0100000000 0.0049666670 0.0097696470 0.0145089140
0.0010000000 0.0004996667 0.0009976697 0.0014950090
0.0001000000 0.0000499967 0.0000999767 0.0001499500
0.0000100000 0.0000129997 0.0000179993 0.0000229989
0.0000010000 0.0000005000 0.0000010000 0.0000015000
0.0000001000 0.0000000500 0.0000001000 0.0000001500
x:
0.1000000000 0.0500000000 0.0950000000 0.1400000000
0.0100000000 0.0050000000 0.0099500000 0.0149000000
0.0010000000 0.0005000000 0.0009995000 0.0014990000
0.0001000000 0.0000500000 0.0000999950 0.0001499900
0.0000100000 0.0000100000 0.0000149999 0.0000199999
0.0000010000 0.0000005000 0.0000010000 0.0000015000
0.0000001000 0.0000000500 0.0000001000 0.0000001500
我预计辛普森规则在所有这些函数中收敛速度最快,但正如您所见,它的收敛速度最差,黎曼和表现最好。
对我来说,这毫无意义,尤其是对于简单的多项式示例,辛普森规则显然会提供更好(或至少等效)的近似值。我猜这意味着我的函数逻辑/公式存在非常微妙的问题,或者我遇到了浮点精度错误。我希望在诊断此问题方面得到一些帮助。
【问题讨论】:
-
如果
x你的意思是函数f(x) = x那么你的输出没有意义。一方面,所有 delta 的梯形误差都应为 0,因为所讨论的区域 是 梯形的区域。此外,使用所有这些功能,辛普森误差应该会比这快得多,快得多。无论您对这两种方法的实现做了什么,它都不起作用。我对 Rust 的了解还不够多。为什么不看看是否可以重现手动计算的输出?浮点错误不能解释这一点。你有一个错误。 -
FWIW,您忘记在所有三个版本中添加回
a。不过,不确定您真正的问题是什么——您为什么不尝试创建minimal reproducible example? -
@trentcl 你能详细说明“加回
a”是什么意思吗?我从未听说过该公式的那部分。另外,谢谢你的建议;我现在正在开发一个 MCVE。 -
不妨看看我的
FloatIterator:stackoverflow.com/a/47869373/1478356 -
是的,我表达得不是很好。 Shepmaster 的回答详细说明,但如果您尝试从 1.0 积分到 2.0(或任何不从 0 开始的区间),它将执行错误的区间。