Skip to content

Commit d0a5d6f

Browse files
authored
Merge pull request #398 from TA-Lib/fix/395-cheap
perf(cci,correl): test the divisor instead of reformulating around it (#395)
2 parents 6c49971 + 75caa6f commit d0a5d6f

18 files changed

Lines changed: 792 additions & 569 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ See [github commits](https://github.com/TA-Lib/ta-lib/commits) for complete list
114114
- (#253) Fix many TA_IS_ZERO vs TA_IS_ZERO_SCALED choices. Numerically better for edge cases, like very small inputs (<10e-8) or mostly flat input prices.
115115
- (#390) STOCH and STOCHF returned `inf` or `NaN` while reporting success, for prices near the bottom of the double range. A close sitting on the window high now comes out as exactly 100.
116116
- (#390) KAMA could return values outside the range of the prices it was smoothing, and ER values above 1. Both come from the same efficiency ratio exceeding its own maximum when floating-point drift left the running sum of price movement below the net move it bounds. The ratio is now clamped, making ER a hard 0..1.
117-
- (#395) CCI returned `+/-Inf` while reporting success, and CORREL returned `NaN` or a perfect +/-1 correlation from a window that had none, for prices near the bottom of the double range. Both now divide by the value their guard actually tests, so they stay accurate to the last bits at every price scale.
117+
- (#395) CCI returned `+/-Inf` while reporting success, and CORREL returned `NaN` or a perfect +/-1 correlation from a window that had none, for prices near the bottom of the double range. Both now test the divisor itself, and answer such a window with 0 like any other degenerate one. Values are unchanged everywhere the old code returned a number.
118118
- (#395) WILLR could return values outside its documented [-100, 0] range, and answered 0 - the value meaning a close at the period high - for a close sitting on the period low whenever the window's high-low range was very small. A close on the period low now comes out as exactly -100, the range is guaranteed even for a close outside its own bar, and a window flat to within rounding of its own prices answers 0, as STOCH and STOCHF already did.
119119

120120
## [0.7.1] 2026-07-03

src/ta_func/ta_CCI.c

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@
6767
* 082326 MF,CC Fix #253. Scale that flatness test to the window's own price
6868
* level: the fixed band zeroed the whole output for any
6969
* instrument quoted small enough to fall under it.
70-
* 090626 MF,CC Fix #395. Divide by the mean deviation, scale after: the
71-
* pre-scaled `0.015*tempReal2` underflowed to 0.0 on a
72-
* denormal price that the guard still called "not flat".
70+
* 090626 MF,CC Fix #395. Test the divisor itself, not just the deviation it
71+
* scales: `0.015*tempReal2` underflows to 0.0 on a denormal
72+
* price the deviation's own band still calls "not flat", and
73+
* the division returned +/-Inf under TA_SUCCESS.
7374
*/
7475

7576
TA_LIB_API int TA_CCI_Lookback( int optInTimePeriod )
@@ -206,13 +207,14 @@ TA_LIB_API TA_RetCode TA_CCI( int startIdx,
206207
tempReal2 /= optInTimePeriod;
207208
/* And finally, the CCI... */
208209
tempReal = lastValue - theAverage;
209-
/* Divide by the mean deviation itself and scale after: the guard has to
210-
* test the very expression the division uses, or a scaling step can
211-
* carry a guarded-non-zero into a zero divisor. The band is relative and
212-
* the underflow of the 0.015 product is absolute, so no band on the
213-
* deviation can cover the product.
210+
/* The third test is the divisor itself, and it is not implied by the
211+
* second: the deviation's band is RELATIVE and the product's underflow is
212+
* ABSOLUTE, so below ~1.6e-308 the band admits a deviation whose scaled
213+
* copy is exactly 0.0 (issue #395). An exact test, not a band -- the
214+
* flatness question is already answered above, and this one is only
215+
* asking whether the value the division uses exists.
214216
*
215-
* Both tests are relative to the window's own price level (issue #253).
217+
* The first two tests are relative to the window's own price level (#253).
216218
* They ask "is this window flat?", and flatness is a property of the
217219
* prices relative to each other -- but a deviation carries the quote
218220
* unit, so the fixed TA_IS_ZERO band these used to be answered "flat" for
@@ -222,9 +224,9 @@ TA_LIB_API TA_RetCode TA_CCI( int startIdx,
222224
* average, which is what it was widened for in the first place (#7).
223225
*/
224226
tempReal3 = fabs(theAverage);
225-
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) )
227+
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) && 0.015 * tempReal2 != 0.0 )
226228
{
227-
outReal[outIdx++] = tempReal / tempReal2 / 0.015;
229+
outReal[outIdx++] = tempReal / (0.015 * tempReal2);
228230
} else
229231
{
230232
outReal[outIdx++] = 0.0;
@@ -342,9 +344,9 @@ TA_RetCode TA_S_CCI( int startIdx,
342344
tempReal2 /= optInTimePeriod;
343345
tempReal = lastValue - theAverage;
344346
tempReal3 = fabs(theAverage);
345-
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) )
347+
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) && 0.015 * tempReal2 != 0.0 )
346348
{
347-
outReal[outIdx++] = tempReal / tempReal2 / 0.015;
349+
outReal[outIdx++] = tempReal / (0.015 * tempReal2);
348350
} else
349351
{
350352
outReal[outIdx++] = 0.0;
@@ -412,13 +414,14 @@ static void TA_CCI_StepImpl( struct TA_CCI_Stream *sp, double inHigh, double inL
412414
tempReal2 /= sp->optInTimePeriod;
413415
/* And finally, the CCI... */
414416
tempReal = lastValue - theAverage;
415-
/* Divide by the mean deviation itself and scale after: the guard has to
416-
* test the very expression the division uses, or a scaling step can
417-
* carry a guarded-non-zero into a zero divisor. The band is relative and
418-
* the underflow of the 0.015 product is absolute, so no band on the
419-
* deviation can cover the product.
417+
/* The third test is the divisor itself, and it is not implied by the
418+
* second: the deviation's band is RELATIVE and the product's underflow is
419+
* ABSOLUTE, so below ~1.6e-308 the band admits a deviation whose scaled
420+
* copy is exactly 0.0 (issue #395). An exact test, not a band -- the
421+
* flatness question is already answered above, and this one is only
422+
* asking whether the value the division uses exists.
420423
*
421-
* Both tests are relative to the window's own price level (issue #253).
424+
* The first two tests are relative to the window's own price level (#253).
422425
* They ask "is this window flat?", and flatness is a property of the
423426
* prices relative to each other -- but a deviation carries the quote
424427
* unit, so the fixed TA_IS_ZERO band these used to be answered "flat" for
@@ -428,9 +431,9 @@ static void TA_CCI_StepImpl( struct TA_CCI_Stream *sp, double inHigh, double inL
428431
* average, which is what it was widened for in the first place (#7).
429432
*/
430433
tempReal3 = fabs(theAverage);
431-
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) )
434+
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) && 0.015 * tempReal2 != 0.0 )
432435
{
433-
*outReal= tempReal / tempReal2 / 0.015;
436+
*outReal= tempReal / (0.015 * tempReal2);
434437
} else
435438
{
436439
*outReal= 0.0;
@@ -562,13 +565,14 @@ static TA_RetCode TA_CCI_OpenImpl( struct TA_CCI_Stream **stream, const double i
562565
tempReal2 /= optInTimePeriod;
563566
/* And finally, the CCI... */
564567
tempReal = lastValue - theAverage;
565-
/* Divide by the mean deviation itself and scale after: the guard has to
566-
* test the very expression the division uses, or a scaling step can
567-
* carry a guarded-non-zero into a zero divisor. The band is relative and
568-
* the underflow of the 0.015 product is absolute, so no band on the
569-
* deviation can cover the product.
568+
/* The third test is the divisor itself, and it is not implied by the
569+
* second: the deviation's band is RELATIVE and the product's underflow is
570+
* ABSOLUTE, so below ~1.6e-308 the band admits a deviation whose scaled
571+
* copy is exactly 0.0 (issue #395). An exact test, not a band -- the
572+
* flatness question is already answered above, and this one is only
573+
* asking whether the value the division uses exists.
570574
*
571-
* Both tests are relative to the window's own price level (issue #253).
575+
* The first two tests are relative to the window's own price level (#253).
572576
* They ask "is this window flat?", and flatness is a property of the
573577
* prices relative to each other -- but a deviation carries the quote
574578
* unit, so the fixed TA_IS_ZERO band these used to be answered "flat" for
@@ -578,9 +582,9 @@ static TA_RetCode TA_CCI_OpenImpl( struct TA_CCI_Stream **stream, const double i
578582
* average, which is what it was widened for in the first place (#7).
579583
*/
580584
tempReal3 = fabs(theAverage);
581-
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) )
585+
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) && 0.015 * tempReal2 != 0.0 )
582586
{
583-
outReal[outIdx++ * outStride] = tempReal / tempReal2 / 0.015;
587+
outReal[outIdx++ * outStride] = tempReal / (0.015 * tempReal2);
584588
} else
585589
{
586590
outReal[outIdx++ * outStride] = 0.0;
@@ -704,13 +708,14 @@ TA_LIB_API TA_RetCode TA_CCI_Peek( const TA_CCI_Stream *stream, double inHigh, d
704708
tempReal2 /= sp->optInTimePeriod;
705709
/* And finally, the CCI... */
706710
tempReal = lastValue - theAverage;
707-
/* Divide by the mean deviation itself and scale after: the guard has to
708-
* test the very expression the division uses, or a scaling step can
709-
* carry a guarded-non-zero into a zero divisor. The band is relative and
710-
* the underflow of the 0.015 product is absolute, so no band on the
711-
* deviation can cover the product.
711+
/* The third test is the divisor itself, and it is not implied by the
712+
* second: the deviation's band is RELATIVE and the product's underflow is
713+
* ABSOLUTE, so below ~1.6e-308 the band admits a deviation whose scaled
714+
* copy is exactly 0.0 (issue #395). An exact test, not a band -- the
715+
* flatness question is already answered above, and this one is only
716+
* asking whether the value the division uses exists.
712717
*
713-
* Both tests are relative to the window's own price level (issue #253).
718+
* The first two tests are relative to the window's own price level (#253).
714719
* They ask "is this window flat?", and flatness is a property of the
715720
* prices relative to each other -- but a deviation carries the quote
716721
* unit, so the fixed TA_IS_ZERO band these used to be answered "flat" for
@@ -720,9 +725,9 @@ TA_LIB_API TA_RetCode TA_CCI_Peek( const TA_CCI_Stream *stream, double inHigh, d
720725
* average, which is what it was widened for in the first place (#7).
721726
*/
722727
tempReal3 = fabs(theAverage);
723-
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) )
728+
if( !TA_IS_ZERO_SCALED(tempReal, tempReal3) && !TA_IS_ZERO_SCALED(tempReal2, tempReal3) && 0.015 * tempReal2 != 0.0 )
724729
{
725-
*outReal= tempReal / tempReal2 / 0.015;
730+
*outReal= tempReal / (0.015 * tempReal2);
726731
} else
727732
{
728733
*outReal= 0.0;

src/ta_func/ta_CORREL.c

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,10 @@
6060
* 082326 MF Fix #242. Cancellation-free sums (shifted data + reseed, as
6161
* TA_VAR does since #118), per-factor degeneracy test and a
6262
* range clamp.
63-
* 090626 MF,CC Fix #395. A root of each guarded factor, not a root of their
64-
* product: sqrt(ssX*ssY) left the double range at both ends
65-
* while ssX and ssY were still ordinary normals, returning NaN
66-
* under TA_SUCCESS and a perfect correlation from a degenerate
67-
* window.
63+
* 090626 MF,CC Fix #395. Test the product too: it underflows to 0.0 while ssX
64+
* and ssY are still ordinary normals, and the divide then
65+
* returned NaN under TA_SUCCESS -- which the range clamp cannot
66+
* catch -- or a perfect correlation from a degenerate window.
6867
*/
6968

7069
TA_LIB_API int TA_CORREL_Lookback( int optInTimePeriod )
@@ -301,13 +300,23 @@ TA_LIB_API TA_RetCode TA_CORREL( int startIdx,
301300
* is also the cheaper test: the two fabs() cost ~7% of this function's
302301
* runtime, and buy a wrong answer.
303302
*
304-
* A root of each guarded factor, never a root of their product: that
305-
* product leaves the double range at BOTH ends while ssX and ssY are
306-
* still ordinary normals, which no test of the factors can see (#395).
303+
* sqrt(ssX*ssY) rather than sqrt(ssX)*sqrt(ssY): the guard has already
304+
* established both are positive, so the product needs no protection from
305+
* a negative operand, and the second square root is worth ~14% of this
306+
* function's runtime (measured, #395).
307+
*
308+
* The product is then tested on its own, because neither factor's test
309+
* implies it: at that fourth power it underflows to exactly 0.0 while ssX
310+
* and ssY are still ordinary normals (#395). A zero divisor there gives
311+
* NaN, which the clamp below does NOT catch -- NaN fails both comparisons
312+
* -- or an infinity the clamp rewrites into a perfect correlation. Exact,
313+
* not a band: an absolute band on the product is the #253 defect at a new
314+
* address. At the other end the product overflows to +Inf and the quotient
315+
* is 0.0, the degenerate answer anyway.
307316
*/
308-
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 )
317+
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 && ssX * ssY > 0.0 )
309318
{
310-
tempReal = spXY / (sqrt(ssX) * sqrt(ssY));
319+
tempReal = spXY / sqrt(ssX * ssY);
311320
/* A correlation coefficient cannot leave [-1,1]; rounding in the
312321
* three sums can still put it a few ulp outside.
313322
*/
@@ -482,9 +491,9 @@ TA_RetCode TA_S_CORREL( int startIdx,
482491
trailingX = (double)inReal0[trailingIdx] - shiftX;
483492
trailingY = (double)inReal1[trailingIdx] - shiftY;
484493
trailingIdx += 1;
485-
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 )
494+
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 && ssX * ssY > 0.0 )
486495
{
487-
tempReal = spXY / (sqrt(ssX) * sqrt(ssY));
496+
tempReal = spXY / sqrt(ssX * ssY);
488497
if( tempReal > 1.0 )
489498
{
490499
tempReal = 1.0;
@@ -695,13 +704,23 @@ static void TA_CORREL_StepImpl( struct TA_CORREL_Stream *sp, double inReal0, dou
695704
* is also the cheaper test: the two fabs() cost ~7% of this function's
696705
* runtime, and buy a wrong answer.
697706
*
698-
* A root of each guarded factor, never a root of their product: that
699-
* product leaves the double range at BOTH ends while ssX and ssY are
700-
* still ordinary normals, which no test of the factors can see (#395).
707+
* sqrt(ssX*ssY) rather than sqrt(ssX)*sqrt(ssY): the guard has already
708+
* established both are positive, so the product needs no protection from
709+
* a negative operand, and the second square root is worth ~14% of this
710+
* function's runtime (measured, #395).
711+
*
712+
* The product is then tested on its own, because neither factor's test
713+
* implies it: at that fourth power it underflows to exactly 0.0 while ssX
714+
* and ssY are still ordinary normals (#395). A zero divisor there gives
715+
* NaN, which the clamp below does NOT catch -- NaN fails both comparisons
716+
* -- or an infinity the clamp rewrites into a perfect correlation. Exact,
717+
* not a band: an absolute band on the product is the #253 defect at a new
718+
* address. At the other end the product overflows to +Inf and the quotient
719+
* is 0.0, the degenerate answer anyway.
701720
*/
702-
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 )
721+
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 && ssX * ssY > 0.0 )
703722
{
704-
tempReal = spXY / (sqrt(ssX) * sqrt(ssY));
723+
tempReal = spXY / sqrt(ssX * ssY);
705724
/* A correlation coefficient cannot leave [-1,1]; rounding in the
706725
* three sums can still put it a few ulp outside.
707726
*/
@@ -955,13 +974,23 @@ static TA_RetCode TA_CORREL_OpenImpl( struct TA_CORREL_Stream **stream, const do
955974
* is also the cheaper test: the two fabs() cost ~7% of this function's
956975
* runtime, and buy a wrong answer.
957976
*
958-
* A root of each guarded factor, never a root of their product: that
959-
* product leaves the double range at BOTH ends while ssX and ssY are
960-
* still ordinary normals, which no test of the factors can see (#395).
977+
* sqrt(ssX*ssY) rather than sqrt(ssX)*sqrt(ssY): the guard has already
978+
* established both are positive, so the product needs no protection from
979+
* a negative operand, and the second square root is worth ~14% of this
980+
* function's runtime (measured, #395).
981+
*
982+
* The product is then tested on its own, because neither factor's test
983+
* implies it: at that fourth power it underflows to exactly 0.0 while ssX
984+
* and ssY are still ordinary normals (#395). A zero divisor there gives
985+
* NaN, which the clamp below does NOT catch -- NaN fails both comparisons
986+
* -- or an infinity the clamp rewrites into a perfect correlation. Exact,
987+
* not a band: an absolute band on the product is the #253 defect at a new
988+
* address. At the other end the product overflows to +Inf and the quotient
989+
* is 0.0, the degenerate answer anyway.
961990
*/
962-
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 )
991+
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 && ssX * ssY > 0.0 )
963992
{
964-
tempReal = spXY / (sqrt(ssX) * sqrt(ssY));
993+
tempReal = spXY / sqrt(ssX * ssY);
965994
/* A correlation coefficient cannot leave [-1,1]; rounding in the
966995
* three sums can still put it a few ulp outside.
967996
*/
@@ -1246,13 +1275,23 @@ TA_LIB_API TA_RetCode TA_CORREL_Peek( const TA_CORREL_Stream *stream, double inR
12461275
* is also the cheaper test: the two fabs() cost ~7% of this function's
12471276
* runtime, and buy a wrong answer.
12481277
*
1249-
* A root of each guarded factor, never a root of their product: that
1250-
* product leaves the double range at BOTH ends while ssX and ssY are
1251-
* still ordinary normals, which no test of the factors can see (#395).
1278+
* sqrt(ssX*ssY) rather than sqrt(ssX)*sqrt(ssY): the guard has already
1279+
* established both are positive, so the product needs no protection from
1280+
* a negative operand, and the second square root is worth ~14% of this
1281+
* function's runtime (measured, #395).
1282+
*
1283+
* The product is then tested on its own, because neither factor's test
1284+
* implies it: at that fourth power it underflows to exactly 0.0 while ssX
1285+
* and ssY are still ordinary normals (#395). A zero divisor there gives
1286+
* NaN, which the clamp below does NOT catch -- NaN fails both comparisons
1287+
* -- or an infinity the clamp rewrites into a perfect correlation. Exact,
1288+
* not a band: an absolute band on the product is the #253 defect at a new
1289+
* address. At the other end the product overflows to +Inf and the quotient
1290+
* is 0.0, the degenerate answer anyway.
12521291
*/
1253-
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 )
1292+
if( ssX > 0.00000000000001 * sumX2 && ssY > 0.00000000000001 * sumY2 && ssX * ssY > 0.0 )
12541293
{
1255-
tempReal = spXY / (sqrt(ssX) * sqrt(ssY));
1294+
tempReal = spXY / sqrt(ssX * ssY);
12561295
/* A correlation coefficient cannot leave [-1,1]; rounding in the
12571296
* three sums can still put it a few ulp outside.
12581297
*/

0 commit comments

Comments
 (0)