【问题标题】:Slightly different floating point math results (C to golang)浮点数学结果略有不同(C 到 golang)
【发布时间】:2015-12-01 09:56:27
【问题描述】:

我正致力于直接在 golang 中开发技术指标库。除其他外,它是学习 golang 的练习。

我一直在通过使用 TA-Lib(或者更确切地说是 TA-Lib 周围的 ruby​​ 包装器)生成的数据构建测试用例来验证我的算法的结果。

在我开始实施布林带之前,这一直运行良好。我的实现似乎运行良好,但在小数点后 14-15 位有所不同。

我已阅读 Floating point math in different programming languages 并怀疑这可能是罪魁祸首(我的计算顺序略有不同)。

编辑添加:

上面的问题涉及浮点数学的一个非常简单的表现形式。很难确认一段较长的代码确实遇到了这个问题。

我如何确认这只是浮点数学的变化,因为顺序?

/结束编辑

我的理解正确吗?

这是我的实现:

package ta

import (
  "math"
)

func BollingerBands(values []float64, period int) ([]float64, []float64, []float64) {
  deviationsUp := 2.0
  deviationsDown := 2.0

  middleBand := Sma(values, period)
  offset := len(values)-len(middleBand)
  var upperBand []float64
  var lowerBand []float64
  for idx, v := range middleBand {
    backIdx := offset+idx-period+1
    curIdx := offset+idx+1
    if backIdx < 0 {
      backIdx = 0
    }
    stdDev := SliceStdDev(values[backIdx:curIdx])
    upperBand = append(upperBand, v + (stdDev * deviationsUp))
    lowerBand = append(lowerBand, v - (stdDev * deviationsDown))
  }
  return upperBand, middleBand, lowerBand
}

// Sma produces the Simple Moving Average for the
// supplied array of float64 values for a given period
func Sma(values []float64, period int) []float64{
  var result []float64
  for index,_ := range values {
    indexPlusOne := index+1
    if(indexPlusOne>=period) {
      avg := Mean(values[indexPlusOne-period:indexPlusOne])
      result = append(result, avg)
    }
  }
  return result
}

// SliceMean returns the Mean of the slice of float64
func SliceMean(values []float64) float64 {
  var total float64=0
    for _,element := range values {
        total += element
    }
  return total / float64(len(values))
}

// SliceVariance returns the variance of the slice of float64.
func SliceVariance(values []float64) float64 {
    if 0 == len(values) {
        return 0.0
    }
    m := SliceMean(values)
    var sum float64
    for _, v := range values {
        d := v - m
        sum += d * d
    }
    return sum / float64(len(values))
}

// SliceStdDev returns the standard deviation of the slice of float64.
func SliceStdDev(values []float64) float64 {
    return math.Sqrt(SliceVariance(values))
}

这会产生以下值的上限&lt;[]float64 | len:6, cap:8&gt;: [94.92564730599291, 94.50588827974477, 92.12752961253167, 101.58367006802706, 114.64331379078675, 120.58088881180322]

使用红宝石:

require 'indicator/mixin'
x = [26.0, 54.0, 8.0, 77.0, 61.0, 39.0, 44.0, 91.0, 98.0, 17.0]
y = x.indicator(:bbands_5)
# {:out_real_upper_band=>[94.9256473059929, 94.50588827974477, 92.12752961253167, 101.58367006802709, 114.64331379078678, 120.58088881180323, nil, nil, nil, nil] <SNIP>}

【问题讨论】:

  • 这不是一个答案,但如果您想排除显示(四舍五入)浮点值作为差异的原因,您可以考虑将两者打印为十六进制并进行比较。跨度>
  • 如果您不以完全相同的顺序执行操作,则完全可以预期得到类似的结果差异。此外,根据编译器使用浮点寄存器的方式,您可能会有所不同(x86 的 C 代码通常被编译为“意外”利用 x87 寄存器的完整 80 位精度,因此如果您舍入到常规值,则会得到不同的舍入一直是 64 位)。
  • @WanderNauta 似乎是一个答案。谢谢
  • @MatteoItalia 谢谢。只要我理解为什么,它的舍入方式不同就没有什么大问题

标签: go floating-point-precision


【解决方案1】:

我认为算法是不同的。例如variance:

/* Do the MA calculation using tight loops. */
/* Add-up the initial periods, except for the last value. */
periodTotal1 = 0;
periodTotal2 = 0;
trailingIdx = startIdx-nbInitialElementNeeded;

i=trailingIdx;
if( optInTimePeriod > 1 )
{
   while( i < startIdx ) {
      tempReal = inReal[i++];
      periodTotal1 += tempReal;
      tempReal *= tempReal;
      periodTotal2 += tempReal;
   }
}

/* Proceed with the calculation for the requested range.
 * Note that this algorithm allows the inReal and
 * outReal to be the same buffer.
 */
outIdx = 0;
do
{
   tempReal = inReal[i++];

   /* Square and add all the deviation over
    * the same periods.
    */

   periodTotal1 += tempReal;
   tempReal *= tempReal;
   periodTotal2 += tempReal;

   /* Square and add all the deviation over
    * the same period.
    */

   meanValue1 = periodTotal1 / optInTimePeriod;
   meanValue2 = periodTotal2 / optInTimePeriod;

   tempReal = inReal[trailingIdx++];
   periodTotal1 -= tempReal;
   tempReal *= tempReal;
   periodTotal2 -= tempReal;

   outReal[outIdx++] = meanValue2-meanValue1*meanValue1;
} while( i <= endIdx );

这看起来不像你的差异。如果您要重现算法以使它们执行完全相同的操作,那么 Go 版本应该会产生相同的结果。 Go 只是在做标准的 IEEE 754 浮点运算。

关于“顺序重要吗?”这个问题。确实如此。由于浮点运算是不精确的,因此您在进行计算时会丢失信息。大多数时候它并没有太大的区别,但有时算法很容易受到这些变化的影响。 (因此以代数方式重新排列您的公式可能不会在实际代码中得到相同的答案)

您经常会在此类库中发现,算法旨在解决这些问题,因此它们通常看起来不像是简单的实现。例如mean 通常是一个微不足道的函数,但在 GSL 中是这样计算的:

double
FUNCTION (gsl_stats, mean) (const BASE data[], const size_t stride, const size_t size)
{
  /* Compute the arithmetic mean of a dataset using the recurrence relation 
     mean_(n) = mean(n-1) + (data[n] - mean(n-1))/(n+1)   */

  long double mean = 0;
  size_t i;

  for (i = 0; i < size; i++)
    {
      mean += (data[i * stride] - mean) / (i + 1);
    }

  return mean;
}

因此,除非您完全匹配算法,否则您的答案将略有不同。 (这并不一定意味着你的程序是错误的)

一种常用的解决方案是在一个非常小的数字(math.Abs(expected-result) &lt; ɛ,您定义ɛ:const ɛ = 0.0000001)内进行相等比较,而不是使用==

【讨论】:

  • 感谢您的出色回答,对于(math.Abs(expected-result) &lt; ɛ,将调整我的测试用例以使用它。
【解决方案2】:

正如 Caleb 和 Matteo 的 cmets/answers 所建议的,即使代码排序方式的细微差异也会导致浮点值的差异。

我最终确认,至少在小样本量的情况下,实现代码完全像 TA-Lib 会导致 正确 浮点值。正如预期的那样,即使稍微偏离 TA-Lib (C) 实现也会导致浮点值的微小差异。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-20
    • 1970-01-01
    • 1970-01-01
    • 2013-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多