跳转到内容

编程科学/最简单的事

来自维基教科书,开放世界中的开放书籍

如果你学习过CME的第4章,你就会学习到对简单多项式函数(称为)进行微分的幂法则。一项具有以下形式

   

其中a被称为项的系数,n被称为项的指数

以下是项的幂法则

   

例如,考虑多项式

   

如果幂法则正确,则导数应该为

   

让我们测试一下。我们首先定义一个函数来表示多项式

   function y(x)
       {
       2 * (x ^ 3);
       }

和以前一样,因变量成为函数名,自变量成为形式参数。


在我们继续之前,现在是时候学习关于使用Sway与文件,因为在解释器中输入函数定义可能相当痛苦。

因此,在本册书的其余部分,即使显示了与解释器的交互,你也应该将Sway程序存储在文件中,并从文件中运行Sway程序。


导数,或可以用这个函数来表示

   function dy/dx(x)
       {
       6 * (x ^ 2);
       }

有没有办法测试幂法则是否正确?回想一下我们上一章中修改过的比率函数

   function ratio(x,dx,y)
       {
       var dy = y(x + dx) - y(x);
       dy / dx;
       }

如你所知,它计算的比率是给定x的导数的近似值。请注意,我们已将形式参数的名称从w更改为xdw更改为dxh更改为y,以反映我们现在正在处理多项式的事实。[1]

我们认为ratio(对于较小的dx)会产生一个数字,该数字应该接近函数dy/dx产生的数字。

让我们定义一个测试函数

   function test(x)
       {
       var dx = 0.000001;
       
       println("for x = ",x);
       inspect(ratio(x,dx,y));
       inspect(dy/dx(x));
       println();
       }

给定x的值,测试函数将打印出两个结果。现在来说一些命名法。当我们使用ratio函数时,我们是在数值地求导数。也就是说,我们使用dx的一个小数字,找到相应的dy,然后计算比率。另一方面,使用幂法则(如函数dy/dx所体现的),我们符号地求导数,因为我们从未真正计算过实际的比率。

    sway> test(10);
    for x = 10
    ratio(x,dx,y) is 600.00005965
    dy/dx(x) is 600
      
    sway> test(20);
    for x = 20
    ratio(x,dx,y) is 2400.0001231
    dy/dx(x) is 2400
      
    sway> test(30);
    for x = 30
    ratio(x,dx,y) is 5400.0001837
    dy/dx(x) is 5400

对于这个测试,我们看到我们的数值解和符号解之间有很好的一致性。

显然,符号解是首选,因为

  • 它是精确的
  • 它更容易计算

你可能会问自己,我们能编写一个程序来符号地求导数吗?

我们方法的缺陷

[edit | edit source]

如果我们能使用符号方法来求导数,而不是使用基于比率的数值方法,那就太好了。问题是,我们用来表示一项的函数

   function y(x)
       {
       2 * (x ^ 3);
       }

的系数为2,指数为3,是硬编码的。如果我们得到了这样的函数,但不知道内部细节(例如硬编码指数的值),我们就无法创建导数函数,因为我们需要知道指数的值才能做到这一点,并且没有办法获取它。我们需要想办法从函数中提取指数,以便我们可以使用它来构建导数函数。

下一节将向你展示一种从函数中提取组件的方法。该方法使用对象


如果你不知道什么是对象,或者如何在Sway中创建对象,请阅读使用对象的相关内容,这些内容位于Sway 参考手册中。


好的,现在你已经熟悉了对象,我们可以开始使用对象编写一个程序来符号地求导数。

多项式对象和幂法则

[edit | edit source]

我们不使用函数来表示多项式y,而是使用对象。通过学习参考手册,你应该知道对象只是一个环境,环境只是一个变量及其值的表格。你应该还知道,你使用函数来创建对象,而创建对象的函数通常称为构造函数。最后,你应该知道,为了定义一个构造函数,你需要让该函数返回预定义变量this

以下是一个简单多项式对象的构造函数。我们将构造函数命名为term

   function term(a,n)
      {
      function value(x)
          {
          a * (x ^ n);
          }
       this;
       }

请注意,构造函数包含一个内部函数,用于在给定x的情况下计算y的值,就像之前一样。我们使用运算符来调用该函数。

   sway> var y = term(2,3);
   OBJECT: <OBJECT 1671>
   sway> y . value(4);
   INTEGER: 128
   sway> y . value(5);
   INTEGER: 250

实际上,确实是128,而确实是250。

现在是重点!将多项式项存储为对象而不是函数,使我们能够提取系数a和指数n

   sway> y . a;
   INTEGER: 2;
   sway> y . n;
   INTEGER: 3;

我们甚至可以漂亮地打印对象y来查看其所有字段

   sway> pp(y);
   <OBJECT 2561>:
       context: <OBJECT 749>
       dynamicContext: <OBJECT 749>
       callDepth: 1
       constructor: <function term(a,n)>
       this: <OBJECT 2561>
       value: <function value(x)>
       a: 2
       n: 3
   OBJECT: <OBJECT 2561>

