计算给定定点类型的正弦到满精度,何时停止?

时间:2018-02-02 18:28:54

标签: c sine taylor-series

我正在为我自己的定点库编写一个正弦实现而不使用预先计算的表或库函数,所以只需要基本的数学运算(加,减,乘,除)。我想将此正弦值计算为结果所在的任何固定点类型的完整精度,例如一个有16个小数位。

据我所知泰勒系列是这样做的唯一方法,而且我添加的术语越多越精确,但如何确定何时停止添加术语?仅检查下一个术语是否小于目标精度就足够了吗?以这种方式使用泰勒系列是否实用,还是需要使用其他东西?

我正在使用C并且想要使我的定点类型(或类型)的小数位数可配置,这就是为什么我需要能够以这种方式推广我的停止条件。

2 个答案:

答案 0 :(得分:0)

以下不是定点解决方案,而是浮点解决方案。因此,它可以为固定点1提供洞察力。

它确实使用库函数remainder()来减少范围,但是一旦表达了详细的编码目标,它也可以被替换。

它使用递归首先将较小的术语添加到一起。使用更高精度的浮点数,这可能会导致深度堆栈偏移。

此处的递归终止是1.0的测试,这对于浮点是有意义的。我期望与epsilon进行比较以获得固定点。

static double sin_r_helper(double term, double xx, unsigned i) {
  if (1.0 + term == 1.0) return term;
  return term - sin_r_helper(term * xx / ((i + 1) * (i + 2)), xx, i + 2);
}

double sin_r(double x /* radians */) {
  x = remainder(x, 2 * M_PI);
  return x * sin_r_helper(1.0, x * x, 1);
}

答案 1 :(得分:0)

  

计算正弦到给定定点类型的全精度,何时停止?

这部分有点简单。将正弦(x)代码限制为N个多项式项的近似误差大约是泰勒级数的N + 1 th 项的值。

对于有限范围的x [0 ...π/ 4],15位需要约3个项。见正弦series definitions

第4项是(π/ 4) 7 / 7!或者<2> 14.7 中的3.7e-5或约1份。 Trig标识可以处理数字范围的其余部分。

在尝试创建uint16_t sin_f(uint16_t)早期问题时,如何扩展输入和输出。

数学正弦的结果是[-1.0 ... + 1.0]。通过仅计算正弦的前90度,范围为[0.0 ... + 1.0]。使用2的幂来缩放它可以使用[0 ... 65536],但结尾是包含的,因此结果将不适合uint16_t。也许用[0 ... 32768]?

OP暗示1.0是一次旋转,360度或2 *π弧度。所以输入是一个16位的分数[0 ... 65535]。

以下是使用4项多项式的适度尝试。术语是通过一些excel曲线拟合技术找到的,并不一定是最佳的。它有两个已知的问题:产生范围[0 ... 32769](可以稍微调整一点来修复)和最坏情况下的4个结果。 (关闭2是我的目标。)它确实为OP提供了一些想法。正如其他人所说,这不是微不足道的,并且变体固定宽度的动态解决方案看起来非常困难。一个恒定的16位定点很难。

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

uint16_t mul16(uint16_t a, uint16_t b, int shift) {
  uint32_t y32 = a;
  y32 *= b;
  y32 >>= shift - 1;
  if (y32 & 1) {
    y32 >>= 1;
    y32 += 1;
  } else {
    y32 >>= 1;
  }

  if (y32 > 0xFFFF) {
    printf("@@@@@@ %u %u %llu %d\n", 1u*a, 1u*b, 1llu*y32, shift);
    exit(0);
  }
  uint16_t y16 = (uint16_t) y32;
  return y16;
}

// 0 to 0x4000  (map to 0 to 90 degrees or 0 to pi/2 R)
// pseudo code:  = x*(1 - b*x^2 + c*x^4 - d*x^6)
uint16_t sine_fixed(uint16_t x) {
  const uint16_t t3 = 53902; // 431214.77/8;
  const uint16_t t5 = 64636; // 129272.18/2;
  const uint16_t t7 = 58833; // 58833.22

  uint16_t xx = mul16(x, x, 16);
  uint16_t term = 51472; // 2*pi*65536 / 8
  uint16_t sum = term;

  term = mul16(mul16(term, xx, 16 - 3), t3, 16 - 0);
  sum = (uint16_t) (sum - term);

  term = mul16(mul16(term, xx, 16 - 1), t5, 16 - 0);
  sum = (uint16_t) (sum + term);

  term = mul16(mul16(term, xx, 16 - 0), t7, 16 - 0);
  sum = (uint16_t) (sum - term);

  uint16_t y = mul16(x, sum, 16 - 3 + 1);
  return y;
}

测试代码

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

void sin_fixed_test(uint16_t x) {
  double pi = acos(-1);
  uint16_t y = sine_fixed(x);
  double X = x * 2*pi / 65536.0;
  double Y = sin(X);
  long ye =  lrint(Y * 65536.0/2);
  //printf("sine(%5u) --> %5u, expect %5ld\n", 1u * x, 1u * y, ye);
  long diff = labs(ye - y);
  static long diff_max = 0;
  if (diff > diff_max) {
    diff_max = diff;
    printf("sine(%5u) --> %5u, expect %5ld !!!\n", 1u * x, 1u * y, ye);
  }
}

void sin_fixed_tests() {
  sin_fixed_test(15887);
  for (uint16_t x = 0; x <= 65536u / 4u; x += 1) {
    sin_fixed_test(x);
  }
}

int main() {
  sin_fixed_tests();
  return 0;
}