GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
ov-legacy-range.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 1996-2024 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <istream>
31 #include <ostream>
32 #include <sstream>
33 
34 #include "Range.h"
35 #include "lo-ieee.h"
36 #include "lo-utils.h"
37 
38 #include "variables.h"
39 #include "error.h"
40 #include "ovl.h"
41 #include "oct-hdf5.h"
42 #include "ov-legacy-range.h"
43 #include "ov-range.h"
44 #include "ov-re-mat.h"
45 #include "ov-scalar.h"
46 #include "pr-output.h"
47 
48 #include "byte-swap.h"
49 #include "ls-ascii-helper.h"
50 #include "ls-hdf5.h"
51 #include "ls-utils.h"
52 
53 class
54 Range
55 {
56 public:
57 
58  Range ()
59  : m_base (0), m_limit (0), m_inc (0), m_numel (0)
60  { }
61 
62  // Assume range is already properly constructed, so just copy internal
63  // values. However, we set LIMIT to the computed final value because
64  // that mimics the behavior of the other Range class constructors that
65  // reset limit to the computed final value.
66 
67  Range (const octave::range<double>& r)
68  : m_base (r.base ()), m_limit (r.final_value ()), m_inc (r.increment ()),
69  m_numel (r.numel ())
70  { }
71 
72  Range (const Range& r) = default;
73 
74  Range& operator = (const Range& r) = default;
75 
76  ~Range () = default;
77 
78  Range (double b, double l)
79  : m_base (b), m_limit (l), m_inc (1), m_numel (numel_internal ())
80  {
81  if (! octave::math::isinf (m_limit))
82  m_limit = limit_internal ();
83  }
84 
85  Range (double b, double l, double i)
86  : m_base (b), m_limit (l), m_inc (i), m_numel (numel_internal ())
87  {
88  if (! octave::math::isinf (m_limit))
89  m_limit = limit_internal ();
90  }
91 
92  // The range has a finite number of elements.
93  bool ok () const
94  {
95  return (octave::math::isfinite (m_limit)
96  && (m_numel >= 0 || m_numel == -2));
97  }
98 
99  double base () const { return m_base; }
100  double limit () const { return m_limit; }
101  double increment () const { return m_inc; }
102 
103  octave_idx_type numel () const { return m_numel; }
104 
105  bool all_elements_are_ints () const;
106 
107  Matrix matrix_value () const;
108 
109  double min () const;
110  double max () const;
111 
112 private:
113 
114  double m_base;
115  double m_limit;
116  double m_inc;
117 
118  octave_idx_type m_numel;
119 
120  octave_idx_type numel_internal () const;
121 
122  double limit_internal () const;
123 
124  void init ();
125 };
126 
127 bool
128 Range::all_elements_are_ints () const
129 {
130  // If the base and increment are ints, the final value in the range will also
131  // be an integer, even if the limit is not. If there is one or fewer
132  // elements only the base needs to be an integer.
133 
134  return (! (octave::math::isnan (m_base) || octave::math::isnan (m_inc))
135  && (octave::math::nint_big (m_base) == m_base || m_numel < 1)
136  && (octave::math::nint_big (m_inc) == m_inc || m_numel <= 1));
137 }
138 
139 Matrix
140 Range::matrix_value () const
141 {
142  Matrix retval (1, m_numel);
143 
144  if (m_numel > 0)
145  {
146  // The first element must always be *exactly* the base.
147  // E.g, -0 would otherwise become +0 in the loop (-0 + 0*increment).
148  retval(0) = m_base;
149 
150  double b = m_base;
151  double increment = m_inc;
152  for (octave_idx_type i = 1; i < m_numel - 1; i++)
153  retval.xelem (i) = b + i * increment;
154 
155  retval.xelem (m_numel - 1) = m_limit;
156  }
157 
158  return retval;
159 }
160 
161 // NOTE: max and min only return useful values if numel > 0.
162 // do_minmax_body() in max.cc avoids calling Range::min/max if numel == 0.
163 
164 double
165 Range::min () const
166 {
167  double retval = 0.0;
168  if (m_numel > 0)
169  {
170  if (m_inc > 0)
171  retval = m_base;
172  else
173  {
174  retval = m_base + (m_numel - 1) * m_inc;
175 
176  // Require '<=' test. See note in max ().
177  if (retval <= m_limit)
178  retval = m_limit;
179  }
180 
181  }
182  return retval;
183 }
184 
185 double
186 Range::max () const
187 {
188  double retval = 0.0;
189  if (m_numel > 0)
190  {
191  if (m_inc > 0)
192  {
193  retval = m_base + (m_numel - 1) * m_inc;
194 
195  // On some machines (x86 with extended precision floating point
196  // arithmetic, for example) it is possible that we can overshoot the
197  // limit by approximately the machine precision even though we were
198  // very careful in our calculation of the number of elements.
199  // Therefore, we clip the result to the limit if it overshoots.
200  // The test also includes equality (>= m_limit) to have expressions
201  // such as -5:1:-0 result in a -0 endpoint.
202  if (retval >= m_limit)
203  retval = m_limit;
204  }
205  else
206  retval = m_base;
207  }
208  return retval;
209 }
210 
211 // C See Knuth, Art Of Computer Programming, Vol. 1, Problem 1.2.4-5.
212 // C
213 // C===Tolerant FLOOR function.
214 // C
215 // C X - is given as a Double Precision argument to be operated on.
216 // C It is assumed that X is represented with M mantissa bits.
217 // C CT - is given as a Comparison Tolerance such that
218 // C 0.LT.CT.LE.3-SQRT(5)/2. If the relative difference between
219 // C X and A whole number is less than CT, then TFLOOR is
220 // C returned as this whole number. By treating the
221 // C floating-point numbers as a finite ordered set note that
222 // C the heuristic EPS=2.**(-(M-1)) and CT=3*EPS causes
223 // C arguments of TFLOOR/TCEIL to be treated as whole numbers
224 // C if they are exactly whole numbers or are immediately
225 // C adjacent to whole number representations. Since EPS, the
226 // C "distance" between floating-point numbers on the unit
227 // C interval, and M, the number of bits in X'S mantissa, exist
228 // C on every floating-point computer, TFLOOR/TCEIL are
229 // C consistently definable on every floating-point computer.
230 // C
231 // C For more information see the following references:
232 // C (1) P. E. Hagerty, "More On Fuzzy Floor And Ceiling," APL QUOTE
233 // C QUAD 8(4):20-24, June 1978. Note that TFLOOR=FL5.
234 // C (2) L. M. Breed, "Definitions For Fuzzy Floor And Ceiling", APL
235 // C QUOTE QUAD 8(3):16-23, March 1978. This paper cites FL1 through
236 // C FL5, the history of five years of evolutionary development of
237 // C FL5 - the seven lines of code below - by open collaboration
238 // C and corroboration of the mathematical-computing community.
239 // C
240 // C Penn State University Center for Academic Computing
241 // C H. D. Knoble - August, 1978.
242 
243 static inline double
244 tfloor (double x, double ct)
245 {
246 // C---------FLOOR(X) is the largest integer algebraically less than
247 // C or equal to X; that is, the unfuzzy FLOOR function.
248 
249 // DINT (X) = X - DMOD (X, 1.0);
250 // FLOOR (X) = DINT (X) - DMOD (2.0 + DSIGN (1.0, X), 3.0);
251 
252 // C---------Hagerty's FL5 function follows...
253 
254  double q = 1.0;
255 
256  if (x < 0.0)
257  q = 1.0 - ct;
258 
259  double rmax = q / (2.0 - ct);
260 
261  double t1 = 1.0 + std::floor (x);
262  t1 = (ct / q) * (t1 < 0.0 ? -t1 : t1);
263  t1 = (rmax < t1 ? rmax : t1);
264  t1 = (ct > t1 ? ct : t1);
265  t1 = std::floor (x + t1);
266 
267  if (x <= 0.0 || (t1 - x) < rmax)
268  return t1;
269  else
270  return t1 - 1.0;
271 }
272 
273 static inline bool
274 teq (double u, double v,
275  double ct = 3.0 * std::numeric_limits<double>::epsilon ())
276 {
277  double tu = std::abs (u);
278  double tv = std::abs (v);
279 
280  return std::abs (u - v) < ((tu > tv ? tu : tv) * ct);
281 }
282 
284 Range::numel_internal () const
285 {
286  octave_idx_type retval = -1;
287 
288  if (! octave::math::isfinite (m_base) || ! octave::math::isfinite (m_inc)
289  || octave::math::isnan (m_limit))
290  retval = -2;
291  else if (octave::math::isinf (m_limit)
292  && ((m_inc > 0 && m_limit > 0)
293  || (m_inc < 0 && m_limit < 0)))
295  else if (m_inc == 0
296  || (m_limit > m_base && m_inc < 0)
297  || (m_limit < m_base && m_inc > 0))
298  {
299  retval = 0;
300  }
301  else
302  {
303  double ct = 3.0 * std::numeric_limits<double>::epsilon ();
304 
305  double tmp = tfloor ((m_limit - m_base + m_inc) / m_inc, ct);
306 
307  octave_idx_type n_elt = (tmp > 0.0
308  ? static_cast<octave_idx_type> (tmp) : 0);
309 
310  // If the final element that we would compute for the range is equal to
311  // the limit of the range, or is an adjacent floating point number,
312  // accept it. Otherwise, try a range with one fewer element. If that
313  // fails, try again with one more element.
314  //
315  // I'm not sure this is very good, but it seems to work better than just
316  // using tfloor as above. For example, without it, the expression
317  // 1.8:0.05:1.9 fails to produce the expected result of [1.8, 1.85, 1.9].
318 
319  if (! teq (m_base + (n_elt - 1) * m_inc, m_limit))
320  {
321  if (teq (m_base + (n_elt - 2) * m_inc, m_limit))
322  n_elt--;
323  else if (teq (m_base + n_elt * m_inc, m_limit))
324  n_elt++;
325  }
326 
328  ? n_elt : -1);
329  }
330 
331  return retval;
332 }
333 
334 double
335 Range::limit_internal () const
336 {
337  double new_limit = m_inc > 0 ? max () : min ();
338 
339  // If result must be an integer then force the new_limit to be one.
340  if (all_elements_are_ints ())
341  new_limit = std::round (new_limit);
342 
343  return new_limit;
344 }
345 
346 void
347 Range::init ()
348 {
349  m_numel = numel_internal ();
350 
351  if (! octave::math::isinf (m_limit))
352  m_limit = limit_internal ();
353 }
354 
356 
358  : octave_base_value (), m_range (new Range ()) { }
359 
361  : octave_base_value (), m_range (new Range (r))
362 {
363  if (m_range->numel () < 0 && m_range->numel () != -2)
364  error ("invalid range");
365 }
366 
368  : octave_base_value (r), m_range ()
369 {
370  m_range.reset (new Range (*(r.m_range)));
371 }
372 
373 static octave_base_value *
374 default_numeric_conversion_function (const octave_base_value& a)
375 {
376  const octave_legacy_range& v = dynamic_cast<const octave_legacy_range&> (a);
377 
378  return new octave_matrix (v.matrix_value ());
379 }
380 
383 {
384  return octave_base_value::type_conv_info (default_numeric_conversion_function,
386 }
387 
390 {
391  octave_base_value *retval = nullptr;
392 
393  switch (m_range->numel ())
394  {
395  case 1:
396  retval = new octave_scalar (m_range->base ());
397  break;
398 
399  case 0:
400  retval = new octave_matrix (Matrix (1, 0));
401  break;
402 
403  case -2:
404  retval = new octave_matrix (m_range->matrix_value ());
405  break;
406 
407  default:
408  {
409  if (m_range->increment () == 0)
410  retval = new octave_matrix (m_range->matrix_value ());
411  else
412  retval = new octave_range
413  (octave::range<double> (m_range->base (), m_range->increment (),
414  m_range->limit (), m_range->numel ()));
415  }
416  break;
417  }
418 
419  return retval;
420 }
421 
422 // Skip white space and comments on stream IS.
423 
424 static void
425 skip_comments (std::istream& is)
426 {
427  char c = '\0';
428  while (is.get (c))
429  {
430  if (c == ' ' || c == '\t' || c == '\n')
431  ; // Skip whitespace on way to beginning of next line.
432  else
433  break;
434  }
435 
436  octave::skip_until_newline (is, false);
437 }
438 
439 bool
441 {
442  // # base, limit, range comment added by save ().
443  skip_comments (is);
444 
445  double base, limit, inc;
446  is >> base >> limit >> inc;
447 
448  if (! is)
449  error ("load: failed to load range constant");
450 
451  if (inc != 0)
452  m_range.reset (new Range (base, limit, inc));
453  else
454  m_range.reset (new Range (base, inc, static_cast<octave_idx_type> (limit)));
455 
456  return true;
457 }
458 
459 bool
460 octave_legacy_range::load_binary (std::istream& is, bool swap,
462 {
463  char tmp;
464  if (! is.read (reinterpret_cast<char *> (&tmp), 1))
465  return false;
466  double bas, lim, inc;
467  if (! is.read (reinterpret_cast<char *> (&bas), 8))
468  return false;
469  if (swap)
470  swap_bytes<8> (&bas);
471  if (! is.read (reinterpret_cast<char *> (&lim), 8))
472  return false;
473  if (swap)
474  swap_bytes<8> (&lim);
475  if (! is.read (reinterpret_cast<char *> (&inc), 8))
476  return false;
477  if (swap)
478  swap_bytes<8> (&inc);
479  if (inc != 0)
480  m_range.reset (new Range (bas, lim, inc));
481  else
482  m_range.reset (new Range (bas, inc, static_cast<octave_idx_type> (lim)));
483 
484  return true;
485 }
486 
487 #if defined (HAVE_HDF5)
488 
489 // The following subroutines creates an HDF5 representation of the way
490 // we will store Octave range types (triplets of floating-point numbers).
491 // NUM_TYPE is the HDF5 numeric type to use for storage (e.g.
492 // H5T_NATIVE_DOUBLE to save as 'double'). Note that any necessary
493 // conversions are handled automatically by HDF5.
494 
495 static hid_t
496 hdf5_make_range_type (hid_t num_type)
497 {
498  hid_t type_id = H5Tcreate (H5T_COMPOUND, sizeof (double) * 3);
499 
500  H5Tinsert (type_id, "base", 0 * sizeof (double), num_type);
501  H5Tinsert (type_id, "limit", 1 * sizeof (double), num_type);
502  H5Tinsert (type_id, "increment", 2 * sizeof (double), num_type);
503 
504  return type_id;
505 }
506 
507 #endif
508 
509 bool
511 {
512  bool retval = false;
513 
514 #if defined (HAVE_HDF5)
515 
516 #if defined (HAVE_HDF5_18)
517  hid_t data_hid = H5Dopen (loc_id, name, octave_H5P_DEFAULT);
518 #else
519  hid_t data_hid = H5Dopen (loc_id, name);
520 #endif
521  hid_t type_hid = H5Dget_type (data_hid);
522 
523  hid_t range_type = hdf5_make_range_type (H5T_NATIVE_DOUBLE);
524 
525  if (! hdf5_types_compatible (type_hid, range_type))
526  {
527  H5Tclose (range_type);
528  H5Dclose (data_hid);
529  return false;
530  }
531 
532  hid_t space_hid = H5Dget_space (data_hid);
533  hsize_t rank = H5Sget_simple_extent_ndims (space_hid);
534 
535  if (rank != 0)
536  {
537  H5Tclose (range_type);
538  H5Sclose (space_hid);
539  H5Dclose (data_hid);
540  return false;
541  }
542 
543  double rangevals[3];
544  if (H5Dread (data_hid, range_type, octave_H5S_ALL, octave_H5S_ALL,
545  octave_H5P_DEFAULT, rangevals)
546  >= 0)
547  {
548  retval = true;
549  octave_idx_type nel;
550  if (hdf5_get_scalar_attr (data_hid, H5T_NATIVE_IDX,
551  "OCTAVE_RANGE_NELEM", &nel))
552  m_range.reset (new Range (rangevals[0], rangevals[2], nel));
553  else
554  {
555  if (rangevals[2] != 0)
556  m_range.reset (new Range (rangevals[0], rangevals[1], rangevals[2]));
557  else
558  m_range.reset (new Range (rangevals[0], rangevals[2],
559  static_cast<octave_idx_type> (rangevals[1])));
560  }
561  }
562 
563  H5Tclose (range_type);
564  H5Sclose (space_hid);
565  H5Dclose (data_hid);
566 
567 #else
568  octave_unused_parameter (loc_id);
569  octave_unused_parameter (name);
570 
571  warn_load ("hdf5");
572 #endif
573 
574  return retval;
575 }
void swap_bytes< 8 >(void *ptr)
Definition: byte-swap.h:71
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
charNDArray min(char d, const charNDArray &m)
Definition: chNDArray.cc:207
Definition: dMatrix.h:42
void warn_load(const char *type) const
Definition: ov-base.cc:1157
virtual Matrix matrix_value(bool=false) const
Definition: ov-base.cc:600
bool load_ascii(std::istream &is)
bool load_binary(std::istream &is, bool swap, octave::mach_info::float_format fmt)
bool load_hdf5(octave_hdf5_id loc_id, const char *name)
octave_base_value * try_narrowing_conversion()
type_conv_info numeric_conversion_function() const
static int static_type_id()
Definition: ov-re-mat.h:249
const octave_hdf5_id octave_H5P_DEFAULT
const octave_hdf5_id octave_H5S_ALL
void() error(const char *fmt,...)
Definition: error.cc:988
octave_idx_type nint_big(double x)
Definition: lo-mappers.cc:188
bool isfinite(double x)
Definition: lo-mappers.h:192
bool isinf(double x)
Definition: lo-mappers.h:203
double round(double x)
Definition: lo-mappers.h:136
bool isnan(bool)
Definition: lo-mappers.h:178
std::complex< T > floor(const std::complex< T > &x)
Definition: lo-mappers.h:130
F77_RET_T const F77_DBLE * x
void skip_until_newline(std::istream &is, bool keep_newline)
bool hdf5_get_scalar_attr(octave_hdf5_id loc_id, octave_hdf5_id type_id, const char *attr_name, void *buf)
Definition: ls-hdf5.cc:347
bool hdf5_types_compatible(octave_hdf5_id t1, octave_hdf5_id t2)
Definition: ls-hdf5.cc:269
float_format
Definition: mach-info.h:38
T * r
Definition: mx-inlines.cc:781
int64_t octave_hdf5_id
#define H5T_NATIVE_IDX
Definition: oct-hdf5.h:42
T::size_type numel(const T &str)
Definition: oct-string.cc:74
#define DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA(t, n, c)
Definition: ov-base.h:235
octave_double_range octave_range
Definition: ov-range.h:573