Ardour  9.0-pre0-582-g084a23a80d
int62.h
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2020 Paul Davis <paul@linuxaudiosystems.com>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #ifndef __libpbd_int62_h__
20 #define __libpbd_int62_h__
21 
22 #include <atomic>
23 #include <cstdint>
24 #include <cstdlib>
25 #include <iostream>
26 #include <exception>
27 #include <limits>
28 
29 /* int62_t is a class that functions as a 63 bit signed integer with a flag that can be used to indicate a boolean property of
30  * the object. The flag is stored inside the 64 bit integer used by the object (as a single bit), and all operations on the object that
31  * change either the flag or the value are atomic.
32  *
33  * this was written to function as a base class for a timeline positional/distance type which needs to indicate whether it represents
34  * audio time or musical time.
35  */
36 
37 class alignas(16) int62_t {
38  protected:
39  /* std::atomic<> takes care of memory barriers for us; the actual load and stores
40  are atomic on architectures that we're likely to care about.
41  The 2nd highest bit is used for the flag, and the highest bit is used for the sign.
42  Watch out for the impact of two's complement and overflow to and from the flag bit.
43  */
44  std::atomic<int64_t> v;
45 
46  /* this defines the bit used to indicate "flag" or not: the 2nd highest bit */
47  static const int64_t flagbit_mask = (1LL<<62);
48 
49  protected:
50  /* the "flagbit" follows 2's complement logic. It is "set" if the value is positive and the bit is 1; it is also set if the
51  * value is negative and bit is 0.
52  */
53  static int64_t int62 (int64_t v) { if (v >= 0) { return v & ~flagbit_mask; } return (v | flagbit_mask); }
54  static bool flagged (int64_t v) { if (v >= 0) { return v & flagbit_mask; } return ((v & flagbit_mask) == 0); }
55 
56  public:
57  /* this is really a private method but is useful to construct the int64_t value when building tests. It is static anyway, so
58  providing public access doesn't hurt.
59  */
60  static int64_t build (bool flag, int64_t v) { if (v >= 0) { return (flag ? flagbit_mask : 0) | v; } return flag ? (v & ~flagbit_mask) : v; }
61 
62  int62_t () : v (0) {}
63  int62_t (bool bc, int64_t vc) : v (build (bc, vc)) {}
64  int62_t (int62_t const & other) { v.store (other.v.load(std::memory_order_acquire), std::memory_order_release); }
65 
66  static const int64_t max = 4611686018427387903; /* 2^62 - 1 */
67  static const int64_t min = -2305843009213693952;
68 
69  bool flagged() const { return flagged (v); }
70  int64_t val() const { return int62(v); }
71 
72  int62_t& operator= (int64_t n) { v.store (build (flagged (v.load(std::memory_order_acquire)), n), std::memory_order_release); return *this; }
73  int62_t& operator= (int62_t const & other) { v.store (other.v.load(std::memory_order_acquire), std::memory_order_release); return *this; }
74 
75  /* there's a pattern to many of these operators:
76 
77  1) atomically load the current in64_t into "tmp". This value has
78  both the flag bit and the values bits of this int62_t.
79 
80  2) constructor a new int62_t from
81  (a) is the flag bit set (using ::flagged (tmp))
82  (b) the result of applying the operator (plus arg) to the value
83  bits (obtained using ::int62 (tmp))
84 
85  Note that we need to ensure that we're atomically determining both
86  the flag bit and values bit, hence the initial load into "tmp"
87  rather than two separate loads for each "part".
88  */
89 
90  int62_t operator- () const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), -int62(vv)); }
91 
92  int62_t operator+ (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) + n); }
93  int62_t operator- (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) - n); }
94  int62_t operator* (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) * n); }
95  int62_t operator/ (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) / n); }
96  int62_t operator% (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) % n); }
97 
98  int62_t operator+ (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) + n.val()); }
99  int62_t operator- (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) - n.val()); }
100  int62_t operator* (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) * n.val()); }
101  int62_t operator/ (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) / n.val()); }
102  int62_t operator% (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) % n.val()); }
103 
104  /* comparison operators .. will throw if the two objects have different
105  * flag settings (which is assumed to indicate that they differ in some
106  * important respect, and thus should not have their values compared)
107  */
108 
109  struct flag_mismatch : public std::exception {
111  const char* what () const throw () { return "mismatched flags in int62_t"; }
112  };
113 
114  bool operator< (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() < other.val(); }
115  bool operator<= (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() <= other.val(); }
116  bool operator> (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() > other.val(); }
117  bool operator>= (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() >= other.val(); }
118 
119  /* don't throw flag_mismatch for explicit equality checks, since
120  * the semantics are well defined and the computation cost is trivial
121  */
122 
123  bool operator!= (int62_t const & other) const { const int64_t vv = v.load(std::memory_order_acquire); if (flagged (vv) != other.flagged()) return true; return int62 (vv) != other.val(); }
124  bool operator== (int62_t const & other) const { const int64_t vv = v.load(std::memory_order_acquire); if (flagged (vv) != other.flagged()) return false; return int62 (vv) == other.val(); }
125 
126  explicit operator int64_t() const { return int62(v); }
127 
128  bool operator< (int64_t n) const { return val() < n; }
129  bool operator<= (int64_t n) const { return val() <= n; }
130  bool operator> (int64_t n) const { return val() > n; }
131  bool operator>= (int64_t n) const { return val() >= n; }
132  bool operator!= (int64_t n) const { return val() != n; }
133  bool operator== (int64_t n) const { return val() == n; }
134 
135  int62_t abs() const { const int64_t tmp = v; return int62_t (flagged(tmp), ::llabs(int62(tmp))); }
136 
137  int62_t& operator+= (int64_t n) {
138  int64_t oldval = v.load (std::memory_order_acquire);
139  int64_t newval = build (flagged (oldval), int62 (oldval) + n);
140  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
141  newval = build (flagged (oldval), int62 (oldval) + n);
142  }
143  return *this;
144  }
145  int62_t& operator-= (int64_t n) {
146  int64_t oldval = v.load (std::memory_order_acquire);
147  int64_t newval = build (flagged (oldval), int62 (oldval) - n);
148  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
149  newval = build (flagged (oldval), int62 (oldval) - n);
150  }
151  return *this;
152  }
153  int62_t& operator*= (int64_t n) {
154  int64_t oldval = v.load (std::memory_order_acquire);
155  int64_t newval = build (flagged (oldval), int62 (oldval) * n);
156  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
157  newval = build (flagged (oldval), int62 (oldval) * n);
158  }
159  return *this;
160  }
161  int62_t& operator/= (int64_t n) {
162  int64_t oldval = v.load (std::memory_order_acquire);
163  int64_t newval = build (flagged (oldval), int62 (oldval) / n);
164  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
165  newval = build (flagged (oldval), int62 (oldval) / n);
166  }
167  return *this;
168  }
169  int62_t& operator%= (int64_t n) {
170  int64_t oldval = v.load (std::memory_order_acquire);
171  int64_t newval = build (flagged (oldval), int62 (oldval) % n);
172  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
173  newval = build (flagged (oldval), int62 (oldval) % n);
174  }
175  return *this;
176  }
177 
179  int64_t oldval = v.load (std::memory_order_acquire);
180  int64_t newval = build (flagged (oldval), int62 (oldval) + n.val());
181  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
182  newval = build (flagged (oldval), int62 (oldval) + n.val());
183  }
184  return *this;
185  }
187  int64_t oldval = v.load (std::memory_order_acquire);
188  int64_t newval = build (flagged (oldval), int62 (oldval) - n.val());
189  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
190  newval = build (flagged (oldval), int62 (oldval) - n.val());
191  }
192  return *this;
193  }
195  int64_t oldval = v.load (std::memory_order_acquire);
196  int64_t newval = build (flagged (oldval), int62 (oldval) * n.val());
197  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
198  newval = build (flagged (oldval), int62 (oldval) * n.val());
199  }
200  return *this;
201  }
203  int64_t oldval = v.load (std::memory_order_acquire);
204  int64_t newval = build (flagged (oldval), int62 (oldval) / n.val());
205  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
206  newval = build (flagged (oldval), int62 (oldval) / n.val());
207  }
208  return *this;
209  }
211  int64_t oldval = v.load (std::memory_order_acquire);
212  int64_t newval = build (flagged (oldval), int62 (oldval) % n.val());
213  while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
214  newval = build (flagged (oldval), int62 (oldval) % n.val());
215  }
216  return *this;
217  }
218 
219 };
220 
221 namespace std {
222  template<>
223  struct numeric_limits<int62_t> {
224  static int62_t min() { return int62_t (false, -2305843009213693952); }
225  static int62_t max() { return int62_t (false, 4611686018427387904); }
226  };
227 }
228 
229 #endif /* __libpbd_int62_h__ */
Definition: int62.h:37
static bool flagged(int64_t v)
Definition: int62.h:54
static const int64_t flagbit_mask
Definition: int62.h:47
int62_t operator-() const
Definition: int62.h:90
int62_t operator*(int64_t n) const
Definition: int62.h:94
static const int64_t min
Definition: int62.h:67
int62_t & operator+=(int64_t n)
Definition: int62.h:137
int62_t abs() const
Definition: int62.h:135
int62_t operator+(int64_t n) const
Definition: int62.h:92
static const int64_t max
Definition: int62.h:66
int62_t & operator=(int64_t n)
Definition: int62.h:72
bool operator<=(int62_t const &other) const
Definition: int62.h:115
bool operator>=(int62_t const &other) const
Definition: int62.h:117
int62_t(bool bc, int64_t vc)
Definition: int62.h:63
bool operator!=(int62_t const &other) const
Definition: int62.h:123
int62_t & operator*=(int64_t n)
Definition: int62.h:153
int62_t & operator-=(int64_t n)
Definition: int62.h:145
int62_t operator%(int64_t n) const
Definition: int62.h:96
bool operator<(int62_t const &other) const
Definition: int62.h:114
bool flagged() const
Definition: int62.h:69
int62_t & operator%=(int64_t n)
Definition: int62.h:169
int62_t & operator/=(int64_t n)
Definition: int62.h:161
std::atomic< int64_t > v
Definition: int62.h:44
int62_t()
Definition: int62.h:62
static int64_t build(bool flag, int64_t v)
Definition: int62.h:60
bool operator>(int62_t const &other) const
Definition: int62.h:116
int62_t operator/(int64_t n) const
Definition: int62.h:95
static int64_t int62(int64_t v)
Definition: int62.h:53
int64_t val() const
Definition: int62.h:70
bool operator==(int62_t const &other) const
Definition: int62.h:124
int62_t(int62_t const &other)
Definition: int62.h:64
const char * what() const
Definition: int62.h:111
static int62_t max()
Definition: int62.h:225
static int62_t min()
Definition: int62.h:224