mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
[shared] InteractiveCurveViewRange::isOrthonormal
The old method isOrthonormal has been split into two :
- shouldBeNormalized tests whether the range is close enough to a
normal range
- isOrthonormal tests whether the range is strictly orthonormal, but
takes into account imprecisions of floating-point arithmetic
This commit is contained in:
committed by
LeaNumworks
parent
89107da1ae
commit
89c50509d8
@@ -163,7 +163,7 @@ void Store::setDefault() {
|
||||
m_xRange.setMax(xMax);
|
||||
m_yRange.setMin(yMin);
|
||||
m_yRange.setMax(yMax);
|
||||
bool revertToOrthonormal = isOrthonormal(k_orthonormalTolerance);
|
||||
bool revertToOrthonormal = shouldBeNormalized();
|
||||
|
||||
float range = xMax - xMin;
|
||||
setXMin(xMin - k_displayHorizontalMarginRatio * range);
|
||||
|
||||
@@ -157,8 +157,13 @@ void InteractiveCurveViewRange::normalize(bool forceChangeY) {
|
||||
m_yRange.setMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat);
|
||||
MemoizedCurveViewRange::protectedSetYMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat);
|
||||
|
||||
/* The range should be close to orthonormal, unless it has been clipped because the maximum bounds have been reached. */
|
||||
assert(isOrthonormal() || xMin() <= - k_lowerMaxFloat || xMax() >= k_lowerMaxFloat || yMin() <= - k_lowerMaxFloat || yMax() >= k_lowerMaxFloat);
|
||||
/* The range should be close to orthonormal, unless :
|
||||
* - it has been clipped because the maximum bounds have been reached.
|
||||
* - the the bounds are too close and of too large a magnitude, leading to
|
||||
* a drastic loss of significance. */
|
||||
assert(isOrthonormal()
|
||||
|| xMin() <= - k_lowerMaxFloat || xMax() >= k_lowerMaxFloat || yMin() <= - k_lowerMaxFloat || yMax() >= k_lowerMaxFloat
|
||||
|| normalizationSignificantBits() <= 0);
|
||||
setZoomNormalize(isOrthonormal());
|
||||
}
|
||||
|
||||
@@ -193,7 +198,7 @@ void InteractiveCurveViewRange::setDefault() {
|
||||
m_yRange.setMin(roundLimit(m_delegate->addMargin(yMin(), yRange, true , true), yRange, true), k_lowerMaxFloat, k_upperMaxFloat);
|
||||
MemoizedCurveViewRange::protectedSetYMax(roundLimit(m_delegate->addMargin(yMax(), yRange, true , false), yRange, false), k_lowerMaxFloat, k_upperMaxFloat);
|
||||
|
||||
if (m_delegate->defaultRangeIsNormalized() || isOrthonormal(k_orthonormalTolerance)) {
|
||||
if (m_delegate->defaultRangeIsNormalized() || shouldBeNormalized()) {
|
||||
/* Normalize the axes, so that a polar circle is displayed as a circle.
|
||||
* If we are displaying cartesian functions with a default range, we want
|
||||
* the X bounds untouched. */
|
||||
@@ -276,8 +281,27 @@ void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float to
|
||||
setZoomNormalize(isOrthonormal());
|
||||
}
|
||||
|
||||
bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const {
|
||||
if (tolerance == 0.f) {
|
||||
bool InteractiveCurveViewRange::shouldBeNormalized() const {
|
||||
float ratio = (yMax() - yMin()) / (xMax() - xMin());
|
||||
return ratio >= NormalYXRatio() / k_orthonormalTolerance && ratio <= NormalYXRatio() * k_orthonormalTolerance;
|
||||
}
|
||||
|
||||
bool InteractiveCurveViewRange::isOrthonormal() const {
|
||||
float significantBits = normalizationSignificantBits();
|
||||
if (significantBits <= 0) {
|
||||
return false;
|
||||
}
|
||||
float ratio = (yMax() - yMin()) / (xMax() - xMin());
|
||||
/* The last N (= 23 - significantBits) bits of "ratio" mantissa have become
|
||||
* insignificant. "tolerance" is the difference between ratio with those N
|
||||
* bits set to 1, and ratio with those N bits set to 0 ; i.e. a measure of
|
||||
* the interval in which numbers are indistinguishable from ratio with this
|
||||
* level of precision. */
|
||||
float tolerance = std::pow(2.f, IEEE754<float>::exponent(ratio) - significantBits);
|
||||
return ratio - tolerance <= NormalYXRatio() && ratio + tolerance >= NormalYXRatio();
|
||||
}
|
||||
|
||||
int InteractiveCurveViewRange::normalizationSignificantBits() const {
|
||||
float xr = std::fabs(xMin()) > std::fabs(xMax()) ? xMax() / xMin() : xMin() / xMax();
|
||||
float yr = std::fabs(yMin()) > std::fabs(yMax()) ? yMax() / yMin() : yMin() / yMax();
|
||||
/* The subtraction x - y induces a loss of significance of -log2(1-x/y)
|
||||
@@ -285,9 +309,11 @@ bool InteractiveCurveViewRange::isOrthonormal(float tolerance) const {
|
||||
* the ratio of the normalized range will deviate from the Normal ratio. We
|
||||
* add an extra two lost bits to account for loss of precision from other
|
||||
* sources. */
|
||||
tolerance = std::pow(2.f, - std::log2(std::min(1.f - xr, 1.f - yr)) - 23.f + 2.f);
|
||||
}
|
||||
float ratio = (yMax() - yMin()) / (xMax() - xMin());
|
||||
return ratio <= NormalYXRatio() + tolerance && ratio >= NormalYXRatio() - tolerance;
|
||||
float loss = std::log2(std::min(1.f - xr, 1.f - yr));
|
||||
if (loss > 0.f) {
|
||||
loss = 0.f;
|
||||
}
|
||||
return std::floor(loss + 23.f - 2.f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ public:
|
||||
}
|
||||
|
||||
static constexpr float NormalYXRatio() { return NormalizedYHalfRange(1.f) / NormalizedXHalfRange(1.f); }
|
||||
/* If the tolerance is null, isOrthonormal will adapt the tolerance to take
|
||||
* the loss of significance when changing the ratio into account. */
|
||||
bool isOrthonormal(float tolerance = 0.f) const;
|
||||
/* The method isOrthonormal takes the loss of significance when changing the
|
||||
* ratio into account. */
|
||||
bool isOrthonormal() const;
|
||||
|
||||
void setDelegate(InteractiveCurveViewRangeDelegate * delegate);
|
||||
uint32_t rangeChecksum() override;
|
||||
@@ -62,7 +62,7 @@ protected:
|
||||
constexpr static float k_lowerMaxFloat = 9E+7f;
|
||||
constexpr static float k_maxRatioPositionRange = 1E5f;
|
||||
/* The tolerance is chosen to normalize sqrt(x) */
|
||||
constexpr static float k_orthonormalTolerance = 0.24f;
|
||||
constexpr static float k_orthonormalTolerance = 1.78f;
|
||||
static float clipped(float x, bool isMax) { return Range1D::clipped(x, isMax, k_lowerMaxFloat, k_upperMaxFloat); }
|
||||
/* In normalized settings, we put each axis so that 1cm = 2 units. For now,
|
||||
* the screen has size 43.2mm * 57.6mm.
|
||||
@@ -78,11 +78,13 @@ protected:
|
||||
* 2 * 1 unit -> 10.0mm
|
||||
* So normalizedYHalfRange = 43.2mm * 170/240 * 1 unit / 10.0mm */
|
||||
constexpr static float NormalizedYHalfRange(float unit) { return 3.06f * unit; }
|
||||
bool shouldBeNormalized() const;
|
||||
virtual bool hasDefaultRange() const { return (xMin() == std::round(xMin())) && (xMax() == std::round(xMax())); }
|
||||
|
||||
InteractiveCurveViewRangeDelegate * m_delegate;
|
||||
private:
|
||||
float offscreenYAxis() const override { return m_offscreenYAxis; }
|
||||
int normalizationSignificantBits() const;
|
||||
|
||||
float m_offscreenYAxis;
|
||||
bool m_zoomAuto;
|
||||
|
||||
Reference in New Issue
Block a user