浮点数学结果略有不同(C到golang)

浮点数学结果略有不同(C到golang),go,floating-point-precision,Go,Floating Point Precision,我正致力于直接在golang开发一个技术指标库。除其他外,这是一次学习格朗的练习 我一直在用TA-Lib(或者更确切地说是TA-Lib周围的ruby包装器)生成的数据构建测试用例,以验证算法的结果 这一直运作良好,直到我得到布林格乐队的实施。我的实现似乎工作得很好,但在小数点后14-15位有所不同 我已经阅读并怀疑这可能是罪魁祸首(我以稍微不同的顺序进行计算) 编辑以添加: 上面的问题涉及浮点数学的一个非常简单的表现形式。要确认更长的代码是否真的遇到了这个问题要困难得多 我怎样才能确认这仅仅是因

我正致力于直接在golang开发一个技术指标库。除其他外,这是一次学习格朗的练习

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

这一直运作良好,直到我得到布林格乐队的实施。我的实现似乎工作得很好,但在小数点后14-15位有所不同

我已经阅读并怀疑这可能是罪魁祸首(我以稍微不同的顺序进行计算)

编辑以添加:

上面的问题涉及浮点数学的一个非常简单的表现形式。要确认更长的代码是否真的遇到了这个问题要困难得多

我怎样才能确认这仅仅是因为顺序而引起的浮点数学的变化呢

/End编辑

我的理解正确吗

以下是我的实现:

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))
}
ta包
进口(
“数学”
)
func bollingerband(值[]float64,句点int)([]float64,[]float64,[]float64){
偏差SUP:=2.0
偏差下降:=2.0
中间带:=Sma(值、周期)
偏移量:=len(值)-len(中间带)
变量上限[]浮点64
变量lowerBand[]浮点64
对于idx,v:=范围中间带{
backIdx:=偏移量+idx周期+1
curIdx:=偏移量+idx+1
如果backIdx<0{
backIdx=0
}
stdDev:=SliceStdDev(值[backIdx:curIdx])
上限=附加(上限,v+(标准差*偏差上限))
lowerBand=附加(lowerBand,v-(stdDev*偏差向下))
}
返回上带、中带、下带
}
//Sma生成该区域的简单移动平均线
//为给定期间提供的浮点64值数组
func Sma(值[]浮点64,句点整数)[]浮点64{
var结果[]浮点64
对于索引,\:=范围值{
indexPlusOne:=索引+1
if(indexPlusOne>=期间){
平均值:=平均值(值[指数周期:指数周期])
结果=追加(结果,平均值)
}
}
返回结果
}
//SliceMean返回浮点64切片的平均值
func SliceMean(值[]float64)float64{
var total float64=0
对于u,元素:=范围值{
总数+=元素
}
返回总计/浮动64(len(值))
}
//SliceVariance返回浮点64切片的方差。
func SliceVariance(值[]float64)float64{
如果0==len(值){
返回0.0
}
m:=平均值(值)
var和浮动64
对于v,v:=范围值{
d:=v-m
总和+=d*d
}
返回和/浮动64(len(值))
}
//SliceStdDev返回float64切片的标准偏差。
func SliceStdDev(值[]float64)float64{
返回math.Sqrt(方差(值))
}
这将导致上层频带的以下值
:[94.925647305992991、94.505888827974477、92.12752961253167、101.58367006802706、114.6433137907878675、120.580881180322]

使用ruby:

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>}
需要“指示器/混音器”
x=[26.0,54.0,8.0,77.0,61.0,39.0,44.0,91.0,98.0,17.0]
y=x.指示器(:bbands_5)
#{:out_real_upperØband=>[94.9256473059929,94.505888827974477,92.12752961253167,101.58367006802709,114.643313790708678,120.580881180323,nil,nil,nil,nil]}

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

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


通常使用的一种解决方案是在非常小的数字内进行等式比较(
math.Abs(预期结果)<ɛ
,其中定义了ɛ:
constɛ=0.0000001
),而不是使用Caleb和Matteo的评论/回答所建议的
=
,即使代码排序方式上的细微差异也会导致浮点值的差异


我最后确认,至少在小样本的情况下,实现与TA Lib完全相同的代码会产生正确的浮点值。正如所料,即使稍微偏离TA Lib(C)实现,也会导致浮点值的微小差异。

这不是答案,但如果您想排除显示(舍入)浮点值作为差异原因的可能性,你可以考虑打印两个十六进制,并比较它。如果你不按完全相同的顺序执行操作,则完全期望在结果上得到类似的差异。此外,根据编译器使用浮点寄存器的方式,您可能会得到不同的结果(通常x86的C代码被编译为“意外地”利用x87寄存器的全部80位精度,因此如果您一直舍入到常规的64位,则会得到不同的舍入)。@WanderNauta似乎是一个答案。Thanks@MatteoItalia谢谢以不同的方式取整并没有什么大问题,只要我理解为什么可能重复感谢伟大的答案,以及感谢
(math.Abs(expected result)<ɛ
,我会调整测试用例以使用它。
/* 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 );
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;
}