公司被人买下, 换了老板. 他做过一个固定利率(FRM)的 Prepayment 模型, 程序是用C++写的. 他叫我用他的框架做个可调利率(ARM)的模型, 即两者必须用同一程序, 同时还要写成C++程序. 工作的第一步是读懂他的模型, 这步不难. 因为模型测试必须在 Unix 上用 C++ 做, 所以在开始建立模型之前还必须读懂他的程序. 读着读着就问题来了, 模型中的对数和指数函数全部换成了部分分式, 即两个多项式之商. 这想来是用什么恒等式推出来的. 我想凭我的功力, 应该没问题. 结果根本不可能, 我最后猜测, 这是某种近似公式. 但准确公式在那儿, 为什么要近似, 我实在想不出, 我只好去问老板怎么回事. 答案出乎意料的简单, 节约 CPU. 他一语道破天机后, 其余的道理就很容易想明白了. 所谓”准确”公式, 实际上只是方便的公式而已. 这种涉及到人类思维的模型, 根本不可能真正准确. 这些任何语言中都有的现成函数, 尽管直接使用很方便且精炼, 但极其耗费时间. 算这些东西计算机也没有好办法, 只能用级数展开. 它与加减乘相比, 时间可多化几十甚至100 倍. 换成部分分式后, 尽管看起来麻烦多了, 但十几个加减乘除, 花的时间比那函数还是要少的多. 我老板把这些部分分式的系数通过最小二乘法来确定, 得到精度非常高. X 的低次方, 比如 4, 如写成X**4, 看上去很漂亮, 但耗时极多. 自从知道了”地雷”的秘密, 我就把它写成X*X*X*X, 甚至把以前写的程序都一一改过来, 否则简直有一种罪恶感. 但对于多项式, 还有更进一步的优化方法, 在大部分关于数值计算的书中都能找到. 假定是个4次多项式, 如写成 a + b X + C XX + d XXX + e XXXX, 尽管比指数形式要快得多, 但还能继续优化. 我做过检验, 计算机做加减乘的时间是一样的, 所以这相当于14次加法. 但如写成 a + X (b + X ( c + X (d + e X))), 只做 8 次加法, 改进幅度远大于许多人的想象. 一般来说, 对一个 N 次多项式, 前者要做 N(N+3)/2 次加法, 后者只要做 2N 次加法, 即使 N 只是 4 这种不算大的数字, 差别已经十分巨大. 计算机和人一样, 做除法特别慢. 根据《十万个为什么》, 几十年前的计算机,除法和加法的差别是 1:5. 对于现代化计算机, 这比例大约是 1: 2.3, 所以减少除法运算是节约 CPU 的一个重要方面. 我的程序中除以 2 从来是写成乘以 0.5, 除以3就要看我心情了. 数值计算中常用的 Simpson 积分, 前面有个除以三, 我向来是不做的, 要等到积分收敛后再除, 只除一次. 如果迭代一共进行了20次, 这就节约了44次加减乘. 我还从一位计算机专家那儿学到这么一招. 在 C 和 C++ 中, 假如有一项 C/3, 计算机就会任劳任怨地每次都给你做一遍除法. 但是如写成 C * (1/3), 这除法就会在编译(Compile)时完成, 在实际运算时只要做一次乘法就可以了. 有一次, 老板让我看他写的一段程序, 其中有 exp(X), X 大于零. 他将大于一的部分用现成的函数算, 把计算精度较高且使用频率也较高的小于一的部分用上下各四次的部分分式来近似: (1 + X (b + X (c + X (d +e X)))) / (1 - X (b + X (c - X (d +e X)))). 共 16 次加法和一次除法. 我将它改成 Y = X * X U = 1 + Y (c + e Y) V = X (b + d Y). 整个表达式成为 (U + V) / (U – V), 一共 10 次加法,一次除法. 除去无法可想的除法, 其余部分减少了 37.5%. 在这方面颇为厉害的老板对这一改进还是刮目相看的. 这位老板是研究场论的理论物理博士, 理论功底相当坚实, 他曾证明过Ho-Lee 模型与路径无关的充分条件. 用最小二乘法确定部分分式的系数, 从没在文献中见过, 很可能是他的原创. 尽管它只用到了高中代数这一碗水, 但要想到这一点, 没有一桶水是难以想象的. 要想出这儿提到的各种优化, 对计算机的计算原理必须相当熟悉, 但要理解他们, 需要的只是高中甚至初中代数的知识. |