我们看到,在其他字段中,an的值是正确的。稍后,你将了解预定义字段的含义和用途:contextdynamicContextconstructor。你已经知道this字段了。

我们希望访问an,因为幂法则需要它们来计算导数多项式。现在我们可以写出一个表达式,让我们计算导数

   sway> var coeff = y . a;
   sway> var exp = y . n;
   sway> var z = term(coeff * exp,exp - 1);
   OBJECT: <OBJECT 2783>
   sway> z . a;
   INTEGER: 6
   sway> z . n;
   INTEGER: 2
   sway> z . value(4)
   INTEGER: 96

当然,我们可以编写一个函数来完成这个任务

   function powerRule(t)
       {
       var coeff = t . a;
       var exp = t . n;
       term(coeff * exp,exp - 1);
       }

请注意,powerRule函数以一个term对象作为参数,并返回一个表示导数的新term对象。这是因为新项的系数为a * n指数为n - 1正如幂法则所规定的一样。让我们检查一下,确保我们的幂法则按预期工作

   sway> var y = term(2,3);
   sway> var z = powerRule(y);
   
   sway> y . value(4);
   INTEGER: 128
   sway> z . value(4);
   INTEGER: 96

确实如此!

此外,漂亮地打印z会显示出an的正确值

   sway> pp(dy/dx);
   <OBJECT 2922>:
       context: <OBJECT 749>
       dynamicContext: <OBJECT 2808>
       callDepth: 2
       constructor: <function term(a,n)>
       this: <OBJECT 2922>
       value: <function value(x)>
       a: 6
       n: 2
   OBJECT: <OBJECT 2922>

你可能没有注意到,我们有点缺乏对称性。虽然项对象有一个用于计算其值的内部函数,但我们使用外部函数powerRule来计算其导数。当我们使用对象编程时,我们尽可能使用内部函数。因此,让我们重写term函数,以便它可以计算自己的导数

   function term(a,n)
       {
       function value(x)
           {
           a * (x ^ n);
           }
       function diff()
           {
           term(a * n,n - 1);
           }
       this;
       }

我们将这个新的内部函数命名为diff,以表示取我们对象的微分。请注意,我们在diff函数内部不再使用变量coeffexp,而是直接使用an

最后,我们在term中添加了第三个内部函数。它用于以比使用pp函数更简洁的方式可视化我们的对象。按照惯例,我们将这个函数称为toString

   function term(a,n)
       {
       function value(x)
           {
           a * (x ^ n);
           }
       function diff()
           {
           term(a * n,n - 1);
           }
       function toString()
           {
           string(a) + "x^" + string(n);
           }
       this;
       }

toString函数将系数和指数都转换为字符串,然后将这些字符串连接在一起,形成一个项的字符串表示形式

   sway> y = term(2,3);
   sway> z = y . diff();
   sway> y . toString()
   STRING: "2x^3"
   sway> x . toString()
   STRING: "6x^2"

请注意,使用toString函数来提取项的系数和指数有多么容易。另一种表达toString函数的方法是利用这样一个事实:如果你添加一个字符串和一个数字(字符串位于加号的左侧,数字会自动转换为字符串),再加上Sway从左到右组合数学运算符的事实,我们可以删除n的字符串转换

   function toString()
       {
       string(a) + "x^" + n;
       }

如果我们使用空字符串来开始表达式,我们也可以删除第一个字符串转换

   function toString()
       {
       "" + a + "x^" + n;
       }

一个完整的对象系统

[edit | edit source]

从现在开始,我们将开发一个系统来寻找各种数学对象的导数(最终还有积分),这些对象的类型只受我们的想象力和注意力范围的限制。我们系统中的每个对象都将执行(至少)三项操作。首先是在给定位置计算其值。第二是计算其导数(最终还有其积分)。第三是计算其可视化。因此,我们所有的数学对象都将具有difftoString内部函数或方法[2]

即使是最简单的现实世界数学对象,例如数字和变量,也需要重新创建为 Sway 对象。这样,我们就可以随时获取对象的导数,而不必先询问获取导数是否有意义。

问题

[edit | edit source]

所有公式均使用小学优先级编写。

1. 为什么 Sway 实现的 有括号2 * (x ^ 3)?

2. 使用powerRule函数,可以对导数求导吗?解释一下。

3. 编写一个函数,使用 power rule 函数回答 Thompson 第 58 页上的以下问题:1、2、4、6、7。将你的函数命名为p58。你的函数应该计算然后打印问题的答案。

4. 使用纸笔完成 Thompson 第 58 页上的练习 3、8、9 和 10。

脚注

[edit | edit source]
  1. 通常,形式参数的名称并不重要,因此我们可以保留ratio函数不变,它仍然可以完全正常工作。
  2. 在面向对象编程(另一种说法是使用对象编程)的世界中,这些内部函数被称为方法。从现在开始,我们将使用术语方法。请记住,方法只是一个内部函数。


一些这样,一些那样 · 持续的担忧

华夏公益教科书