Mercurial > octave
view liboctave/array/Range.cc @ 31606:dfa5d9c3ae72
maint: merge stable to default
author | Rik <rik@octave.org> |
---|---|
date | Thu, 01 Dec 2022 14:28:07 -0800 |
parents | fb77a0b4a00a e88a07dec498 |
children | 23664317f0d3 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 1993-2022 The Octave Project Developers // // See the file COPYRIGHT.md in the top-level directory of this // distribution or <https://octave.org/copyright/>. // // This file is part of Octave. // // Octave is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Octave is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Octave; see the file COPYING. If not, see // <https://www.gnu.org/licenses/>. // //////////////////////////////////////////////////////////////////////// #if defined (HAVE_CONFIG_H) # include "config.h" #endif #include <cmath> #include <istream> #include <limits> #include <ostream> #include "Array-util.h" #include "Range.h" #include "lo-error.h" #include "lo-mappers.h" #include "lo-utils.h" OCTAVE_BEGIN_NAMESPACE(octave) template <typename T> T xtfloor (T x, T ct) { // C---------FLOOR(X) is the largest integer algebraically less than // C or equal to X; that is, the unfuzzy FLOOR function. // DINT (X) = X - DMOD (X, 1.0); // FLOOR (X) = DINT (X) - DMOD (2.0 + DSIGN (1.0, X), 3.0); // C---------Hagerty's FL5 function follows... T q = 1; if (x < 0) q = 1 - ct; T rmax = q / (2 - ct); T t1 = 1 + std::floor (x); t1 = (ct / q) * (t1 < 0 ? -t1 : t1); t1 = (rmax < t1 ? rmax : t1); t1 = (ct > t1 ? ct : t1); t1 = std::floor (x + t1); if (x <= 0 || (t1 - x) < rmax) return t1; else return t1 - 1; } template <typename T> bool xteq (T u, T v, T ct = 3 * std::numeric_limits<T>::epsilon ()) { T tu = std::abs (u); T tv = std::abs (v); return std::abs (u - v) < ((tu > tv ? tu : tv) * ct); } template <typename T> octave_idx_type xnumel_internal (T base, T limit, T inc) { octave_idx_type retval = -1; if (! math::isfinite (base) || ! math::isfinite (inc) || math::isnan (limit)) retval = -2; else if (math::isinf (limit) && ((inc > 0 && limit > 0) || (inc < 0 && limit < 0))) retval = std::numeric_limits<octave_idx_type>::max () - 1; else if (inc == 0 || (limit > base && inc < 0) || (limit < base && inc > 0)) { retval = 0; } else { T ct = 3 * std::numeric_limits<T>::epsilon (); T tmp = xtfloor ((limit - base + inc) / inc, ct); octave_idx_type n_elt = (tmp > 0 ? static_cast<octave_idx_type> (tmp) : 0); // If the final element that we would compute for the range is // equal to the limit of the range, or is an adjacent floating // point number, accept it. Otherwise, try a range with one // fewer element. If that fails, try again with one more // element. // // I'm not sure this is very good, but it seems to work better // than just using tfloor as above. For example, without it, // the expression 1.8:0.05:1.9 fails to produce the expected // result of [1.8, 1.85, 1.9]. if (! xteq (base + (n_elt - 1) * inc, limit)) { if (xteq (base + (n_elt - 2) * inc, limit)) n_elt--; else if (xteq (base + n_elt * inc, limit)) n_elt++; } retval = (n_elt < std::numeric_limits<octave_idx_type>::max () - 1 ? n_elt : -1); } return retval; } template <typename T> bool xall_elements_are_ints (T base, T inc, T final_val, octave_idx_type nel) { // If the range is empty or NaN then there are no elements so there // can be no int elements. if (nel == 0 || math::isnan (final_val)) return false; // If the base and increment are ints, all elements will be // integers. if (math::nint_big (base) == base && math::nint_big (inc) == inc) return true; // If the range has only one element, then the base needs to be an // integer. if (nel == 1 && math::nint_big (base)) return true; return false; } template <typename T> T xfinal_value (T base, T limit, T inc, octave_idx_type nel) { T retval = T (0); if (nel <= 1) return base; // If increment is 0, then numel should also be zero. retval = base + (nel - 1) * inc; // On some machines (x86 with extended precision floating point // arithmetic, for example) it is possible that we can overshoot // the limit by approximately the machine precision even though // we were very careful in our calculation of the number of // elements. Therefore, we clip the result to the limit if it // overshoots. // NOTE: The test also includes equality (>= limit) to have // expressions such as -5:1:-0 result in a -0 endpoint. if ((inc > T (0) && retval >= limit) || (inc < T (0) && retval <= limit)) retval = limit; // If all elements are integers, then ensure the final value is. // Note that we pass the preliminary computed final value to // xall_elements_are_ints, but it only checks whether that value is // NaN. if (xall_elements_are_ints (base, inc, retval, nel)) retval = std::round (retval); return retval; } template <typename T> void xinit (T base, T limit, T inc, bool reverse, T& final_val, octave_idx_type& nel) { // Catch obvious NaN ranges. if (math::isnan (base) || math::isnan (limit) || math::isnan (inc)) { final_val = numeric_limits<T>::NaN (); nel = 1; return; } // Floating point numbers are always signed if (reverse) inc = -inc; // Catch empty ranges. if (inc == 0 || (limit < base && inc > 0) || (limit > base && inc < 0)) { nel = 0; return; } // The following case also catches Inf values for increment when // there will be only one element. if ((limit <= base && base + inc < limit) || (limit >= base && base + inc > limit)) { final_val = base; nel = 1; return; } // Any other calculations with Inf will give us either a NaN range // or an infinite nember of elements. T dnel = (limit - base) / inc; if (math::isnan (dnel)) { nel = 1; final_val = numeric_limits<T>::NaN (); return; } if (dnel > 0 && math::isinf (dnel)) { // FIXME: Should this be an immediate error? nel = std::numeric_limits<octave_idx_type>::max (); // FIXME: Will this do the right thing in all cases? final_val = xfinal_value (base, limit, inc, nel); return; } // Now that we have handled all the special cases, we can compute // the number of elements and the final value in a way that attempts // to avoid rounding errors as much as possible. nel = xnumel_internal (base, limit, inc); final_val = xfinal_value (base, limit, inc, nel); } template <typename T> void xinit (const octave_int<T>& base, const octave_int<T>& limit, const octave_int<T>& inc, bool reverse, octave_int<T>& final_val, octave_idx_type& nel) { // We need an integer division that is truncating decimals instead // of rounding. So, use underlying C++ types instead of // octave_int<T>. // FIXME: The numerator might underflow or overflow. Add checks for // that. if (reverse) { nel = ((inc == octave_int<T> (0) || (limit > base && inc > octave_int<T> (0)) || (limit < base && inc < octave_int<T> (0))) ? 0 : (base.value () - limit.value () + inc.value ()) / inc.value ()); final_val = base - (nel - 1) * inc; } else { nel = ((inc == octave_int<T> (0) || (limit > base && inc < octave_int<T> (0)) || (limit < base && inc > octave_int<T> (0))) ? 0 : (limit.value () - base.value () + inc.value ()) / inc.value ()); final_val = base + (nel - 1) * inc; } } template <typename T> bool xis_storable (T base, T limit, octave_idx_type nel) { return ! (nel > 1 && (math::isinf (base) || math::isinf (limit))); } template <> bool range<double>::all_elements_are_ints (void) const { return xall_elements_are_ints (m_base, m_increment, m_final, m_numel); } template <> bool range<float>::all_elements_are_ints (void) const { return xall_elements_are_ints (m_base, m_increment, m_final, m_numel); } template <> void range<double>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<float>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } // For now, only define for float and double. #if 0 template <> void range<octave_int8>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_int16>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_int32>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_int64>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_uint8>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_uint16>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_uint32>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } template <> void range<octave_uint64>::init (void) { xinit (m_base, m_limit, m_increment, m_reverse, m_final, m_numel); } #endif template <> bool range<double>::is_storable (void) const { return xis_storable (m_base, m_limit, m_numel); } template <> bool range<float>::is_storable (void) const { return xis_storable (m_base, m_limit, m_numel); } template <typename T> octave_idx_type xnnz (T base, T limit, T inc, T final_val, octave_idx_type nel) { // Note that the order of the following checks matters. // If there are no elements, there can be no non-zero elements. if (nel == 0) return 0; // All elements have the same sign, hence there are no zeros. if ((base > 0 && limit > 0) || (base < 0 && limit < 0)) return nel; // All elements are equal (inc = 0) but we know from the previous // condition that they are not positive or negative, therefore all // elements are zero. if (inc == 0) return 0; // Exactly one zero at beginning or end of range. if (base == 0 || final_val == 0) return nel - 1; // Range crosses negative/positive without hitting zero. // FIXME: Is this test sufficiently tolerant or do we need to be // more careful? if (math::mod (-base, inc) != 0) return nel; // Range crosses negative/positive and hits zero. return nel - 1; } template <> octave_idx_type range<double>::nnz (void) const { return xnnz (m_base, m_limit, m_increment, m_final, m_numel); } template <> octave_idx_type range<float>::nnz (void) const { return xnnz (m_base, m_limit, m_increment, m_final, m_numel); } OCTAVE_END_NAMESPACE(octave)