36

是否有任何可用的函数定义,如, , , , sqrt()sin()这些cos()来自tan()math.h /cmath)?log()exp()

我只是想知道它们是如何工作的。

4

8 回答 8

66

这是一个有趣的问题,但是除非您碰巧知道使用的方法,否则阅读高效库的资源不会让您走得太远。

这里有一些提示可以帮助您理解经典方法。我的信息绝不准确。以下方法仅为经典方法,具体实现可以使用其他方法。

  • 查找表经常使用
  • 三角函数通常通过CORDIC算法(在 CPU 上或使用库)实现。请注意,通常正弦和余弦是一起计算的,我一直想知道为什么标准 C 库不提供sincos函数。
  • 平方根使用牛顿方法和一些巧妙的实现技巧:您可能会在网络上的某个地方找到来自 Quake 源代码的摘录,其中包含令人难以置信的 1 / sqrt(x) 实现。
  • 指数和对数使用 exp(2^nx) = exp(x)^(2^n) 和 log2(2^nx) = n + log2(x) 使参数接近零(对数为一)并使用有理函数逼近(通常是Padé 逼近)。请注意,这个完全相同的技巧可以得到矩阵指数和对数。根据@Stephen Canon 的说法,现代实现倾向于泰勒展开而不是有理函数逼近,其中除法比乘法慢得多。
  • 可以从这些函数中推导出其他函数。实现可以提供专门的例程。
  • pow(x, y) = exp(y * log(x)),所以当 y 为整数时不使用 pow
  • hypot(x, y) = abs(x) sqrt(1 + (y/x)^2) if x > y (hypot(y, x) 否则)以避免溢出。atan2是通过调用sincos和一点逻辑来计算的。这些函数是复杂算术的构建块。
  • 对于其他超越函数(gamma、erf、bessel、...),请参阅优秀的书Numerical Recipes, 3rd edition了解一些想法。good'old Abramowitz & Stegun也很有用。在http://dlmf.nist.gov/上有一个新版本。
  • Chebyshev 近似、连分数展开(实际上与 Padé 近似)或幂级数节约等技术用于更复杂的函数(例如,如果您碰巧阅读了 erf、bessel 或 gamma 的源代码)。我怀疑它们在裸机简单的数学函数中有真正的用途,但谁知道呢。有关概述,请参阅数字食谱。
于 2010-12-27T19:39:43.060 回答
21

每个实现可能不同,但您可以从 glibc(GNU C 库)源代码中查看一个实现。

编辑:谷歌代码搜索已下线,所以我的旧链接无处可去。

glibc 的数学库的源代码位于此处:

http://sourceware.org/git/?p=glibc.git;a=tree;f=math;h=3d5233a292f12cd9e9b9c67c3a114c64564d72ab;hb=HEAD

于 2010-12-27T19:14:40.000 回答
7

看看如何glibc实现各种数学函数,充满魔力、逼近和汇编。

于 2010-12-27T19:16:41.107 回答
5

一定要看看fdlibm源代码。它们很好,因为 fdlibm 库是自包含的,每个函数都有详细的文档说明,并详细解释了所涉及的数学,并且代码非常清晰易读。

于 2010-12-28T01:45:36.067 回答
4

看过很多数学代码后,我建议不要看 glibc - 代码通常很难理解,并且很大程度上取决于 glibc 的魔法。FreeBSD 中的数学库更容易阅读,尽管有时速度较慢(但不是很多)。

对于复杂函数,主要困难是边界情况——正确的 nan/inf/0 处理对于实际函数来说已经很困难了,但对于复杂函数来说却是一场噩梦。C99标准定义了很多corner case,有些函数很容易就有10-20个corner case。您可以查看最新的C99 标准文档的附件 G以获得一个想法。long double 也有一个困难,因为它的格式不是标准化的——根据我的经验,你应该预料到 long double 会有很多错误。希望即将到来的具有扩展精度的修订版 IEEE754 将改善这种情况。

于 2010-12-28T03:06:24.357 回答
0

大多数现代硬件都包括非常有效地实现这些功能的浮点单元。

于 2010-12-28T10:58:23.663 回答
-1

用法:根(数字,根,深度)

示例:root(16,2) == sqrt(16) == 4
示例:root(16,2,2) == sqrt(sqrt(16)) == 2
示例:root(64,3) == 4

C# 中的实现

double root(double number, double root, double depth = 1f)
{
    return Math.Pow(number, Math.Pow(root, -depth));
}

用法:Sqrt(数字,深度)

示例:Sqrt(16) == 4
示例:Sqrt(8,2) == sqrt(sqrt(8))

double Sqrt(double number, double depth = 1) return root(number,2,depth);

作者:Imk0tter

于 2020-05-09T17:05:20.520 回答
-16

这些几乎总是作为系统调用实现的。如果您想查看源代码,则需要访问操作系统源代码,这意味着您需要查看像 Linux 或 BSD 这样的开源操作系统。

于 2010-12-27T19:13:00.560 回答