Skip to content

Commit 3d6e068

Browse files
committed
Create Magnitude class for magnitudes with greater precision
1 parent d2d525b commit 3d6e068

File tree

4 files changed

+465
-0
lines changed

4 files changed

+465
-0
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ GRIDCOIN_CORE_H = \
100100
neuralnet/accrual/research_age.h \
101101
neuralnet/claim.h \
102102
neuralnet/cpid.h \
103+
neuralnet/magnitude.h \
103104
neuralnet/project.h \
104105
neuralnet/quorum.h \
105106
neuralnet/researcher.h \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ GRIDCOIN_TESTS =\
4343
test/netbase_tests.cpp \
4444
test/neuralnet/claim_tests.cpp \
4545
test/neuralnet/cpid_tests.cpp \
46+
test/neuralnet/magnitude_tests.cpp \
4647
test/neuralnet/project_tests.cpp \
4748
test/neuralnet/researcher_tests.cpp \
4849
test/neuralnet/superblock_tests.cpp \

src/neuralnet/magnitude.h

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
#pragma once
2+
3+
#include <tinyformat.h>
4+
5+
#include <cmath>
6+
#include <stdint.h>
7+
#include <string>
8+
9+
namespace NN {
10+
//!
11+
//! \brief Represents a CPID's computing power at a point in time relative to
12+
//! the other CPIDs in the network.
13+
//!
14+
//! Gridcoin uses units of magnitude to quantify a BOINC participant's computing
15+
//! power. Magnitude values are one of the primary variables in the formula that
16+
//! calculates research rewards. The protocol derives these values using BOINC's
17+
//! recent average credit (RAC) metric by calculating the ratios of a CPID's RAC
18+
//! at each BOINC project to the sum of the RAC of all the participants at these
19+
//! projects.
20+
//!
21+
//! With block version 11 and later, the network tracks magnitudes with decaying
22+
//! precision as the magnitude values increase. These manifest in three tiers:
23+
//!
24+
//! - Less than 1: two fractional places
25+
//! - Between 1 and 10: one fractional place
26+
//! - 10 and greater: whole numbers only
27+
//!
28+
//! This improves representation of participants with less computing power while
29+
//! retaining the superblock compactness of the earlier, integer-only format. To
30+
//! present these values to the rest of the application uniformly, the Magnitude
31+
//! type stores normalized magnitudes as integers scaled by a factor of 100.
32+
//!
33+
class Magnitude
34+
{
35+
public:
36+
//!
37+
//! \brief The largest magnitude value that a CPID may obtain.
38+
//!
39+
static constexpr uint16_t MAX = 32767;
40+
41+
//!
42+
//! \brief The smallest magnitude value that the network can represent as
43+
//! a non-zero magnitude is just greater than this value.
44+
//!
45+
//! Note: magnitudes less than 0.01 are rounded-up to 0.01.
46+
//!
47+
static constexpr double ZERO_THRESHOLD = 0.005;
48+
49+
//!
50+
//! \brief The largest possible magnitude stored with two fractional places
51+
//! is just less than this value.
52+
//!
53+
static constexpr double MIN_MEDIUM = 0.995;
54+
55+
//!
56+
//! \brief The largest possible magnitude stored with one fractional place
57+
//! is just less than this value.
58+
//!
59+
static constexpr double MIN_LARGE = 9.95;
60+
61+
//!
62+
//! \brief Factor by which a canonical magnitude is scaled for storage in
63+
//! this type.
64+
//!
65+
static constexpr size_t SCALE_FACTOR = 100;
66+
67+
//!
68+
//! \brief Factor by which a magnitude with a value less than 10 is scaled
69+
//! from compact storage.
70+
//!
71+
static constexpr size_t MEDIUM_SCALE_FACTOR = 10;
72+
73+
//!
74+
//! \brief Factor by which a magnitude with a value less than 1 is scaled
75+
//! from compact storage.
76+
//!
77+
static constexpr size_t SMALL_SCALE_FACTOR = 1;
78+
79+
//!
80+
//! \brief Describes the size of the magnitude value for the purpose of
81+
//! assigning the number of fractional places.
82+
//!
83+
enum class Kind
84+
{
85+
ZERO, //!< A zero magnitude.
86+
SMALL, //!< Less than 1 with two fractional places.
87+
MEDIUM, //!< 1 or greater and less than 10 with one fractional place.
88+
LARGE, //!< 10 or greater with no fractional places.
89+
};
90+
91+
//!
92+
//! \brief Initialize a magnitude with a value of zero.
93+
//!
94+
//! \return A magnitude with a value of zero.
95+
//!
96+
static Magnitude Zero()
97+
{
98+
return Magnitude(0);
99+
}
100+
101+
//!
102+
//! \brief Initialize a magnitude directly from its scaled representation.
103+
//!
104+
//! \param scaled A magnitude value scaled by a factor of 100.
105+
//!
106+
//! \return The wrapped magnitude value.
107+
//!
108+
static Magnitude FromScaled(const uint32_t scaled)
109+
{
110+
return Magnitude(scaled);
111+
}
112+
113+
//!
114+
//! \brief Initialize a magnitude from a floating-point number.
115+
//!
116+
//! \param value An unscaled magnitude.
117+
//!
118+
//! \return The wrapped magnitude value appropriately rounded and scaled.
119+
//!
120+
static Magnitude RoundFrom(const double value)
121+
{
122+
if (value <= ZERO_THRESHOLD || value > MAX) {
123+
return Zero();
124+
}
125+
126+
if (value < MIN_MEDIUM) {
127+
return Magnitude(RoundAndScale(value, SMALL_SCALE_FACTOR));
128+
}
129+
130+
if (value < MIN_LARGE) {
131+
return Magnitude(RoundAndScale(value, MEDIUM_SCALE_FACTOR));
132+
}
133+
134+
return Magnitude(RoundAndScale(value, SCALE_FACTOR));
135+
}
136+
137+
bool operator==(const Magnitude other) const
138+
{
139+
return m_scaled == other.m_scaled;
140+
}
141+
142+
bool operator!=(const Magnitude other) const
143+
{
144+
return m_scaled != other.m_scaled;
145+
}
146+
147+
bool operator==(const int64_t other) const
148+
{
149+
return static_cast<int64_t>(m_scaled) == other * SCALE_FACTOR;
150+
}
151+
152+
bool operator!=(const int64_t other) const
153+
{
154+
return !(*this == other);
155+
}
156+
157+
bool operator==(const int32_t other) const
158+
{
159+
return *this == static_cast<int64_t>(other);
160+
}
161+
162+
bool operator!=(const int32_t other) const
163+
{
164+
return !(*this == other);
165+
}
166+
167+
bool operator==(const double other) const
168+
{
169+
return *this == RoundFrom(other);
170+
}
171+
172+
bool operator!=(const double other) const
173+
{
174+
return !(*this == other);
175+
}
176+
177+
//!
178+
//! \brief Describe the size of the magnitude value for the purpose of
179+
//! categorizing it for serialization.
180+
//!
181+
//! \return A value that represents the size of the magnitude.
182+
//!
183+
Kind Which() const
184+
{
185+
if (m_scaled == 0) {
186+
return Kind::ZERO;
187+
}
188+
189+
if (m_scaled < (MIN_MEDIUM * SCALE_FACTOR)) {
190+
return Kind::SMALL;
191+
}
192+
193+
if (m_scaled < (MIN_LARGE * SCALE_FACTOR)) {
194+
return Kind::MEDIUM;
195+
}
196+
197+
return Kind::LARGE;
198+
}
199+
200+
//!
201+
//! \brief Get the scaled representation of the magnitude.
202+
//!
203+
//! Use the scaled value instead of the floating-point representation when
204+
//! appropriate to avoid floating-point errors.
205+
//!
206+
//! \return Magnitude scaled by a factor of 100.
207+
//!
208+
uint32_t Scaled() const
209+
{
210+
return m_scaled;
211+
}
212+
213+
//!
214+
//! \brief Get the compact representation of the magnitude.
215+
//!
216+
//! This produces an integer value that matches the format of the magnitude
217+
//! as a superblock stores it.
218+
//!
219+
//! \return Magnitude scaled-down as needed for its size category.
220+
//!
221+
uint16_t Compact() const
222+
{
223+
switch (Which()) {
224+
case Kind::ZERO: return 0;
225+
case Kind::SMALL: return m_scaled / SMALL_SCALE_FACTOR;
226+
case Kind::MEDIUM: return m_scaled / MEDIUM_SCALE_FACTOR;
227+
case Kind::LARGE: return m_scaled / SCALE_FACTOR;
228+
}
229+
230+
return 0;
231+
}
232+
233+
//!
234+
//! \brief Get the floating-point representation of the magnitude.
235+
//!
236+
//! Use the scaled value instead of the floating-point representation when
237+
//! appropriate to avoid floating-point errors.
238+
//!
239+
//! \return Floating-point magnitude at canonical scale.
240+
//!
241+
double Floating() const
242+
{
243+
return static_cast<double>(m_scaled) / SCALE_FACTOR;
244+
}
245+
246+
//!
247+
//! \brief Get the string representation of the magnitude.
248+
//!
249+
//! \return The magnitude's floating-point representation as a string.
250+
//!
251+
std::string ToString() const
252+
{
253+
return strprintf("%g", Floating());
254+
}
255+
256+
private:
257+
uint32_t m_scaled; //!< Magnitude scaled by a factor of 100.
258+
259+
//!
260+
//! \brief Initialize a magnitude with a value of zero.
261+
//!
262+
Magnitude() : Magnitude(0)
263+
{
264+
}
265+
266+
//!
267+
//! \brief Initialize a magnitude directly from its scaled representation.
268+
//!
269+
//! \param scaled A magnitude value scaled by a factor of 100.
270+
//!
271+
explicit Magnitude(const uint32_t scaled) : m_scaled(scaled)
272+
{
273+
}
274+
275+
//!
276+
//! \brief Scale a floating-point magnitude by the specified factor and
277+
//! round the result.
278+
//!
279+
//! \param magnitude An unscaled magnitude.
280+
//! \param scale Scale factor of the magnitude's size category.
281+
//!
282+
//! \return The scaled integer representation of the rounded magnitude.
283+
//!
284+
static uint32_t RoundAndScale(const double magnitude, const double scale)
285+
{
286+
return std::nearbyint(magnitude * (SCALE_FACTOR / scale)) * scale;
287+
}
288+
}; // Magnitude
289+
}

0 commit comments

Comments
 (0)