diff --git a/shared/libebm/PartitionMultiDimensionalTree.cpp b/shared/libebm/PartitionMultiDimensionalTree.cpp index 0b88e8e7f..7b3fa7e14 100644 --- a/shared/libebm/PartitionMultiDimensionalTree.cpp +++ b/shared/libebm/PartitionMultiDimensionalTree.cpp @@ -628,8 +628,8 @@ template class PartitionMultiDimensionalT ++iDimension; } while(cDimensions != iDimension); - constexpr double tolerance = - 0.0; // TODO: for now purify to the max, but test tolerances and profile them + // TODO: for now purify to the max, but test tolerances and profile them + constexpr double tolerance = 0.0; // TODO: in the future try randomizing the purification order. It probably doesn't make much // difference diff --git a/shared/libebm/Purify.cpp b/shared/libebm/Purify.cpp index aebaad8ec..f51902604 100644 --- a/shared/libebm/Purify.cpp +++ b/shared/libebm/Purify.cpp @@ -228,7 +228,7 @@ extern ErrorEbm PurifyInternal(const double tolerance, double* const pIntercept) { EBM_ASSERT(!std::isnan(tolerance)); EBM_ASSERT(!std::isinf(tolerance)); - EBM_ASSERT(0.0 <= tolerance); + EBM_ASSERT(0.0 == tolerance || std::numeric_limits::min() <= tolerance); EBM_ASSERT(1 <= cScores); EBM_ASSERT(1 <= cTensorBins); EBM_ASSERT(nullptr != pRng || aRandomize == nullptr); @@ -259,10 +259,14 @@ extern ErrorEbm PurifyInternal(const double tolerance, size_t iScorePre = 0; const double* pWeightPre = aWeights; do { - const double weight = *pWeightPre; - if(!(0.0 <= weight)) { - LOG_0(Trace_Error, "ERROR PurifyInternal weight cannot be negative or NaN"); - return Error_IllegalParamVal; + double weight = *pWeightPre; + if(!(std::numeric_limits::min() <= weight)) { + if(!(0.0 <= weight)) { + LOG_0(Trace_Error, "ERROR PurifyInternal weight cannot be negative or NaN"); + return Error_IllegalParamVal; + } + // eliminate denormals + weight = 0.0; } EBM_ASSERT(!std::isnan(weight)); // !(0.0 <= weight) above checks for NaN if(std::numeric_limits::infinity() == weight) { @@ -279,17 +283,31 @@ extern ErrorEbm PurifyInternal(const double tolerance, impurityTotalPre = 0.0; cInfWeights = 0; do { - const double weightInterior = *pWeightInterior; - if(!(0.0 <= weightInterior)) { - LOG_0(Trace_Error, "ERROR PurifyInternal weight cannot be negative or NaN"); - return Error_IllegalParamVal; + double weightInterior = *pWeightInterior; + if(!(std::numeric_limits::min() <= weightInterior)) { + if(!(0.0 <= weightInterior)) { + LOG_0(Trace_Error, "ERROR PurifyInternal weight cannot be negative or NaN"); + return Error_IllegalParamVal; + } + // eliminate denormals + weightInterior = 0.0; } EBM_ASSERT(!std::isnan(weightInterior)); // !(0.0 <= weightInterior) above checks for NaN if(std::numeric_limits::infinity() == weightInterior) { const double scoreInterior = *IndexByte(pScores, iScoreInterior); if(!std::isnan(scoreInterior) && !std::isinf(scoreInterior)) { ++cInfWeights; - impurityTotalPre += factorPre * scoreInterior; + double change = factorPre * scoreInterior; + if(-std::numeric_limits::min() < change && change < std::numeric_limits::min()) { + // eliminate denormals + change = 0.0; + } + impurityTotalPre += change; + if(-std::numeric_limits::min() < impurityTotalPre && + impurityTotalPre < std::numeric_limits::min()) { + // eliminate denormals + impurityTotalPre = 0.0; + } // impurityTotalPre can reach -+inf, but once it gets there it cannot // escape that value because everything we add subsequently is non-inf. @@ -306,25 +324,38 @@ extern ErrorEbm PurifyInternal(const double tolerance, // cInfWeights can be zero if all the +inf weights are +-inf or NaN scores, so goto a check for this goto pre_intercept; } - const double score = *IndexByte(pScores, iScorePre); + double score = *IndexByte(pScores, iScorePre); if(!std::isnan(score) && !std::isinf(score)) { + if(-std::numeric_limits::min() < score && score < std::numeric_limits::min()) { + // eliminate denormals + score = 0.0; + } weightTotalPre += weight; - const double impurity = weight * score; + double impurity = weight * score; + if(-std::numeric_limits::min() < impurity && impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } impurityTotalPre += impurity; + if(-std::numeric_limits::min() < impurityTotalPre && + impurityTotalPre < std::numeric_limits::min()) { + // eliminate denormals + impurityTotalPre = 0.0; + } impurityMax += std::abs(impurity); } iScorePre += cBytesScoreClasses; ++pWeightPre; } while(pWeightsEnd != pWeightPre); EBM_ASSERT(!std::isnan(weightTotalPre)); - EBM_ASSERT(0.0 <= weightTotalPre); + EBM_ASSERT(0.0 == weightTotalPre || std::numeric_limits::min() <= weightTotalPre); // even if score and weight are never NaN or infinity, the product of both can be +-inf and if +inf is added // to -inf it will be NaN, so impurityTotalPre can be NaN even if weight and score are well formed // impurityMax cannot be NaN though here since abs(impurity) cannot be -inf. If score was zero and weight // was +inf then impurityMax could be NaN, but we've excluded it at this point through bInfWeight. EBM_ASSERT(!std::isnan(impurityMax)); - EBM_ASSERT(0.0 <= impurityMax); + EBM_ASSERT(0.0 == impurityMax || std::numeric_limits::min() <= impurityMax); if(std::numeric_limits::min() <= impurityMax) { // impurityMax could be zero because the scores are zero or the weights are zero. @@ -343,20 +374,40 @@ extern ErrorEbm PurifyInternal(const double tolerance, const double* pWeightRetry = aWeights; do { const double weight = *pWeightRetry; - EBM_ASSERT(!std::isnan(weight) && 0.0 <= weight && weight != std::numeric_limits::infinity()); + EBM_ASSERT(!std::isnan(weight) && (0.0 == weight || std::numeric_limits::min() <= weight) && + weight != std::numeric_limits::infinity()); const double score = *IndexByte(pScores, iScoreRetry); if(!std::isnan(score) && !std::isinf(score)) { - const double weightTimesFactor = factorPre * weight; + double weightTimesFactor = factorPre * weight; + if(weightTimesFactor < std::numeric_limits::min()) { + // eliminate denormals + weightTimesFactor = 0.0; + } weightTotalPre += weightTimesFactor; - const double scoreTimesFactor = factorPre * score; - impurityTotalPre += scoreTimesFactor * weightTimesFactor; + double scoreTimesFactor = factorPre * score; + if(-std::numeric_limits::min() < scoreTimesFactor && + scoreTimesFactor < std::numeric_limits::min()) { + // eliminate denormals + scoreTimesFactor = 0.0; + } + double prod = scoreTimesFactor * weightTimesFactor; + if(-std::numeric_limits::min() < prod && prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurityTotalPre += prod; + if(-std::numeric_limits::min() < impurityTotalPre && + impurityTotalPre < std::numeric_limits::min()) { + // eliminate denormals + impurityTotalPre = 0.0; + } } iScoreRetry += cBytesScoreClasses; ++pWeightRetry; } while(pWeightsEnd != pWeightRetry); } EBM_ASSERT(!std::isnan(weightTotalPre)); - EBM_ASSERT(0.0 <= weightTotalPre); + EBM_ASSERT(0.0 == weightTotalPre || std::numeric_limits::min() <= weightTotalPre); pre_intercept:; if(std::numeric_limits::min() <= weightTotalPre) { @@ -364,7 +415,24 @@ extern ErrorEbm PurifyInternal(const double tolerance, // handle this by just turning off early exit and let the algorithm exit when it cannot improve impurityMax = 0.0; } else { - impurityMax = impurityMax * tolerance * factorPre / weightTotalPre; + impurityMax *= tolerance; + if(impurityMax < std::numeric_limits::min()) { + // eliminate denormals + impurityMax = 0.0; + } + + impurityMax *= factorPre; + if(impurityMax < std::numeric_limits::min()) { + // eliminate denormals + impurityMax = 0.0; + } + + impurityMax /= weightTotalPre; + if(impurityMax < std::numeric_limits::min()) { + // eliminate denormals + impurityMax = 0.0; + } + // at this location: // 0.0 < impurityMax < +inf // 0.0 <= tolerance < +inf @@ -381,7 +449,17 @@ extern ErrorEbm PurifyInternal(const double tolerance, if(nullptr != pIntercept) { // pull out the intercept early since this will make purification easier - double intercept = impurityTotalPre / weightTotalPre / factorPre; + double intercept = impurityTotalPre / weightTotalPre; + if(-std::numeric_limits::min() < intercept && intercept < std::numeric_limits::min()) { + // eliminate denormals + intercept = 0.0; + } + intercept /= factorPre; + if(-std::numeric_limits::min() < intercept && intercept < std::numeric_limits::min()) { + // eliminate denormals + intercept = 0.0; + } + EBM_ASSERT(!std::isnan(intercept)); if(std::isinf(intercept)) { // the intercept is the weighted average of numbers, so it cannot mathematically @@ -404,9 +482,17 @@ extern ErrorEbm PurifyInternal(const double tolerance, do { // this can create new +-inf values, but not NaN since we limited intercept to non-NaN, non-inf double* const pScoreUpdate = IndexByte(pScores, iScoreUpdate); - const double scoreOld = *pScoreUpdate; - const double scoreNew = scoreOld + interceptNeg; + double scoreOld = *pScoreUpdate; + if(-std::numeric_limits::min() < scoreOld && scoreOld < std::numeric_limits::min()) { + // eliminate denormals + scoreOld = 0.0; + } + double scoreNew = scoreOld + interceptNeg; EBM_ASSERT(std::isnan(scoreOld) || !std::isnan(scoreNew)); + if(-std::numeric_limits::min() < scoreNew && scoreNew < std::numeric_limits::min()) { + // eliminate denormals + scoreNew = 0.0; + } *pScoreUpdate = scoreNew; iScoreUpdate += cBytesScoreClasses; } while(iScoresEnd != iScoreUpdate); @@ -419,6 +505,7 @@ extern ErrorEbm PurifyInternal(const double tolerance, // so as long as we multiply by 1/number_of_terms_summed we can guarantee the sum will not overflow // start from 0.5 instead of 1.0 to allow for floating point error. const double impuritySumOverflowPreventer = 0.5 / static_cast(cSurfaceBins); + EBM_ASSERT(std::numeric_limits::min() <= impuritySumOverflowPreventer); double impurityPrev; double impurityCur = std::numeric_limits::infinity(); bool bRetry; @@ -515,7 +602,7 @@ extern ErrorEbm PurifyInternal(const double tolerance, size_t iTensorWeightCur = iTensorWeight; size_t iTensorScoreCur = iTensorScore; do { - const double weight = *IndexByte(aWeights, iTensorWeightCur); + double weight = *IndexByte(aWeights, iTensorWeightCur); EBM_ASSERT(!std::isnan(weight) && 0.0 <= weight); if(std::numeric_limits::infinity() == weight) { size_t cInfWeights; @@ -536,7 +623,18 @@ extern ErrorEbm PurifyInternal(const double tolerance, const double scoreInterior = *IndexByte(pScores, iTensorScoreCurInterior); if(!std::isnan(scoreInterior) && !std::isinf(scoreInterior)) { ++cInfWeights; - impurity += factor * scoreInterior; + double prod = factor * scoreInterior; + if(-std::numeric_limits::min() < prod && + prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurity += prod; + if(-std::numeric_limits::min() < impurity && + impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } // impurity can reach -+inf, but once it gets there it cannot // escape that value because everything we add subsequently is non-inf. @@ -552,10 +650,29 @@ extern ErrorEbm PurifyInternal(const double tolerance, // check for this goto do_impurity; } - const double score = *IndexByte(pScores, iTensorScoreCur); + double score = *IndexByte(pScores, iTensorScoreCur); if(!std::isnan(score) && !std::isinf(score)) { + if(weight < std::numeric_limits::min()) { + // eliminate denormals + weight = 0.0; + } + if(-std::numeric_limits::min() < score && + score < std::numeric_limits::min()) { + // eliminate denormals + score = 0.0; + } weightTotal += weight; - impurity += weight * score; + double prod = weight * score; + if(-std::numeric_limits::min() < prod && prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurity += prod; + if(-std::numeric_limits::min() < impurity && + impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } } iTensorWeightCur += cTensorWeightIncrement; iTensorScoreCur += cTensorScoreIncrement; @@ -580,10 +697,30 @@ extern ErrorEbm PurifyInternal(const double tolerance, weight != std::numeric_limits::infinity()); const double score = *IndexByte(pScores, iTensorScoreCur); if(!std::isnan(score) && !std::isinf(score)) { - const double weightTimesFactor = factor * weight; + double weightTimesFactor = factor * weight; + if(weightTimesFactor < std::numeric_limits::min()) { + // eliminate denormals + weightTimesFactor = 0.0; + } weightTotal += weightTimesFactor; - const double scoreTimesFactor = factor * score; - impurity += scoreTimesFactor * weightTimesFactor; + double scoreTimesFactor = factor * score; + if(-std::numeric_limits::min() < scoreTimesFactor && + scoreTimesFactor < std::numeric_limits::min()) { + // eliminate denormals + scoreTimesFactor = 0.0; + } + double prod = scoreTimesFactor * weightTimesFactor; + if(-std::numeric_limits::min() < prod && + prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurity += prod; + if(-std::numeric_limits::min() < impurity && + impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } } iTensorWeightCur += cTensorWeightIncrement; iTensorScoreCur += cTensorScoreIncrement; @@ -593,7 +730,18 @@ extern ErrorEbm PurifyInternal(const double tolerance, do_impurity:; if(std::numeric_limits::min() <= weightTotal) { - impurity = impurity / weightTotal / factor; + impurity /= weightTotal; + if(-std::numeric_limits::min() < impurity && + impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } + impurity /= factor; + if(-std::numeric_limits::min() < impurity && + impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } EBM_ASSERT(!std::isnan(impurity)); if(std::isinf(impurity)) { // impurity is the weighted average of numbers, so it cannot mathematically @@ -612,11 +760,21 @@ extern ErrorEbm PurifyInternal(const double tolerance, if(nullptr != pImpurities) { double* const pImpurity = IndexByte(pImpurities, iSurfaceBin * cBytesScoreClasses); - const double oldImpurity = *pImpurity; + double oldImpurity = *pImpurity; // we prevent impurities from reaching NaN or an infinity EBM_ASSERT(!std::isnan(oldImpurity)); EBM_ASSERT(!std::isinf(oldImpurity)); + if(-std::numeric_limits::min() < oldImpurity && + oldImpurity < std::numeric_limits::min()) { + // eliminate denormals + oldImpurity = 0.0; + } double newImpurity = oldImpurity + impurity; + if(-std::numeric_limits::min() < newImpurity && + newImpurity < std::numeric_limits::min()) { + // eliminate denormals + newImpurity = 0.0; + } if(std::isinf(newImpurity)) { // There should be a solution that allows any tensor to be purified // without overflowing any of the impurity cells, however due to the @@ -627,11 +785,15 @@ extern ErrorEbm PurifyInternal(const double tolerance, if(std::numeric_limits::infinity() == newImpurity) { EBM_ASSERT(0.0 < oldImpurity); impurity = std::numeric_limits::max() - oldImpurity; + EBM_ASSERT(std::numeric_limits::max() == oldImpurity || + std::numeric_limits::min() <= impurity); newImpurity = std::numeric_limits::max(); } else { EBM_ASSERT(-std::numeric_limits::infinity() == newImpurity); EBM_ASSERT(oldImpurity < 0.0); impurity = -std::numeric_limits::max() - oldImpurity; + EBM_ASSERT(-std::numeric_limits::max() == oldImpurity || + impurity <= -std::numeric_limits::min()); newImpurity = -std::numeric_limits::max(); } } @@ -639,9 +801,14 @@ extern ErrorEbm PurifyInternal(const double tolerance, } const double absImpurity = std::abs(impurity); + EBM_ASSERT(0.0 == absImpurity || std::numeric_limits::min() <= absImpurity); bRetry |= impurityMax < absImpurity; - impurityCur += absImpurity * impuritySumOverflowPreventer; - + double prod = absImpurity * impuritySumOverflowPreventer; + if(prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurityCur += prod; impurity = -impurity; size_t iScoreUpdate = iTensorScore; @@ -650,6 +817,11 @@ extern ErrorEbm PurifyInternal(const double tolerance, double* const pScoreUpdate = IndexByte(pScores, iScoreUpdate); double score = *pScoreUpdate; if(!std::isinf(score)) { + if(-std::numeric_limits::min() < score && + score < std::numeric_limits::min()) { + // eliminate denormals + score = 0.0; + } score += impurity; if(std::isinf(score)) { // we transitioned a score to an infinity. This can dramatically increase the @@ -657,6 +829,10 @@ extern ErrorEbm PurifyInternal(const double tolerance, // value against an opposite sign big value and now with one of them as infinite // the other has no counterbalance, so we need to reset our impurity checker impurityCur = std::numeric_limits::quiet_NaN(); + } else if(-std::numeric_limits::min() < score && + score < std::numeric_limits::min()) { + // eliminate denormals + score = 0.0; } *pScoreUpdate = score; } @@ -678,7 +854,7 @@ extern ErrorEbm PurifyInternal(const double tolerance, double impurityTotalPost = 0.0; double weightTotalPost = 0.0; do { - const double weight = *pWeightPost; + double weight = *pWeightPost; EBM_ASSERT(!std::isnan(weight) && 0.0 <= weight); if(std::numeric_limits::infinity() == weight) { size_t cInfWeights; @@ -697,10 +873,21 @@ extern ErrorEbm PurifyInternal(const double tolerance, const double weightInterior = *pWeightInterior; EBM_ASSERT(!std::isnan(weightInterior) && 0.0 <= weightInterior); if(std::numeric_limits::infinity() == weightInterior) { - const double scoreInterior = *IndexByte(pScores, iScoreInterior); + double scoreInterior = *IndexByte(pScores, iScoreInterior); if(!std::isnan(scoreInterior) && !std::isinf(scoreInterior)) { ++cInfWeights; - impurityTotalPost += factorPost * scoreInterior; + double prod = factorPost * scoreInterior; + if(-std::numeric_limits::min() < prod && + prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurityTotalPost += prod; + if(-std::numeric_limits::min() < impurityTotalPost && + impurityTotalPost < std::numeric_limits::min()) { + // eliminate denormals + impurityTotalPost = 0.0; + } // impurityTotalPost can reach -+inf, but once it gets there it cannot // escape that value because everything we add subsequently is non-inf. @@ -716,17 +903,35 @@ extern ErrorEbm PurifyInternal(const double tolerance, // this goto post_intercept; } - const double score = *IndexByte(pScores, iScorePost); + double score = *IndexByte(pScores, iScorePost); if(!std::isnan(score) && !std::isinf(score)) { + if(weight < std::numeric_limits::min()) { + // eliminate denormals + weight = 0.0; + } weightTotalPost += weight; - const double impurity = weight * score; + if(-std::numeric_limits::min() < score && score < std::numeric_limits::min()) { + // eliminate denormals + score = 0.0; + } + double impurity = weight * score; + if(-std::numeric_limits::min() < impurity && + impurity < std::numeric_limits::min()) { + // eliminate denormals + impurity = 0.0; + } impurityTotalPost += impurity; + if(-std::numeric_limits::min() < impurityTotalPost && + impurityTotalPost < std::numeric_limits::min()) { + // eliminate denormals + impurityTotalPost = 0.0; + } } iScorePost += cBytesScoreClasses; ++pWeightPost; } while(pWeightsEnd != pWeightPost); EBM_ASSERT(!std::isnan(weightTotalPost)); - EBM_ASSERT(0.0 <= weightTotalPost); + EBM_ASSERT(0.0 == weightTotalPost || std::numeric_limits::min() <= weightTotalPost); while(std::isnan(impurityTotalPost) || std::isinf(impurityTotalPost) || std::isinf(weightTotalPost)) { // If impurity is NaN, it means that score * weight overflowed to +inf once and -inf another time @@ -745,22 +950,53 @@ extern ErrorEbm PurifyInternal(const double tolerance, !std::isnan(weight) && 0.0 <= weight && weight != std::numeric_limits::infinity()); const double score = *IndexByte(pScores, iScoreRetry); if(!std::isnan(score) && !std::isinf(score)) { - const double weightTimesFactor = factorPost * weight; + double weightTimesFactor = factorPost * weight; + if(weightTimesFactor < std::numeric_limits::min()) { + // eliminate denormals + weightTimesFactor = 0.0; + } weightTotalPost += weightTimesFactor; - const double scoreTimesFactor = factorPost * score; - impurityTotalPost += scoreTimesFactor * weightTimesFactor; + double scoreTimesFactor = factorPost * score; + if(-std::numeric_limits::min() < scoreTimesFactor && + scoreTimesFactor < std::numeric_limits::min()) { + // eliminate denormals + scoreTimesFactor = 0.0; + } + double prod = scoreTimesFactor * weightTimesFactor; + if(-std::numeric_limits::min() < prod && prod < std::numeric_limits::min()) { + // eliminate denormals + prod = 0.0; + } + impurityTotalPost += prod; + if(-std::numeric_limits::min() < impurityTotalPost && + impurityTotalPost < std::numeric_limits::min()) { + // eliminate denormals + impurityTotalPost = 0.0; + } } iScoreRetry += cBytesScoreClasses; ++pWeightRetry; } while(pWeightsEnd != pWeightRetry); } EBM_ASSERT(!std::isnan(weightTotalPost)); - EBM_ASSERT(0.0 <= weightTotalPost); + EBM_ASSERT(0.0 == weightTotalPost || std::numeric_limits::min() <= weightTotalPost); post_intercept:; if(std::numeric_limits::min() <= weightTotalPost) { // pull out the intercept early since this will make purification easier - double interceptChange = impurityTotalPost / weightTotalPost / factorPost; + double interceptChange = impurityTotalPost / weightTotalPost; + if(-std::numeric_limits::min() < interceptChange && + interceptChange < std::numeric_limits::min()) { + // eliminate denormals + interceptChange = 0.0; + } + interceptChange /= factorPost; + if(-std::numeric_limits::min() < interceptChange && + interceptChange < std::numeric_limits::min()) { + // eliminate denormals + interceptChange = 0.0; + } + EBM_ASSERT(!std::isnan(interceptChange)); if(std::isinf(interceptChange)) { // the intercept is the weighted average of numbers, so it cannot mathematically @@ -777,7 +1013,18 @@ extern ErrorEbm PurifyInternal(const double tolerance, } } - double newIntercept = *pIntercept + interceptChange; + double newIntercept = *pIntercept; + if(-std::numeric_limits::min() < newIntercept && + newIntercept < std::numeric_limits::min()) { + // eliminate denormals + newIntercept = 0.0; + } + newIntercept += interceptChange; + if(-std::numeric_limits::min() < newIntercept && + newIntercept < std::numeric_limits::min()) { + // eliminate denormals + newIntercept = 0.0; + } if(std::isinf(newIntercept)) { // It should be pretty difficult, or perhaps even impossible, for the impurity, // which starts from 0.0 to reach +-infinity since it comes from the weighted averaged @@ -800,8 +1047,18 @@ extern ErrorEbm PurifyInternal(const double tolerance, do { // this can create new +-inf values, but not NaN since we limited intercept to non-NaN, non-inf double* const pScoreUpdate = IndexByte(pScores, iScoreUpdate); - const double scoreOld = *pScoreUpdate; - const double scoreNew = scoreOld + interceptChangeNeg; + double scoreOld = *pScoreUpdate; + if(-std::numeric_limits::min() < scoreOld && + scoreOld < std::numeric_limits::min()) { + // eliminate denormals + scoreOld = 0.0; + } + double scoreNew = scoreOld + interceptChangeNeg; + if(-std::numeric_limits::min() < scoreNew && + scoreNew < std::numeric_limits::min()) { + // eliminate denormals + scoreNew = 0.0; + } EBM_ASSERT(std::isnan(scoreOld) || !std::isnan(scoreNew)); *pScoreUpdate = scoreNew; iScoreUpdate += cBytesScoreClasses; @@ -1802,6 +2059,10 @@ EBM_API_BODY ErrorEbm EBM_CALLING_CONVENTION Purify(double tolerance, LOG_0(Trace_Error, "ERROR Purify std::isnan(tolerance) || std::isinf(tolerance) || tolerance < 0.0)"); return Error_IllegalParamVal; } + if(tolerance < std::numeric_limits::min()) { + // zero any denormals + tolerance = 0.0; + } size_t cSurfaceBins = 0; if(1 < cDimensions) { diff --git a/shared/libebm/tests/boosting_unusual_inputs.cpp b/shared/libebm/tests/boosting_unusual_inputs.cpp index a3ed4c7a5..82c1f9918 100644 --- a/shared/libebm/tests/boosting_unusual_inputs.cpp +++ b/shared/libebm/tests/boosting_unusual_inputs.cpp @@ -2088,10 +2088,10 @@ TEST_CASE("stress test, boosting") { // terms.push_back({0, 1, 2, 3}); // TODO: enable when fast enough } const size_t cRounds = 200; - std::vector boostFlagsAny{// TermBoostFlags_PurifyGain, + std::vector boostFlagsAny{TermBoostFlags_PurifyGain, TermBoostFlags_DisableNewtonGain, TermBoostFlags_DisableCategorical, - // TermBoostFlags_PurifyUpdate, + TermBoostFlags_PurifyUpdate, // TermBoostFlags_GradientSums, // does not return a metric TermBoostFlags_DisableNewtonUpdate, TermBoostFlags_RandomSplits}; @@ -2169,5 +2169,5 @@ TEST_CASE("stress test, boosting") { } } - CHECK(validationMetric == 62013566170252.117); + CHECK(validationMetric == 30885317143376.566); }