Python没有';t运行Euler';宽度小于0.2的s方法?

Python没有';t运行Euler';宽度小于0.2的s方法?,python,Python,我对python编码有一个奇怪的问题。我有一个简单的代码,用来执行Euler近似,数值上近似一个微分方程的解。它通过取一段曲线并将其划分为等宽“w”的间隔来实现 代码是: import math x = 0 y = 2 w = 0.5 while x < 1: dydx = 1 - 2*x + y deltaY = dydx*w y = y + deltaY x += w print(x,y) 导入数学 x=0 y=2 w=0.5 当x

我对python编码有一个奇怪的问题。我有一个简单的代码,用来执行Euler近似,数值上近似一个微分方程的解。它通过取一段曲线并将其划分为等宽“w”的间隔来实现

代码是:

import math

x = 0
y = 2
w = 0.5

while x < 1:
    dydx = 1 - 2*x + y
    deltaY = dydx*w
    y = y + deltaY
    x += w
print(x,y)
导入数学
x=0
y=2
w=0.5
当x<1时:
dydx=1-2*x+y
deltaY=dydx*w
y=y+deltaY
x+=w
打印(x,y)
奇怪的是,我发现该代码适用于1到1/5之间的“w”,但不会更小

例如,使用w=1/5,代码将正确输出(1.0、5.48832…)

或使用w=1/4。代码正确输出(1.0、5.4414…)

但是如果我使用w=1/6,输出是(1.16667,6.27523…)

我已经为运行Euler的修改方法和Romberg的方法(用于近似相同的事情)的程序改编了相同的代码,并且它们对w<1/5做了相同的事情

我觉得这个问题的答案要么非常明显,要么非常模糊。如果有人有解决办法,我将非常感激


谢谢

0.2的截止值只是一个巧合。这里真正发生的是浮点舍入

float
值不能准确表示大多数分数;它们只给你最接近你想要的数字的52位二进制分数。这会导致舍入错误

  • 如果将1/2与0相加两次,则正好得到1
  • 如果你把1/3和0相加三次,你会得到一个比1大一点点的数字,但是1实际上是最接近这个数字的二进制分数
  • 如果将1/4与0相加四次,则正好得到1
  • 如果你把1/5和0相加五次,你会得到一个比1大一点的数字
  • 如果你把1/6和0相加六次,你会得到一个比1小一点的数字
  • 如果你把1/7和0相加七次,你会得到一个比1小一点的数字
  • 如果将1/8与0相加四次,则正好得到1
所以,1/3是好的,因为它恰好取整为1;1/5很好,因为当
x
稍微大于1时,
x<1
为false,循环停止。但是1/6和1/7是不好的,因为当
x
比1小一点点时,
x<1
仍然是真的,所以循环次数过多了一次


最简单的修复方法是使用
isclose

while not math.isclose(x, 1):
…虽然这意味着如果x不是非常接近一元分数,则会有一个无限循环。当然,您的方法不适用于这样的值,但最好是得到一个错误或不正确的结果,而不是等到世界末日。所以你可能想做一些更聪明的事情,比如:

while x < 0.999999:
现在您将获得:

1 5.521626371742112

但最好的选择可能是跟踪
w
1/6
,如下所示:

import math

x = 0
y = 2
w_inv = 6
w = 1/w_inv

for _ in range(w_inv):
    dydx = 1 - 2*x + y
    deltaY = dydx*w
    y = y + deltaY
    x += w
print(x,y)
现在舍入误差不是问题;无论如何,我们肯定要循环6次

0.9999999999999999 5.521626371742112

0.2的截止值只是一个巧合。这里真正发生的是浮点舍入

float
值不能准确表示大多数分数;它们只给你最接近你想要的数字的52位二进制分数。这会导致舍入错误

  • 如果将1/2与0相加两次,则正好得到1
  • 如果你把1/3和0相加三次,你会得到一个比1大一点点的数字,但是1实际上是最接近这个数字的二进制分数
  • 如果将1/4与0相加四次,则正好得到1
  • 如果你把1/5和0相加五次,你会得到一个比1大一点的数字
  • 如果你把1/6和0相加六次,你会得到一个比1小一点的数字
  • 如果你把1/7和0相加七次,你会得到一个比1小一点的数字
  • 如果将1/8与0相加四次,则正好得到1
所以,1/3是好的,因为它恰好取整为1;1/5很好,因为当
x
稍微大于1时,
x<1
为false,循环停止。但是1/6和1/7是不好的,因为当
x
比1小一点点时,
x<1
仍然是真的,所以循环次数过多了一次


最简单的修复方法是使用
isclose

while not math.isclose(x, 1):
…虽然这意味着如果x不是非常接近一元分数,则会有一个无限循环。当然,您的方法不适用于这样的值,但最好是得到一个错误或不正确的结果,而不是等到世界末日。所以你可能想做一些更聪明的事情,比如:

while x < 0.999999:
现在您将获得:

1 5.521626371742112

但最好的选择可能是跟踪
w
1/6
,如下所示:

import math

x = 0
y = 2
w_inv = 6
w = 1/w_inv

for _ in range(w_inv):
    dydx = 1 - 2*x + y
    deltaY = dydx*w
    y = y + deltaY
    x += w
print(x,y)
现在舍入误差不是问题;无论如何,我们肯定要循环6次

0.9999999999999999 5.521626371742112

我猜这是一个浮点精度问题。我不知道你想计算什么,所以我无法详细解释到底发生了什么,但我怀疑你会发现,在某个地方,你应该得到
x==1.0
,但由于四舍五入,你反而得到了一些小数目,这个错误会导致额外的迭代,从而导致错误的输出。您希望浮点和正好落在1上。我猜这是一个浮点精度问题。我不知道你想计算什么,所以我无法详细解释到底发生了什么,但我怀疑你会发现,在某个地方,你应该得到
x==1.0
,但由于四舍五入,你反而得到了一些小数目,这个错误会导致额外的迭代,导致错误的输出。您希望浮点和正好落在1上。