MUSX Document Model
Loading...
Searching...
No Matches
Fraction.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include <iostream>
25#include <numeric>
26#include <stdexcept>
27#include <limits>
28
29#include "musx/dom/Fundamentals.h"
30
31namespace musx {
32namespace util {
33
38class [[nodiscard]] Fraction {
39private:
40 int m_numerator = 0;
41 int m_denominator = 1;
42
47 static constexpr std::pair<int, int> reduce(int num, int den) {
48 int gcd = std::gcd(num, den);
49 num /= gcd;
50 den /= gcd;
51
52 // Ensure denominator is always positive
53 if (den < 0) {
54 num = -num;
55 den = -den;
56 }
57
58 return {num, den};
59 }
60
61 friend class std::numeric_limits<Fraction>;
62
63public:
64 constexpr Fraction() = default;
65
72 constexpr Fraction(int num, int den)
73 {
74 if (den == 0) {
75 throw std::invalid_argument("Denominator cannot be zero.");
76 }
77
78 auto [n, d] = reduce(num, den);
79 m_numerator = n;
80 m_denominator = d;
81 }
82
88 constexpr Fraction(int value) : m_numerator(value), m_denominator(1) {}
89
93 static constexpr Fraction fromEdu(dom::Edu edu) { return Fraction(edu, dom::EDU_PER_WHOLE_NOTE); }
94
97 static constexpr Fraction fromPercent(int percent) { return Fraction(percent, 100); }
98
103 [[nodiscard]]
104 constexpr int numerator() const { return m_numerator; }
105
110 [[nodiscard]]
111 constexpr int denominator() const { return m_denominator; }
112
117 [[nodiscard]]
118 constexpr int quotient() const {
119 return m_numerator / m_denominator;
120 }
121
126 Fraction constexpr remainder() const {
127 return Fraction(m_numerator % m_denominator, m_denominator);
128 }
129
131 constexpr Fraction reciprocal() const {
132 return Fraction(m_denominator, m_numerator);
133 }
134
141 [[nodiscard]]
142 constexpr dom::Edu calcEduDuration() const
143 {
144 using Edu = dom::Edu;
145 using Wide = std::int64_t;
146
147 // Do the scaling in wide integer to avoid overflow.
148 const auto num = Wide(m_numerator) * Wide(dom::EDU_PER_WHOLE_NOTE);
149 const auto den = Wide(m_denominator); // always > 0 by class invariant
150
151 // Round to nearest, 0.5 away from zero, without using double.
152 auto rounded = Wide{};
153 if (num >= 0) {
154 rounded = (num + den / 2) / den;
155 } else {
156 rounded = (num - den / 2) / den;
157 }
158
159 // Saturate to dom::Edu range.
160 constexpr auto maxEdu = static_cast<Wide>((std::numeric_limits<Edu>::max)());
161 constexpr auto minEdu = static_cast<Wide>((std::numeric_limits<Edu>::min)());
162
163 if (rounded > maxEdu) {
164 rounded = maxEdu;
165 } else if (rounded < minEdu) {
166 rounded = minEdu;
167 }
168
169 return Edu(static_cast<int>(rounded));
170 }
171
175 constexpr Fraction abs() const
176 {
177 if (numerator() < 0)
178 return Fraction(-numerator(), denominator());
179 return *this;
180 }
181
185 [[nodiscard]]
186 constexpr double toDouble() const
187 { return double(m_numerator) / double(m_denominator); }
188
194 Fraction constexpr operator+(const Fraction& other) const {
195 return Fraction(
196 m_numerator * other.m_denominator + other.m_numerator * m_denominator,
197 m_denominator * other.m_denominator
198 );
199 }
200
206 Fraction constexpr operator-(const Fraction& other) const {
207 return Fraction(
208 m_numerator * other.m_denominator - other.m_numerator * m_denominator,
209 m_denominator * other.m_denominator
210 );
211 }
212
218 Fraction constexpr operator*(const Fraction& other) const {
219 return Fraction(
220 m_numerator * other.m_numerator,
221 m_denominator * other.m_denominator
222 );
223 }
224
231 constexpr Fraction operator/(const Fraction& other) const {
232 return Fraction(
233 m_numerator * other.m_denominator,
234 m_denominator * other.m_numerator
235 );
236 }
237
243 constexpr Fraction& operator+=(const Fraction& other) {
244 *this = *this + other;
245 return *this;
246 }
247
253 constexpr Fraction& operator-=(const Fraction& other) {
254 *this = *this - other;
255 return *this;
256 }
257
263 constexpr Fraction& operator*=(const Fraction& other) {
264 *this = *this * other;
265 return *this;
266 }
267
273 constexpr Fraction& operator/=(const Fraction& other) {
274 *this = *this / other;
275 return *this;
276 }
277
283 [[nodiscard]]
284 constexpr bool operator==(const Fraction& other) const {
285 return m_numerator == other.m_numerator && m_denominator == other.m_denominator;
286 }
287
293 [[nodiscard]]
294 constexpr bool operator!=(const Fraction& other) const {
295 return !(*this == other);
296 }
297
303 [[nodiscard]]
304 constexpr bool operator<(const Fraction& other) const {
305 double lhs = static_cast<double>(m_numerator) / m_denominator;
306 double rhs = static_cast<double>(other.m_numerator) / other.m_denominator;
307 return lhs < rhs;
308 }
309
315 [[nodiscard]]
316 constexpr bool operator<=(const Fraction& other) const {
317 return *this < other || *this == other;
318 }
319
325 [[nodiscard]]
326 constexpr bool operator>(const Fraction& other) const {
327 return !(*this <= other);
328 }
329
335 [[nodiscard]]
336 constexpr bool operator>=(const Fraction& other) const {
337 return !(*this < other);
338 }
339
344 constexpr explicit operator bool() const noexcept {
345 return m_numerator != 0;
346 }
347
354 friend std::ostream& operator<<(std::ostream& os, const Fraction& frac) {
355 os << frac.m_numerator;
356 if (frac.m_denominator != 1) {
357 os << "/" << frac.m_denominator;
358 }
359 return os;
360 }
361
369 friend std::istream& operator>>(std::istream& is, Fraction& frac) {
370 int num, den;
371 char sep;
372 is >> num >> sep >> den;
373 if (sep != '/' || den == 0) {
374 throw std::invalid_argument("Invalid fraction format or zero m_denominator.");
375 }
376 frac = Fraction(num, den);
377 return is;
378 }
379};
380
382constexpr Fraction abs(const Fraction& v) noexcept
383{
384 return v.abs();
385}
386
387} // namespace util
388} // namespace musx
389
390#ifndef DOXYGEN_SHOULD_IGNORE_THIS
391#include <limits>
392
393namespace std {
394template <>
395class numeric_limits<musx::util::Fraction> {
396public:
397 static constexpr bool is_specialized = true;
398
399 // Smallest positive normalized value (not necessarily lowest)
400 static constexpr musx::util::Fraction min() noexcept {
401 return musx::util::Fraction(1, (std::numeric_limits<int>::max)());
402 }
403
404 // Largest representable positive fraction
405 static constexpr musx::util::Fraction max() noexcept {
406 return musx::util::Fraction((std::numeric_limits<int>::max)());
407 }
408
409 // Most negative representable fraction
410 static constexpr musx::util::Fraction lowest() noexcept {
411 return musx::util::Fraction(std::numeric_limits<int>::lowest());
412 }
413
414 static constexpr int digits = std::numeric_limits<int>::digits;
415 static constexpr int digits10 = std::numeric_limits<int>::digits10;
416
417 static constexpr bool is_signed = true;
418 static constexpr bool is_integer = false;
419 static constexpr bool is_exact = true;
420 static constexpr bool has_infinity = false;
421 static constexpr bool has_quiet_NaN = false;
422 static constexpr bool has_signaling_NaN = false;
423
424 static constexpr musx::util::Fraction epsilon() noexcept {
425 return musx::util::Fraction(1, (std::numeric_limits<int>::max)());
426 }
427
428 static constexpr musx::util::Fraction round_error() noexcept {
429 return musx::util::Fraction(0);
430 }
431
432 static constexpr int radix = 2;
433
434 static constexpr musx::util::Fraction infinity() noexcept { return musx::util::Fraction(0); }
435 static musx::util::Fraction quiet_NaN() noexcept { return musx::util::Fraction(0); }
436 static musx::util::Fraction signaling_NaN() noexcept { return musx::util::Fraction(0); }
437
438 static constexpr bool is_iec559 = false;
439 static constexpr bool is_bounded = true;
440 static constexpr bool is_modulo = false;
441 static constexpr bool traps = true; // Because invalid construction throws
442 static constexpr bool tinyness_before = false;
443 static constexpr float_round_style round_style = round_indeterminate;
444};
445
446template <>
447struct hash<musx::util::Fraction>
448{
449 size_t operator()(const musx::util::Fraction& frac) const noexcept
450 {
451 // boost algorithm tailored to Fraction
452 size_t seed = std::hash<int>{}(frac.numerator());
453 seed ^= std::hash<int>{}(frac.denominator()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
454 return seed;
455 }
456};
457} // namespace std
458
459#endif // DOXYGEN_SHOULD_IGNORE_THIS
A class to represent fractions with integer m_numerator and m_denominator, automatically reduced to s...
Definition Fraction.h:38
constexpr int quotient() const
Returns the integer (whole number) part of the fraction.
Definition Fraction.h:118
constexpr Fraction operator/(const Fraction &other) const
Divides one fraction by another.
Definition Fraction.h:231
constexpr double toDouble() const
Converts the fraction to floating point double.
Definition Fraction.h:186
constexpr int numerator() const
Gets the m_numerator of the fraction.
Definition Fraction.h:104
static constexpr Fraction fromPercent(int percent)
Constructs a Fraction from a percent (where 100 is 100%)
Definition Fraction.h:97
Fraction constexpr operator*(const Fraction &other) const
Multiplies two fractions.
Definition Fraction.h:218
constexpr Fraction & operator-=(const Fraction &other)
Compound subtraction assignment operator.
Definition Fraction.h:253
constexpr bool operator<(const Fraction &other) const
Less-than comparison operator.
Definition Fraction.h:304
constexpr int denominator() const
Gets the m_denominator of the fraction.
Definition Fraction.h:111
constexpr Fraction(int num, int den)
Constructs a Fraction object.
Definition Fraction.h:72
constexpr Fraction reciprocal() const
Returns the reciprocal fraction.
Definition Fraction.h:131
constexpr bool operator<=(const Fraction &other) const
Less-than-or-equal-to comparison operator.
Definition Fraction.h:316
static constexpr Fraction fromEdu(dom::Edu edu)
Constructs a Fraction from edu.
Definition Fraction.h:93
Fraction constexpr operator+(const Fraction &other) const
Adds two fractions.
Definition Fraction.h:194
constexpr dom::Edu calcEduDuration() const
Calculates duration as a fraction of a whole note. The result is rounded to the nearest integer Edu v...
Definition Fraction.h:142
constexpr Fraction abs() const
Calculates the absolute value of a Fraction.
Definition Fraction.h:175
friend std::ostream & operator<<(std::ostream &os, const Fraction &frac)
Stream output operator.
Definition Fraction.h:354
Fraction constexpr remainder() const
Returns the fractional part of the fraction.
Definition Fraction.h:126
friend std::istream & operator>>(std::istream &is, Fraction &frac)
Stream input operator.
Definition Fraction.h:369
constexpr Fraction & operator/=(const Fraction &other)
Compound division assignment operator.
Definition Fraction.h:273
constexpr Fraction(int value)
Constructs a Fraction object from an integer.
Definition Fraction.h:88
constexpr bool operator>(const Fraction &other) const
Greater-than comparison operator.
Definition Fraction.h:326
constexpr Fraction & operator*=(const Fraction &other)
Compound multiplication assignment operator.
Definition Fraction.h:263
Fraction constexpr operator-(const Fraction &other) const
Subtracts one fraction from another.
Definition Fraction.h:206
constexpr Fraction & operator+=(const Fraction &other)
Compound addition assignment operator.
Definition Fraction.h:243
constexpr bool operator!=(const Fraction &other) const
Inequality comparison operator.
Definition Fraction.h:294
constexpr bool operator==(const Fraction &other) const
Equality comparison operator. (This depends on the fact that instances of Fraction are always reduced...
Definition Fraction.h:284
constexpr bool operator>=(const Fraction &other) const
Greater-than-or-equal-to comparison operator.
Definition Fraction.h:336
int32_t Edu
"Enigma Durational Units" value (1024 per quarter note)
Definition Fundamentals.h:61
object model for musx file (enigmaxml)
Definition BaseClasses.h:36