-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AMM Add Number class and associated algorithms #4145
Conversation
* Conversions to Number are implicit * Conversions away from Number are explicit and potentially lossy * If lossy, round to nearest, and to even on tie
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. I reviewed/tested only from integration into AMM point of view.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a good enough numerics person to have an opinion on the algorithms here. So I have to rely on tests returning results that I'd expect. There are some places where the test coverage is not very good, so I'd very much like to see some additional coverage. That will give me better confidence with the algorithms. Finding the cube root of -27 might be interesting, for example. And maybe exercising some of the places that you expect errors to be thrown.
Beyond that I had a few constexpr
, naming, and structural thoughts for you to consider. But they are at your discretion. Let me know what you think. Thanks.
src/ripple/basics/Number.h
Outdated
{ | ||
using rep = std::int64_t; | ||
rep mantissa_{0}; | ||
int exponent_{-2'147'483'648}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be inclined to use std::int32_t
rather than int
. That will increase behavioral similarities across platforms.
I'd also be inclined to use std::numeric_limits<std::int32_t>::lowest()
in place of -2'147'483'648
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I attempted to minimize differences between Number
and IOUAmount
. The latter uses int
here. However I've adopted your suggestion about numeric_limits
.
src/ripple/basics/Number.h
Outdated
explicit unchecked() = default; | ||
}; | ||
|
||
explicit Number() = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the compiler treats this constructor as constexpr
since both members are initialized, so it might be nice to mark it constexpr
explicitly.
Number(rep mantissa); | ||
explicit Number(rep mantissa, int exponent); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these two constructors could be declared constexpr
if we made normalize()
constexpr
and moved the normalize()
implementation into the header file. I don't see anything in the normalize()
implementation that would prevent if from being used during constant evaluation. But maybe I'm missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not move normalize()
into the header because of its size. The compiler probably won't inline it, which is good, but then the linker has to spend time eliminating duplicate outlined copies, which is bad. My policy is to not risk the code bloat or the extra linker work unless there is a benefit that outweighs these costs.
However sometimes I do want to have constexpr
values. Thus I introduced:
explicit constexpr Number(rep mantissa, int exponent, unchecked) noexcept;
and
constexpr bool isnormal() const noexcept;
Here is an example use: 9968d47#diff-6c9395b2018418d46bb1c2584aa296d79ab620de7615538c6734648ca6eb71aeR357-R362
src/test/basics/Number_test.cpp
Outdated
test_to_integer() | ||
{ | ||
testcase("test_to_integer"); | ||
Number x[]{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this test case in particular, having the inputs and output split into separate arrays makes the test hard to read and maintain. What would you think of making a struct
that has both the input and the expected result and making an array of that struct
? Maybe something like this:
struct OneTest
{
Number num;
std::int64_t i;
};
// clang-format off
static constexpr OneTest tests[]{
// Number int
{Number{ 0 }, 0},
// ...
{Number{-9'999'999'999'999'999, 2}, -999'999'999'999'999'900},
// ...
};
// clang-format on
for (auto const& test : tests)
{
auto j = static_cast<std::int64_t>(test.num);
BEAST_EXPECT(j == test.i);
}
You could consider a similar approach to the earlier tests as well, but I think it's a real win here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I've adopted something similar to what you suggest.
operator<(Number const& x, Number const& y) noexcept | ||
{ | ||
// If the two amounts have different signs (zero is treated as positive) | ||
// then the comparison is true iff the left is negative. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm lovin' the comments in this section! Thanks!
return one; | ||
if (n == 1) | ||
return f; | ||
auto r = power(f, n / 2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This power()
function is untested from here down. This seems like the interesting stuff? Perhaps some tests could be added to exercise this part?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
friend constexpr bool | ||
operator<=(Number const& x, Number const& y) noexcept | ||
{ | ||
return !(y < x); | ||
} | ||
|
||
friend constexpr bool | ||
operator>=(Number const& x, Number const& y) noexcept | ||
{ | ||
return !(x < y); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These operators are untested. Would probably be good to at least add a couple touch tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests.
inline Number& | ||
Number::operator++() | ||
{ | ||
*this += Number{1000000000000000, -15, unchecked{}}; | ||
return *this; | ||
} | ||
|
||
inline Number | ||
Number::operator++(int) | ||
{ | ||
auto x = *this; | ||
++(*this); | ||
return x; | ||
} | ||
|
||
inline Number& | ||
Number::operator--() | ||
{ | ||
*this -= Number{1000000000000000, -15, unchecked{}}; | ||
return *this; | ||
} | ||
|
||
inline Number | ||
Number::operator--(int) | ||
{ | ||
auto x = *this; | ||
--(*this); | ||
return x; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These operators are untested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests.
src/ripple/basics/impl/Number.cpp
Outdated
{ | ||
if (exponent_ >= maxExponent) | ||
throw std::overflow_error("Number::normalize 1"); | ||
m /= 10; | ||
++exponent_; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The body of this while
loop is untested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests.
{ | ||
*this = Number{}; | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The body of this if
is untested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests.
|
||
// The Guard class is used to tempoarily add extra digits of | ||
// preicision to an operation. This enables the final result | ||
// to be correctly rounded to the internal precision of Number. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this comment. It really helps me understand what Guard
is doing.
{Number{-1}, 0, Number{1}}, | ||
{Number{5, -1}, 0, Number{0}}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these calls are asking for the zeroth root of -1
and 0.5
respectively? If that reading is correct, a couple of non-authoritative) web sites I checked suggest that the zeroth root of any number (possibly excepting 1) is undefined:
- https://math.stackexchange.com/questions/962807/does-the-zeroth-root-exist
- https://www.quora.com/What-is-the-zeroth-root-of-any-number
So I'm wondering if the better answer for taking the zeroth root of a number is to throw? I'm open to being wrong on this call...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with Annex F of the C standard: https://cigix.me/c17#F.10.4.4.p1
pow(−1, ±∞) returns 1.
pow(x, +∞) returns +0 for |x| < 1.
Annex F is consistent with the IEEE floating point standards: https://cigix.me/c17#F.1.p1
I did take the liberty of interpreting 1/0 as +∞.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm great with that call. How would you feel about adding comments to the root(Number, unsigned)
and power(Number, unsigned, unsigned)
declarations in Number.h that the implementations are consistent with Annex F of the C standard and with IEEE floating point standards?
{Number{-1}, 1, 0, Number{1}}, | ||
{Number{-1, -1}, 1, 0, Number{0}}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given a little bit of research, I'm thinking that having a 0
in the d
parameter of root
should be an undefined result and so should throw
. Am I misunderstanding?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that there is room for debate here. But I thought the safest thing to do was follow the established C and IEEE standards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First of all, the unit test coverage is outstanding! Thanks for putting in the effort there.
There are a couple of small changes I'd like to see:
- Rename
clip()
to something likenoiseGate()
. - I'd like to see comments added to
root(Number, unsigned)
andpower(Number, unsigned, unsigned)
that they comply with Annex F of the C standard. - See whether it makes sense to pass
Number const&
to the twopower
functions.
Assuming those are addressed this looks like it's good to go as far as I can see. Nice work!
src/ripple/basics/Number.h
Outdated
// Returns f^(n/d) | ||
|
||
Number | ||
power(Number f, unsigned n, unsigned d); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the functions in this header pass Number const&
. That could be done here too.
// to find the root of the polynomial g(x) = x^d - f | ||
|
||
Number | ||
root(Number f, unsigned d); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation of root
modifies the passed in f
, so there's a good reason to be passing a non-ref Number
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we add these functions, I'd like to make sure they are needed. We need to be able to take the square root of a number, and we need to be able to square a number (multiply is likely good enough). I'd like to not add root
and ``pow` until we know we need them for sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AMM equations require at a minimum root
in case of equal weights. pow
is required if unequal weights is supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gregtatcam Don't we only need square root, not a general root function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, just the square root
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the unequal weights case (if we support that), we need the general root function to support the power raise to a general rational function.
src/ripple/basics/Number.h
Outdated
// Uses a log_2(n) number of multiplications | ||
|
||
Number | ||
power(Number f, unsigned n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the functions in this header pass Number const&
. That could be done here too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment on root
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
雷达啥时候回来
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你们是不是雷达实验室的人
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little hesitant to add another numeric class to rippled. Do we really need to add a new one?
Number | ||
power(Number f, unsigned n, unsigned d); | ||
|
||
// Return 0 if abs(x) < limit, else returns x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to think of a better name. I agree that this shouldn't be named clip
, but noiseGate
doesn't really give an intuition for what this function does. I'll try to think of something too. Maybe squelch
?
// to find the root of the polynomial g(x) = x^d - f | ||
|
||
Number | ||
root(Number f, unsigned d); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we add these functions, I'd like to make sure they are needed. We need to be able to take the square root of a number, and we need to be able to square a number (multiply is likely good enough). I'd like to not add root
and ``pow` until we know we need them for sure.
src/ripple/basics/Number.h
Outdated
// Uses a log_2(n) number of multiplications | ||
|
||
Number | ||
power(Number f, unsigned n); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment on root
.
I've addressed all of @scottschurr's comments. On |
AMM equations are not like other math ops in the rippled and are neither |
// This function, and power(Number f, unsigned n, unsigned d) | ||
// treat corner cases such as 0 roots as advised by Annex F of | ||
// the C standard, which itself is consistent with the IEEE | ||
// floating point standards. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
radar的贡献者,雷达还有希望吗 |
你是谁?
| |
***@***.***
|
|
***@***.***
|
…---- 回复的原邮件 ----
| 发件人 | ***@***.***> |
| 日期 | 2022年05月23日 17:09 |
| 收件人 | ***@***.***> |
| 抄送至 | ***@***.******@***.***> |
| 主题 | Re: [ripple/rippled] AMM Add Number class and associated algorithms (PR #4145) |
radar的贡献者,雷达还有希望吗
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
|
我也是玩家
| |
***@***.***
|
|
***@***.***
|
…---- 回复的原邮件 ----
| 发件人 | ***@***.***> |
| 日期 | 2022年05月23日 17:09 |
| 收件人 | ***@***.***> |
| 抄送至 | ***@***.******@***.***> |
| 主题 | Re: [ripple/rippled] AMM Add Number class and associated algorithms (PR #4145) |
radar的贡献者,雷达还有希望吗
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
|
我也是玩家,你是玩雷达的吗, |
@HowardHinnant, should this pull request be closed? It looks like you have a competing pull request: #4192. Your call. |
Closed in lieu of #4192. |
High Level Overview of Change
Initial check-in for general purpose decimal floating point Number type with power raised to rational exponent algorithm.
Context of Change
Type of Change