略有不同的浮点数学结果(C 到 golang)
Slightly different floating point math results (C to golang)
我正致力于直接在 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))
}
这会导致上频带的以下值 <[]float64 | len:6, cap:8>: [94.92564730599291, 94.50588827974477, 92.12752961253167, 101.58367006802706, 114.64331379078675, 120.58088881180322]
使用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>}
我认为算法不同。例如 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 浮点运算。
至于问题"does order matter?"确实如此。由于浮点运算是不精确的,因此您在进行计算时会丢失信息。大多数时候它并没有太大的区别,但有时算法可能很容易受到这些变化的影响。 (因此以代数方式重新排列您的公式可能不会在实际代码中得到相同的答案)
您经常会在这样的库中发现算法被设计来解决这些问题,因此它们通常看起来不像天真的实现。例如 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) < ɛ
,您定义 ɛ: const ɛ = 0.0000001
)而不是使用 ==
.
正如 Caleb 和 Matteo 的 comments/answers 所建议的那样,即使代码排序方式的细微差异也会导致浮点值的差异。
我最终确认,至少在样本量较小的情况下,实现与 TA-Lib 完全相同的代码会产生 正确的 浮点值。正如预期的那样,即使稍微偏离 TA-Lib (C) 实现也会导致浮点值出现微小差异。
我正致力于直接在 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))
}
这会导致上频带的以下值 <[]float64 | len:6, cap:8>: [94.92564730599291, 94.50588827974477, 92.12752961253167, 101.58367006802706, 114.64331379078675, 120.58088881180322]
使用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>}
我认为算法不同。例如 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 浮点运算。
至于问题"does order matter?"确实如此。由于浮点运算是不精确的,因此您在进行计算时会丢失信息。大多数时候它并没有太大的区别,但有时算法可能很容易受到这些变化的影响。 (因此以代数方式重新排列您的公式可能不会在实际代码中得到相同的答案)
您经常会在这样的库中发现算法被设计来解决这些问题,因此它们通常看起来不像天真的实现。例如 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) < ɛ
,您定义 ɛ: const ɛ = 0.0000001
)而不是使用 ==
.
正如 Caleb 和 Matteo 的 comments/answers 所建议的那样,即使代码排序方式的细微差异也会导致浮点值的差异。
我最终确认,至少在样本量较小的情况下,实现与 TA-Lib 完全相同的代码会产生 正确的 浮点值。正如预期的那样,即使稍微偏离 TA-Lib (C) 实现也会导致浮点值出现微小差异。