Skip to content

File MathUtils.hpp

File List > QAccelPlot > src > QAccelPlot > MathUtils.hpp

Go to the documentation of this file

//
// SPDX-FileCopyrightText: 2026 Michal Grabarczyk
// SPDX-License-Identifier: GPL-3.0-only WITH Universal-FOSS-exception-1.0
//
// This file is also available under a separate commercial license.
// See COMMERCIAL-LICENSING.md for contact information.
//
#pragma once

#include <algorithm>
#include <cmath>
#include <limits>

namespace QAccelPlot {

// Default relative tolerance for nearlyEqual. Two machine-epsilon steps keep
// the comparison close to representable double precision at every magnitude.
// Can be overridden at compile time: -DQACCELPLOT_NEARLY_EQUAL_EPSILON=1e-9
#ifndef QACCELPLOT_NEARLY_EQUAL_EPSILON
inline constexpr double kNearlyEqualEpsilon = 2.0 * std::numeric_limits<double>::epsilon();
#else
inline constexpr double kNearlyEqualEpsilon = QACCELPLOT_NEARLY_EQUAL_EPSILON;
#endif

// Returns true if a and b are nearly equal using a relative tolerance and
// an optional absolute tolerance.
//
//  - If a == b exactly (including both infinite with the same sign), returns true.
//  - The relative test scales with max(|a|, |b|) * relativeEpsilon, preserving distinct
//    values near zero.
//  - Pass absoluteEpsilon explicitly when an absolute tolerance is needed.
[[nodiscard]] inline bool nearlyEqual(double a, double b, double relativeEpsilon = kNearlyEqualEpsilon, double absoluteEpsilon = 0.0) noexcept
{
    if (a == b) {
        return true;
    }

    if (!std::isfinite(a) || !std::isfinite(b)) {
        return false;
    }

    const auto diff = std::abs(a - b);
    const auto scale = std::max(std::abs(a), std::abs(b));
    return diff <= std::max(absoluteEpsilon, scale * relativeEpsilon);
}

// Returns true when float coordinates stored relative to origin lose precision in the
// viewport [viewportMin, viewportMax]. Series upload double data to the GPU as floats
// relative to a render origin; far from it, the float spacing becomes visible. At 64
// viewport widths the rounding error stays below 1e-5 of the viewport.
[[nodiscard]] inline bool renderOriginTooFar(double origin, double viewportMin, double viewportMax) noexcept
{
    constexpr auto kMaxDistanceInViewports = 64.0;
    const auto span = viewportMax - viewportMin;
    if (!(span > 0.0) || !std::isfinite(span) || !std::isfinite(origin)) {
        return false;
    }
    const auto center = viewportMin + span * 0.5;
    return std::abs(center - origin) > kMaxDistanceInViewports * span;
}

// Returns origin, or the viewport center when origin is too far from the viewport.
[[nodiscard]] inline double renderOriginForViewport(double origin, double viewportMin, double viewportMax) noexcept
{
    return renderOriginTooFar(origin, viewportMin, viewportMax) ? viewportMin + (viewportMax - viewportMin) * 0.5 : origin;
}

// Returns true if a single coordinate of a data point can be plotted.
//
// A coordinate is invalid when it is non-finite (NaN or +/-Inf) or, on a
// logarithmic dimension, when it is not strictly positive. Invalid coordinates
// break line curves, are excluded from auto-ranging, and are never hit-tested.
//
// Implemented with plain comparisons (false for NaN and +/-Inf) rather than
// std::isfinite(), which some standard libraries do not inline. This check runs
// per coordinate on multi-million-point buffers.
[[nodiscard]] inline bool isValidCoordinate(double value, bool logScale) noexcept
{
    return value >= std::numeric_limits<double>::lowest() && value <= std::numeric_limits<double>::max() && (!logScale || value > 0.0);
}

} // namespace QAccelPlot