Skip to content
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

[feature]: add a function, rfqmath.FixedPointFromUint64_Decimal_Display #1385

Open
ZZiigguurraatt opened this issue Feb 13, 2025 · 5 comments
Labels
enhancement New feature or request

Comments

@ZZiigguurraatt
Copy link

There is a lot of confusion when running a price oracle server on how to properly input the exchange rate. One example of this is here: https://lightningcommunity.slack.com/archives/C03B3556HQ8/p1738866875640909?thread_ts=1738748330.746709&cid=C03B3556HQ8 .

The basic-price-oracle example uses the rfqmath.FixedPointFromUint64 function, and that is where a lot of the confusions arises.

if transactionType == oraclerpc.TransactionType_PURCHASE {
// As an example, the purchase rate is $42,000 per BTC. To
// increase precision, we represent this as 42 billion
// taproot asset units per BTC. Therefore, we scale the
// $42,000 per BTC rate by a factor of 10^6.
subjectAssetRate = rfqmath.FixedPointFromUint64[rfqmath.BigInt](
42_000, 6,
)
} else {
// Our example sell rate will be lower at $40,000 per BTC. This
// rate will be represented as 40 billion taproot asset units
// per BTC. Therefore, we scale the $40,000 per BTC rate by a
// factor of 10^6.
subjectAssetRate = rfqmath.FixedPointFromUint64[rfqmath.BigInt](
40_000, 6,
)

// FixedPointFromUint64 creates a new FixedPoint from the given integer and
// scale. Note that the input here should be *unscaled*.
func FixedPointFromUint64[N Int[N]](value uint64, scale uint8) FixedPoint[N] {
scaleN := NewInt[N]().FromFloat(math.Pow10(int(scale)))
coefficientN := NewInt[N]().FromUint64(value)
return FixedPoint[N]{
Coefficient: scaleN.Mul(coefficientN),
Scale: scale,
}
}

What is very confusing to new users is the "scale". It is recommended that this be the same as the decimal display that was defined when creating the asset. This leads users to think that the "coefficient" be defined in terms of decimal display asset units and then the exchange rate can be off by an order of magnitude of the decimal display. The "scale" parameter is recommended to be the same as the decimal display in order to reduce arithmetic round off error, but it doesn't really change the exchange rate itself.

I'd recommend a new function be created, rfqmath.FixedPointFromUint64_Decimal_Display, where the the exchange rate in terms of decimal display assets can be input, and then the decimal display digits as the second input. Then, convert the exchange rate from decimal display asset units to base asset units and use that as the coefficient input to rfqmath.FixedPointFromUint64. Then, also use the decimal display digits as the scale input to rfqmath.FixedPointFromUint64.

See also, the definition of FixedPoint:

// FixedPoint is a scaled integer representation of a fractional number.
//
// This type consists of two integer fields: a coefficient and a scale.
// Using this format enables precise and consistent representation of fractional
// numbers while avoiding floating-point data types, which are prone to
// precision errors.
//
// The relationship between the fractional representation and its fixed-point
// representation is expressed as:
// ```
// V = F_c / (10^F_s)
// ```
// where:
//
// * `V` is the fractional value.
//
// * `F_c` is the coefficient component of the fixed-point representation. It is
// the scaled-up fractional value represented as an integer.
//
// * `F_s` is the scale component. It is an integer specifying how
// many decimal places `F_c` should be divided by to obtain the fractional
// representation.
message FixedPoint {
// The coefficient is the fractional value scaled-up as an integer. This
// integer is represented as a string as it may be too large to fit in a
// uint64.
string coefficient = 1;
// The scale is the component that determines how many decimal places
// the coefficient should be divided by to obtain the fractional value.
uint32 scale = 2;
}
.

@kallerosenbaum
Copy link

Thanks for looking into this. What would have helped me most would be better examples in the price-oracle-example code. So I suggest to keep the code as is but improve the comments. For example:

 	// As an example, the purchase rate is $42,000 per BTC.
        // The returned value from this function must represent the
        // number of base asset units per BTC. With a decimal display
        // of 6, we should return the amount 42,000,000,000.
        // But we need to return the number as a FixedPoint, which
        // contains a coefficient (42,000,000,000) and a scale which is
        // used to increase precision in arithmetics. The scale can be
        // anything, but it's recommended to use the same value as
        // decimal_display. In our example, a reasonable value for
        // scale is thus 6. But it's going to work with 5 or 8 too.

@ZZiigguurraatt
Copy link
Author

ZZiigguurraatt commented Feb 20, 2025

I think your coefficient is going to be 42,000,000,000,000,000 with a scale of 6 and a decimal display of 6.

With a decimal display of 6 and $42,000 per BTC, 42,000,000,000 is going to be the "fractional value" defined here:

// FixedPoint is a scaled integer representation of a fractional number.
//
// This type consists of two integer fields: a coefficient and a scale.
// Using this format enables precise and consistent representation of fractional
// numbers while avoiding floating-point data types, which are prone to
// precision errors.
//
// The relationship between the fractional representation and its fixed-point
// representation is expressed as:
// ```
// V = F_c / (10^F_s)
// ```
// where:
//
// * `V` is the fractional value.
//
// * `F_c` is the coefficient component of the fixed-point representation. It is
// the scaled-up fractional value represented as an integer.
//
// * `F_s` is the scale component. It is an integer specifying how
// many decimal places `F_c` should be divided by to obtain the fractional
// representation.
message FixedPoint {
// The coefficient is the fractional value scaled-up as an integer. This
// integer is represented as a string as it may be too large to fit in a
// uint64.
string coefficient = 1;
// The scale is the component that determines how many decimal places
// the coefficient should be divided by to obtain the fractional value.
uint32 scale = 2;
}

@kallerosenbaum
Copy link

Gaahh, yes you're right of couse. Thanks for pointing it out. So my suggested comment should be:

 	// As an example, the purchase rate is $42,000 per BTC.
        // The returned value from this function must represent the
        // number of base asset units per BTC. With a decimal display
        // of 6, we should return the amount 42,000,000,000.
        // But we need to return the number as a FixedPoint, which
        // contains a coefficient (42,000,000,000,000,000) and a scale which is
        // used to increase precision in arithmetics. The scale can be
        // anything, but it's recommended to use the same value as
        // decimal_display. In our example, a reasonable value for
        // scale is thus 6. But it's going to work with 5 or 8 too.

@ZZiigguurraatt
Copy link
Author

yes, very confusing, that's why I think we need more than just better documentation to clear some of this stuff up for people!

@Roasbeef
Copy link
Member

The FixedPointFromUint64 function will do the scaling for you. You provide the base value, and it does the multiplication needed to scale it up.

The base value is the amount of assets to 1 BTC. If you didn't have any decimal display at all, then it would be 100k. However if you have a decimal display of 3, then this means 1000 units is actually $1, so it would be 100 million.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: 🆕 New
Development

No branches or pull requests

3 participants