Skip to content

Commit

Permalink
rfqmath: implement fixed point based arithmetic for RFQ
Browse files Browse the repository at this point in the history
  • Loading branch information
Roasbeef authored and guggero committed Sep 4, 2024
1 parent ac783f0 commit bc28c37
Show file tree
Hide file tree
Showing 55 changed files with 2,313 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/rfq-and-decimal-display.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ node as:
## Examples

See `TestFindDecimalDisplayBoundaries` and `TestUsdToJpy` in
`rfq/convert_test.go` for how these examples are constructed.
`rfqmath/convert_test.go` for how these examples are constructed.

**Case 1**: Buying/selling USD against BTC.

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
gopkg.in/macaroon-bakery.v2 v2.1.0
gopkg.in/macaroon.v2 v2.1.0
modernc.org/sqlite v1.30.0
pgregory.net/rapid v1.1.0
)

require (
Expand Down Expand Up @@ -201,7 +202,6 @@ require (
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
nhooyr.io/websocket v1.8.7 // indirect
pgregory.net/rapid v1.1.0 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)

Expand Down
3 changes: 3 additions & 0 deletions rfq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ The high-level/conceptual explanation of [how RFQ (and related concepts such
as the decimal display value and price oracles) works, can be found
in this separate document](../docs/rfq-and-decimal-display.md).

The implementation of the [RFQ fixed point arithmetic can be found in the
`rfqmath` package](../rfqmath).

The actual [wire messages are located in the `rfqmsg`](../rfqmsg) package.

The [gRPC definitions of the RFQ methods can be found in the `taprpc/rfqrpc`
Expand Down
8 changes: 8 additions & 0 deletions rfqmath/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# RFQ math

This package contains code related to fixed point arithmetics used in RFQ
exchange rate calculations.

The high-level/conceptual explanation of [how RFQ (and related concepts such
as the decimal display value and price oracles) works, can be found
in this separate document](../docs/rfq-and-decimal-display.md).
213 changes: 213 additions & 0 deletions rfqmath/arithmetic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package rfqmath

import (
"math/big"

"golang.org/x/exp/constraints"
)

// Arithmetic defines the basic arithmetic operations. The structure of the
// interfaces allows for chaining the arithmetic operations.
type Arithmetic[N any] interface {
// Add returns the sum of the two numbers.
Add(N) N

// Mul returns the product of the two numbers.
Mul(N) N

// Sub returns the difference of the two numbers.
Sub(N) N

// Div returns the division of the two numbers.
Div(N) N
}

// Int is an interface that represents an integer types and the operations we
// care about w.r.t that type.
type Int[N any] interface {
// Arithmetic asserts that the target type of this interface satisfies
// the Arithmetic interface. This lets us get around limitations
// regarding recursive types in Go.
Arithmetic[N]

// Equals returns true if the two integers are equal.
Equals(other N) bool

// ToFloat converts the integer to a float.
ToFloat() float64

// FromFloat converts a float to the integer type.
FromFloat(float64) N

// ToUint64 converts the integer to a uint64.
ToUint64() uint64

// FromUint64 converts a uint64 to the integer type.
FromUint64(uint64) N
}

// NewInt creates a new integer of the target type.
func NewInt[N Int[N]]() N {
var n N
return n
}

// GoInt is a concrete implementation of the Int interface for the set of
// built-in integer types. It ends up mapping the integers to a uint64
// internally for operations.
type GoInt[T constraints.Unsigned] struct {
value T
}

// NewGoInt creates a new GoInt from the given integer.
func NewGoInt[T constraints.Unsigned](value T) GoInt[T] {
return GoInt[T]{
value: value,
}
}

// Add returns the sum of the two integers.
func (b GoInt[T]) Add(other GoInt[T]) GoInt[T] {
return GoInt[T]{
value: b.value + other.value,
}
}

// Mul returns the product of the two integers.
func (b GoInt[T]) Mul(other GoInt[T]) GoInt[T] {
return GoInt[T]{
value: b.value * other.value,
}
}

// Sub returns the difference of the two integers.
func (b GoInt[T]) Sub(other GoInt[T]) GoInt[T] {
return GoInt[T]{
value: b.value - other.value,
}
}

// Div returns the division of the two integers.
func (b GoInt[T]) Div(other GoInt[T]) GoInt[T] {
return GoInt[T]{
value: b.value / other.value,
}
}

// ToFloat converts the integer to a float.
func (b GoInt[T]) ToFloat() float64 {
return float64(b.value)
}

// FromFloat converts a float to the integer type.
func (b GoInt[T]) FromFloat(f float64) GoInt[T] {
b.value = T(f)
return b
}

// ToUint64 converts the integer to a uint64.
func (b GoInt[T]) ToUint64() uint64 {
return uint64(b.value)
}

// FromUint64 converts a uint64 to the integer type.
func (b GoInt[T]) FromUint64(u uint64) GoInt[T] {
b.value = T(u)
return b
}

// Equals returns true if the two integers are equal.
func (b GoInt[T]) Equals(other GoInt[T]) bool {
return b.value == other.value
}

// A compile-time constraint to ensure that the GoInt type implements the Int
// interface.
var _ Int[GoInt[uint]] = GoInt[uint]{}

// BigInt is a concrete implementation of the Int interface using Go's big
// integer type.
type BigInt struct {
value *big.Int
}

// NewBigInt creates a new BigInt from the given integer.
func NewBigInt(value *big.Int) BigInt {
return BigInt{
value: value,
}
}

// copyInt returns a copy of the internal big.Int. This is used to ensure we
// don't mutate the underlying bit.Int during arithmetic operations.
func (b BigInt) copyInt() *big.Int {
return new(big.Int).Set(b.value)
}

// Add returns the sum of the two integers.
func (b BigInt) Add(other BigInt) BigInt {
return BigInt{
value: b.copyInt().Add(b.value, other.value),
}
}

// Mul returns the product of the two integers.
func (b BigInt) Mul(other BigInt) BigInt {
return BigInt{
value: b.copyInt().Mul(b.value, other.value),
}
}

// Sub returns the difference of the two integers.
func (b BigInt) Sub(other BigInt) BigInt {
return BigInt{
value: b.copyInt().Sub(b.value, other.value),
}
}

// Div returns the division of the two integers.
func (b BigInt) Div(other BigInt) BigInt {
return BigInt{
value: b.copyInt().Div(b.value, other.value),
}
}

// ToFloat converts the integer to a float.
func (b BigInt) ToFloat() float64 {
floatVal, _ := b.value.Float64()
return floatVal
}

// FromFloat converts a float to the integer type.
func (b BigInt) FromFloat(f float64) BigInt {
if b.value == nil {
b.value = new(big.Int)
}

b.value.SetInt64(int64(f))
return b
}

// FromUint64 converts a uint64 to the integer type.
func (b BigInt) FromUint64(u uint64) BigInt {
if b.value == nil {
b.value = new(big.Int)
}

b.value.SetUint64(u)
return b
}

// ToUint64 converts the integer to a uint64.
func (b BigInt) ToUint64() uint64 {
return b.value.Uint64()
}

// Equals returns true if the two integers are equal.
func (b BigInt) Equals(other BigInt) bool {
return b.value.Cmp(other.value) == 0
}

// A compile-time constraint to ensure that the BigInt type implements the Int
// interface.
var _ Int[BigInt] = BigInt{}
Loading

0 comments on commit bc28c37

Please sign in to comment.