-
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
fixUniversalNumber
: Number merge
#4192
Conversation
这个是关于ripple还是radar? |
@zhangdongwei123, this repository is for the XRP Ledger and has nothing to do with radar. |
雷达还开吗? |
@zhangdongwei123, you are asking at the wrong site. This site has no connection with radar. We don't know whether radar is working or not. Please find somewhere else to ask. |
@HowardHinnant I'm seeing two unit tests fail (the first is this one: |
Looking into it now, thanks. |
This new commit fixes the failures you observed @seelabs. For reasons that are not clear to me, the Taker test ran with the amendment on in non-unity build, and with the amendment off in unity build. The patch forces the amendment on in the Taker test for both non-unity and unity builds. |
db718ac
to
85b810e
Compare
Rebased to 1.9.2-rc1. |
fafdf92
to
574b497
Compare
Rebased to 1.9.2. |
return 1; | ||
return -1; | ||
} | ||
} |
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.
Compiler warning: Number.cpp:159:1: warning: control reaches end of non-void function [-Wreturn-type]
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.
Fixed in f099878
Number y{1'000'000'000'000'001'500, 32000}; | ||
BEAST_EXPECT((y == Number{1'000'000'000'000'002, 32003})); | ||
Number m{std::numeric_limits<std::int64_t>::min()}; | ||
BEAST_EXPECT((m == Number{-9'223'372'036'854'776, 3})); |
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 test fails on Ubuntu 20.04.1 (ARM64). m
is 0. Should this architecture be 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.
I had it also failed on Ubuntu 20.04.4 Intel architecture.
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 report. Is m == 0
on the Intel architecture as well?
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.
Never mind. I'm reproducing it now. This is a bug somewhere in Number
as it only reproduces with optimizations on.
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.
Fixed in 7e10665.
Taking the negative of the most negative signed value is undefined behavior, not identity. However one can cast negative signed to unsigned and then negate, getting the desired result without UB.
The existing unit tests caught this, and should now pass.
574b497
to
7e10665
Compare
src/ripple/basics/impl/Number.cpp
Outdated
#include <type_traits> | ||
#include <utility> | ||
|
||
#ifdef _MSVC_LANG |
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 would prefer to use BOOST_COMP_MSVC
if possible; standardizing on that makes it easier to find all instances of this kind of thing.
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.
Ok, done.
src/ripple/basics/impl/Number.cpp
Outdated
Number::Guard::round() noexcept | ||
{ | ||
auto mode = Number::getround(); | ||
switch (mode) |
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.
No change needed, but I think this would be more readable as standalone if
statements:
auto mode = Number::getround();
if (mode == towards_zero)
return -1;
if (mode == downward)
{
if (sbit_)
{
if (digits_ > 0 || xbit_)
return 1;
}
return -1;
}
if (mode == upward)
{
if (sbit_)
return -1;
if (digits_ > 0 || xbit_)
return 1;
return -1;
}
// assume round to nearest if mode is not one of the predefined values
if (digits_ > 0x5000'0000'0000'0000)
return 1;
if (digits_ < 0x5000'0000'0000'0000)
return -1;
if (xbit_)
return 1;
return 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.
Ok, done.
assert(isnormal() && y.isnormal()); | ||
auto xm = mantissa(); | ||
auto xe = exponent(); | ||
int xn = 1; |
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.
Unless you plan on using 0
(i.e. having a tribool
like thing) then I'd recommend turning this to a bool
(same for yn
) for better self-documenting code.
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 reason I like the -1/1 int is because of this line:
mantissa_ = xm * xn;
Yes, I could rewrite it with:
if (xn)
mantissa_ = -xm;
else
mantissa_ = xm;
but I really like the conciseness, and potential branch elimination of this formulation.
src/ripple/basics/impl/Number.cpp
Outdated
do | ||
{ | ||
rm2 = rm1; | ||
rm1 = r; | ||
r = r + r * (one - d * r); | ||
} while (r != rm1 && r != rm2); |
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.
Are we confident that this will always terminate for every possible input? Should we have a counter to place some reasonable upper limit on the number of iterations?
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.
Yes, both by theory and testing.
Theory: The Newton algorithm will reliably point towards the right answer. If that right answer is between two representable values, and the starting guess is one of those representable values, then the algorithm either gives back the same answer, or the other representable value closest to the exact answer. It never "wanders" away.
Practice: The set up to this look reduces the domain to [1, 10). Very significant testing was done throughout this small finite range, especially near the endpoints, and at the point where the difference between the initial guess and the exact answer is at a maximum.
I also tried going with a fixed number of iterations. However testing revealed that the required number of iterations varied enough that I felt it was too expensive to hard code the maximum number of iterations when many inputs in the range [1, 10) required fewer than the maximum.
In the end, among these four approaches:
- Hard code the number of iterations: Rejected because there was no one number of iterations that was acceptable.
- Stop when r == rm1. Rejected because the loop sometimes did not terminate because the algorithm would bounce between two values.
- Stop when r == rm1 || r == rm2. Accepted as the highest performance alternative while maintaining correctness.
- Stop when r == rm1 || r == rm2 || r == rm3. Although correct, a history of three values was never observed to be needed. Rejected.
src/test/app/Offer_test.cpp
Outdated
@@ -2117,7 +2117,7 @@ class Offer_test : public beast::unit_test::suite | |||
jrr = ledgerEntryState(env, bob, gw, "USD"); | |||
BEAST_EXPECT( | |||
jrr[jss::node][sfBalance.fieldName][jss::value] == | |||
"-0.966500000033334"); | |||
"-0.9665000000333333"); |
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 concerns me: should this change even if the new Number
logic amendment is not activated? Can we check both with and without please?
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 will attempt to do this.
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.
Done.
@@ -43,6 +45,17 @@ IOUAmount::minPositiveAmount() | |||
void | |||
IOUAmount::normalize() | |||
{ | |||
if (*stNumberSwitchover) |
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 that the check for if (mantissa_ == 0)
can safely be above this. Not sure if it's worth doing, but there's nothing that the Number
back-and-forth will achieve in that case.
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.
Agreed.
src/ripple/basics/impl/IOUAmount.cpp
Outdated
IOUAmount& | ||
IOUAmount::operator+=(IOUAmount const& other) | ||
{ | ||
if (other == beast::zero) |
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.
Same comment as before: if there's nothing to be done, we should void round-tripping via 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.
Agreed.
inline constexpr Number | ||
Number::operator+() const noexcept | ||
{ | ||
return *this; |
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.
TIL... or at least was reminded: the unary +
operator, unlike unary -
, leaves its argument completely unchanged.
if (x.mantissa_ == 0) | ||
return y.mantissa_ > 0; | ||
|
||
// Both have same sign, the right is zero and the left is non-zero. |
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.
Should comment be // Both are positive ...
instead of // Both have same sign ...
?
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.
Nope. At this point they both could be negative, or they both could be non-negative. I.e. lneg == rneg
.
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.
Well the same comment says "the right is zero" which means the sign they both have is positive.
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 married to this code. I lifted it straight from IOUAmount.cpp:
rippled/src/ripple/basics/impl/IOUAmount.cpp
Lines 127 to 155 in e32bc67
bool | |
IOUAmount::operator<(IOUAmount const& other) const | |
{ | |
// If the two amounts have different signs (zero is treated as positive) | |
// then the comparison is true iff the left is negative. | |
bool const lneg = mantissa_ < 0; | |
bool const rneg = other.mantissa_ < 0; | |
if (lneg != rneg) | |
return lneg; | |
// Both have same sign and the left is zero: the right must be | |
// greater than 0. | |
if (mantissa_ == 0) | |
return other.mantissa_ > 0; | |
// Both have same sign, the right is zero and the left is non-zero. | |
if (other.mantissa_ == 0) | |
return false; | |
// Both have the same sign, compare by exponents: | |
if (exponent_ > other.exponent_) | |
return lneg; | |
if (exponent_ < other.exponent_) | |
return !lneg; | |
// If equal exponents, compare mantissas | |
return mantissa_ < other.mantissa_; | |
} |
I suppose a more precise comment might be:
Both x and y have the same sign. If the left is 0, then y must also be non-negative. So y is either equal to x (equal to 0), or y is positive and thus greater than x (which is 0).
If the comment is really causing problems, I'd be more in favor of just removing it. For my money the code speaks clearly enough for itself.
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.
Totally up to you, Howard. I kinda like your latest comment but it is a mouthful.
be8e0c4
to
9e4796b
Compare
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.
Honestly, you write outstanding code. Always fun to review it.
I left a couple comments.
setround(rounding_mode mode); | ||
|
||
private: | ||
static thread_local rounding_mode mode_; |
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 worried about co-routines what might resume on different threads. If that happens they can be resumed with a different rounding mode than what they were suspended with. If this can never happen, we should at least have a comment explaining why. If it can, we probably need to handle that case.
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 open to suggestions here.
It looks like co-routines resuming on different threads is an explicit design for the co-routine author, and not something that happens by accident. So we could just document "don't do that" for the Number
class.
Changing rounding_mode
to be static
doesn't seem like a good idea because that means all computations using Number
(on different threads) will be forced to share the same rounding mode.
Changing rounding_mode
to be a non-static data member isn't practical as there will be many Number
instances participating in a single expression.
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.
Setting the rounding mode is used exactly once in non-test code: in AMM_formula.h. How would the code look if we made the rounding mode a template parameter and made it part of the type instead of a thread local? It sounds like all but one place uses the same rounding mode. If this is true, that might work out well.
Edit: I think the only code that changes rounding mode is here:
rippled/src/ripple/app/misc/AMM_formulae.h
Lines 47 to 50 in 2b33ec3
Number::setround(mode); | |
auto const res = STAmount{issue, (std::int64_t)n}; | |
Number::setround(Number::rounding_mode::to_nearest); | |
return res; |
And I'm assuming the ending Number::setround(Number::rounding_mode::to_nearest)
is unintentional and it's supposed to set to the previous rounding mode. If it's intentional, then my suggestion doesn't work well. Although if it's intentional then I don't love that code in AMM_formulae` and will probably see what we can do to change it.
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.
Handy utility for setting rounding mode, and resetting it back to previous:
Used like this:
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.
If to_nearest
is the only mode ever set, we don't need this facility because to_nearest
is the default, and what was in place prior to the introduction of Number::setround
.
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.
Other rounding modes are used in AMM when swapping in/out of the pool.
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.
Good to know, thanks.
bool const negative = (mantissa_ < 0); | ||
auto m = static_cast<std::make_unsigned_t<rep>>(mantissa_); | ||
if (negative) | ||
m = -m; |
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.
In several algorithms, we negate a number. Negating the largest negative value doesn't actually negate it. Can you add a comment near each negation explaining why we never see the largest negative value or what the consequences of that value would be for the algorithm?
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 just recently became aware that negating the most negative signed value is undefined behavior, and the gcc optimizer takes advantage of that fact, producing code that changes behavior compared to non-optimized code.
Because of this I inserted line 179:
auto m = static_cast<std::make_unsigned_t<rep>>(mantissa_);
I'm negating an unsigned integral, not a signed integral. And this is well-defined for all values.
This language design makes no sense to me at all, resulting in code that is much more difficult to read and reason about. However I am now using this poorly designed part of the language correctly.
*this = Number{}; | ||
return *this; | ||
} | ||
assert(isnormal() && y.isnormal()); |
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.
How hard would it be to remove creating unnormalized numbers? If we can't, maybe we should change this assert so it works on normalized numbers if we ever hit this case. (here and other operators that assume normalized inputs)
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 definition of isnormal()
the only de-normalized number that is allowed is 0, which should be taken care of by the checks above this assert. If this assert fires, there is a serious bug in Number
and should be investigated immediately.
One way for de-normalized numbers to arise is via the "unchecked" constructor. Clients that use this constructor should take care to only construct normalized numbers. This constructor is an optimization for creating constants, while avoiding the normalization process which the coder can do manually, for example:
constexpr Number a2{1405502114116773, -17, unchecked{}};
static_assert(a2.isnormal());
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.
@HowardHinnant , maybe you you use if (std::is_constant_evaluated())
within the "unchecked" constructor to perform the static_assert(isnormal());
?
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.
or maybe just static_assert(!std::is_constant_evaluated() || isnormal());
?
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.
That's a possibility, but doing so would not eliminate the motivation for these asserts.
717ff40
to
ae44160
Compare
ade5cf8
to
16f1057
Compare
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.
👍
Just needs conflicts to be resolved @HowardHinnant . There is no need to rebase (although that is fine if that's what you prefer) - merge commits are OK since they will be squashed anyway. |
* Conversions to Number are implicit * Conversions away from Number are explicit and potentially lossy * If lossy, round to nearest, and to even on tie
* Guarded by amendment fixUniversalNumber * Produces slightly better accuracy in some computations.
You can set a thread-local flag to direct Number how to round non-exact results with the syntax: Number::rounding_mode prev_mode = Number::setround(Number::towards_zero); This flag will stay in effect for this thread only until another call to setround. The previously set rounding mode is returned. You can also retrieve the current rounding mode with: Number::rounding_mode current_mode = Number::getround(); The available rounding modes are: * to_nearest : Rounds to nearest representable value. On tie, rounds to even. * towards_zero : Rounds towards zero. * downward : Rounds towards negative infinity. * upward : Rounds towards positive infinity. The default rounding mode is to_nearest.
* Taking the negative of a signed negative is UB, but taking the negative of an unsigned is not.
* Replace division with faster algorithm. * Correct some rounding bugs in multiplication. * Add tests for rounding bugs.
* Optimization includes computing remainder from division. * Used only within Number::operator*=.
4dc0206
to
2f11eb3
Compare
Squashed and rebased to Develop (1.10.0-rc1). Builds and passes --unittest. |
Three static member functions are introduced with definitions consistent with std::numeric_limits: static constexpr Number min() noexcept; Returns: The minimum positive value. This is the value closest to zero. static constexpr Number max() noexcept; Returns: The maximum possible value. static constexpr Number lowest() noexcept; Returns: The negative value which is less than all other values.
2f11eb3
to
ff1495b
Compare
|
||
friend constexpr bool | ||
operator==(Number const& x, Number const& y) noexcept | ||
{ |
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.
Do we expect the user to normalize the arguments before calling this operator? Would the following code work?
Number a{1,0};
Number b{1,-1}, c{9,-1};
assert(a == b+c);
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.
Yes, Number
s are always normalized as a class invariant. There's a way to break that invariant by using unchecked
in the constructor, but one shouldn't. unchecked
is used as a tool to normalize manually at coding time in order to form a constexpr Number
.
The assert passes in this example.
return false; | ||
|
||
// Both have the same sign, compare by exponents: | ||
if (x.exponent_ > y.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.
Do we expect that x
and y
have been normalized?
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.
Yes.
|
||
inline Number | ||
operator+(Number const& x, Number const& 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.
Should we check if x
and y
have the same 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.
They do not have to have the same exponent. Different exponents are handled within the +=
operator here: https://github.com/XRPLF/rippled/blob/develop/src/ripple/basics/impl/Number.cpp#L256-L277
throw std::overflow_error("Number::power nan"); | ||
if (d == 0) | ||
{ | ||
if (f == -one) |
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.
if (f == -one) | |
if (f == abs(one)) |
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 case f == one
is already handled on line 731. And f == -one
must be handled separately because power(1, 0, 0) == 1
but power(-1, 0, 0)
is nan and should throw an exception. Only if either n
or d
is not 0 (the exponent is not nan) should power(-1, n, d) == 1
.
These corner cases were designed to be consistent with Annex F of the C standard, which is itself designed to be consistent with ISO/IEC 60559.
Add AMM functionality: - InstanceCreate - Deposit - Withdraw - Governance - Auctioning - payment engine integration To support this functionality, add: - New RPC method, `amm_info`, to fetch pool and LPT balances - AMM Root Account - trust line for each IOU AMM token - trust line to track Liquidity Provider Tokens (LPT) - `ltAMM` object The `ltAMM` object tracks: - fee votes - auction slot bids - AMM tokens pair - total outstanding tokens balance - `AMMID` to AMM `RootAccountID` mapping Add new classes to facilitate AMM integration into the payment engine. `BookStep` uses these classes to infer if AMM liquidity can be consumed. The AMM formula implementation uses the new Number class added in #4192. IOUAmount and STAmount use Number arithmetic. Add AMM unit tests for all features. AMM requires the following amendments: - featureAMM - fixUniversalNumber - featureFlowCross Notes: - Current trading fee threshold is 1% - AMM currency is generated by: 0x03 + 152 bits of sha256{cur1, cur2} - Current max AMM Offers is 30 --------- Co-authored-by: Howard Hinnant <[email protected]>
Add AMM functionality: - InstanceCreate - Deposit - Withdraw - Governance - Auctioning - payment engine integration To support this functionality, add: - New RPC method, `amm_info`, to fetch pool and LPT balances - AMM Root Account - trust line for each IOU AMM token - trust line to track Liquidity Provider Tokens (LPT) - `ltAMM` object The `ltAMM` object tracks: - fee votes - auction slot bids - AMM tokens pair - total outstanding tokens balance - `AMMID` to AMM `RootAccountID` mapping Add new classes to facilitate AMM integration into the payment engine. `BookStep` uses these classes to infer if AMM liquidity can be consumed. The AMM formula implementation uses the new Number class added in XRPLF#4192. IOUAmount and STAmount use Number arithmetic. Add AMM unit tests for all features. AMM requires the following amendments: - featureAMM - fixUniversalNumber - featureFlowCross Notes: - Current trading fee threshold is 1% - AMM currency is generated by: 0x03 + 152 bits of sha256{cur1, cur2} - Current max AMM Offers is 30 --------- Co-authored-by: Howard Hinnant <[email protected]>
Add AMM functionality: - InstanceCreate - Deposit - Withdraw - Governance - Auctioning - payment engine integration To support this functionality, add: - New RPC method, `amm_info`, to fetch pool and LPT balances - AMM Root Account - trust line for each IOU AMM token - trust line to track Liquidity Provider Tokens (LPT) - `ltAMM` object The `ltAMM` object tracks: - fee votes - auction slot bids - AMM tokens pair - total outstanding tokens balance - `AMMID` to AMM `RootAccountID` mapping Add new classes to facilitate AMM integration into the payment engine. `BookStep` uses these classes to infer if AMM liquidity can be consumed. The AMM formula implementation uses the new Number class added in XRPLF#4192. IOUAmount and STAmount use Number arithmetic. Add AMM unit tests for all features. AMM requires the following amendments: - featureAMM - fixUniversalNumber - featureFlowCross Notes: - Current trading fee threshold is 1% - AMM currency is generated by: 0x03 + 152 bits of sha256{cur1, cur2} - Current max AMM Offers is 30 --------- Co-authored-by: Howard Hinnant <[email protected]>
High Level Overview of Change
Context of Change
There are two many decimal floating point classes in rippled, and AMM needed a third. This merges common code. A side effect is that some transaction arithmetic becomes more accurate in the final digit or two. Therefore this change is guarded by an amendment.
The first many commits introduce
Number
. Only the final commit merges the common code withIOUAmount
andSTAmount
, and introduces the amendment.Type of Change
Test Plan
Some tests are updated with the slightly more accurate results.