Skip to content

Commit

Permalink
Migrate fixed_point32 to uq32_32 (#19741)
Browse files Browse the repository at this point in the history
## Description 

- Switching to `uqX_X` naming scheme 
- I cut out `one`, `zero`, and `is_zero` as they felt a bit superfluous 
- Renamed the `u64` based mul/div to `int_mul` and `int_div` so that the
naming is consistent across all `uqX_X` and `qX_X` modules
- We should consider `integer_mul` to match `from_integer` or similarly
`from_int` to match `int_mul`. Although... the "int"s here have
different sizes.
- Reordered functions a bit to best match what I think current modules
are doing in std
  - creation functions, followed by 
  - core API, followed by
  - less-core... API. Not sure what to call these 

## Test plan 

- Migrated  tests 

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] Indexer: 
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] REST API:
  • Loading branch information
tnowacki authored Oct 8, 2024
1 parent b6b5f1e commit 45df3cb
Show file tree
Hide file tree
Showing 6 changed files with 463 additions and 237 deletions.
111 changes: 1 addition & 110 deletions crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/// Defines a fixed-point numeric type with a 32-bit integer part and
/// a 32-bit fractional part.

#[deprecated(note = b"Use `std::uq32_32` instead. If you need to convert from a `FixedPoint32` to a `UQ32_32`, you can use the `std::fixed_point32::get_raw_value` with `std::uq32_32::from_raw_value`.")]
module std::fixed_point32;

/// Define a fixed-point numeric type with 32 fractional bits.
Expand All @@ -26,10 +26,6 @@ const EDENOMINATOR: u64 = 0x10001;
const EDIVISION: u64 = 0x20002;
/// The multiplied value would be too large to be held in a `u64`
const EMULTIPLICATION: u64 = 0x20003;
/// The sum would be too large to be held in a `u64`
const EADDITION: u64 = 0x20004;
/// The difference will be negative.
const ESUBTRACTION: u64 = 0x20006;
/// A division by zero was encountered
const EDIVISION_BY_ZERO: u64 = 0x10004;
/// The computed ratio when converting to a `FixedPoint32` would be unrepresentable
Expand All @@ -51,12 +47,6 @@ public fun multiply_u64(val: u64, multiplier: FixedPoint32): u64 {
product as u64
}

/// Multiply two fixed-point number s, truncating any fractional part of
/// the product. This will abort if the product overflows.
public fun mul(a: FixedPoint32, b: FixedPoint32): FixedPoint32 {
from_raw(multiply_u64(a.value, b))
}

/// Divide a u64 integer by a fixed-point number, truncating any
/// fractional part of the quotient. This will abort if the divisor
/// is zero or if the quotient overflows.
Expand All @@ -74,13 +64,6 @@ public fun divide_u64(val: u64, divisor: FixedPoint32): u64 {
quotient as u64
}

/// Divide two fixed-point numbers, truncating any fractional part of
/// the quotient. This will abort if the divisor is zero or if the
/// quotient overflows.
public fun div(a: FixedPoint32, b: FixedPoint32): FixedPoint32 {
from_raw(divide_u64(a.value, b))
}

/// Create a fixed-point value from a rational number specified by its
/// numerator and denominator. Calling this function should be preferred
/// for using `Self::create_from_raw_value` which is also available.
Expand All @@ -91,7 +74,6 @@ public fun div(a: FixedPoint32, b: FixedPoint32): FixedPoint32 {
/// point, you can use a denominator of 10^N to avoid numbers where the
/// very small imprecision in the binary representation could change the
/// rounding, e.g., 0.0125 will round down to 0.012 instead of up to 0.013.
#[deprecated(note = b"Use `from_rational` instead")]
public fun create_from_rational(numerator: u64, denominator: u64): FixedPoint32 {
// If the denominator is zero, this will abort.
// Scale the numerator to have 64 fractional bits and the denominator
Expand All @@ -108,57 +90,14 @@ public fun create_from_rational(numerator: u64, denominator: u64): FixedPoint32
FixedPoint32 { value: quotient as u64 }
}

/// Create a fixed-point value from a rational number specified by its
/// numerator and denominator. Calling this function should be preferred
/// for using `Self::from_raw` which is also available.
/// This will abort if the denominator is zero. It will also
/// abort if the numerator is nonzero and the ratio is not in the range
/// 2^-32 .. 2^32-1. When specifying decimal fractions, be careful about
/// rounding errors: if you round to display N digits after the decimal
/// point, you can use a denominator of 10^N to avoid numbers where the
/// very small imprecision in the binary representation could change the
/// rounding, e.g., 0.0125 will round down to 0.012 instead of up to 0.013.
public fun from_rational(numerator: u64, denominator: u64): FixedPoint32 {
// If the denominator is zero, this will abort.
// Scale the numerator to have 64 fractional bits and the denominator
// to have 32 fractional bits, so that the quotient will have 32
// fractional bits.
let scaled_numerator = numerator as u128 << 64;
let scaled_denominator = denominator as u128 << 32;
assert!(scaled_denominator != 0, EDENOMINATOR);
let quotient = scaled_numerator / scaled_denominator;
assert!(quotient != 0 || numerator == 0, ERATIO_OUT_OF_RANGE);
// Return the quotient as a fixed-point number. We first need to check whether the cast
// can succeed.
assert!(quotient <= MAX_U64, ERATIO_OUT_OF_RANGE);
FixedPoint32 { value: quotient as u64 }
}

/// Create a fixed-point value from an integer.
public fun from_integer(integer: u32): FixedPoint32 {
from_raw((integer as u64) << 32)
}

/// Create a fixedpoint value from a raw value.
public fun from_raw(value: u64): FixedPoint32 {
FixedPoint32 { value }
}

/// Create a fixedpoint value from a raw value.
#[deprecated(note = b"Use `from_raw` instead")]
public fun create_from_raw_value(value: u64): FixedPoint32 {
FixedPoint32 { value }
}

/// Accessor for the raw u64 value.
public fun to_raw(num: FixedPoint32): u64 {
num.value
}

/// Accessor for the raw u64 value. Other less common operations, such as
/// adding or subtracting FixedPoint32 values, can be done using the raw
/// values directly.
#[deprecated(note = b"Use `to_raw` instead")]
public fun get_raw_value(num: FixedPoint32): u64 {
num.value
}
Expand All @@ -167,51 +106,3 @@ public fun get_raw_value(num: FixedPoint32): u64 {
public fun is_zero(num: FixedPoint32): bool {
num.value == 0
}

/// Add two fixed-point numbers, `a + b`.
public fun add(a: FixedPoint32, b: FixedPoint32): FixedPoint32 {
let sum = (a.value as u128) + (b.value as u128);
assert!(sum <= MAX_U64, EADDITION);
from_raw(sum as u64)
}

/// Subtract two fixed-point numbers, `a - b`.
public fun sub(a: FixedPoint32, b: FixedPoint32): FixedPoint32 {
assert!(a.value >= b.value, ESUBTRACTION);
from_raw(a.value - b.value)
}

/// Return `true` if and only if `x <= y`.
public fun le(a: FixedPoint32, b: FixedPoint32): bool {
a.value <= b.value
}

/// Return `true` if and only if `a < b`.
public fun lt(a: FixedPoint32, b: FixedPoint32): bool {
a.value < b.value
}

/// Return `true` if and only if `a == b`.
public fun eq(a: FixedPoint32, b: FixedPoint32): bool {
a.value == b.value
}

/// Return `true` if and only if `a >= b`.
public fun ge(a: FixedPoint32, b: FixedPoint32): bool {
a.value >= b.value
}

/// Return `true` if and only if `a > b`.
public fun gt(a: FixedPoint32, b: FixedPoint32): bool {
a.value > b.value
}

/// Return a fixed-point representation of 1.
public fun one(): FixedPoint32 {
from_integer(1)
}

/// Return a fixed-point representation of 0.
public fun zero(): FixedPoint32 {
from_raw(0)
}
154 changes: 154 additions & 0 deletions crates/sui-framework/packages/move-stdlib/sources/uq32_32.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Defines an unisnged, fixed-point numeric type with a 32-bit integer part and a 32-bit fractional
/// part. The notation `uq32_32` and `UQ32_32` is based on
/// [Q notation](https://en.wikipedia.org/wiki/Q_(number_format)).`q` indicates it a
/// fixed-point number number. The `u` prefix indicates it is unsigned. The `32_32` suffix indicates
/// number of bits, where the first number indicates the number of bits in the integer part,
/// and the second the number of bits in the fractional part--in this case 32 bits for each.
module std::uq32_32;

#[error]
const EDenominator: vector<u8> = b"`from_rational` called with a denominator of zero";

#[error]
const ERatioTooSmall: vector<u8> =
b"`from_rational` called with a ratio that is too small, and is outside of the supported range";

#[error]
const ERatioTooLarge: vector<u8> =
b"`from_rational` called with a ratio that is too large, and is outside of the supported range";

#[error]
const EOverflow: vector<u8> = b"Overflow from an arithmetic operation";

#[error]
const EDivisionByZero: vector<u8> = b"Division by zero";

/// A fixed-point numeric type with 32 integer bits nd 32 fractional bits, represented by an
/// underlying 64 value. This is a binary representation, so decimal values may not be exactly
/// representable, but it provides more than 9 decimal digits of precision both before and after the
/// decimal point (18 digits total).
public struct UQ32_32(u64) has copy, drop, store;

/// Create a fixed-point value from a rational number specified by its numerator and denominator.
/// `from_rational` and `from_integer` should be preferred over using `from_raw`.
/// When specifying decimal fractions, be careful about rounding errors. If you round to display
/// N digits after the decimal point, you can use a denominator of 10^N to avoid numbers where the
/// very small imprecision in the binary representation could change the rounding. For example,
/// `0.0125` will round down to `0.012` instead of up to `0.013`.
/// Aborts if the denominator is zero.
/// Aborts if the numerator is non-zero and the ratio is not in the range 2^-32 .. 2^32-1.
public fun from_rational(numerator: u64, denominator: u64): UQ32_32 {
// If the denominator is zero, this will abort.
// Scale the numerator to have 64 fractional bits and the denominator to have 32 fractional
// bits, so that the quotient will have 32 fractional bits.
let scaled_numerator = numerator as u128 << 64;
let scaled_denominator = denominator as u128 << 32;
assert!(scaled_denominator != 0, EDenominator);
let quotient = scaled_numerator / scaled_denominator;
assert!(quotient != 0 || numerator == 0, ERatioTooSmall);
// Return the quotient as a fixed-point number. We first need to check whether the cast
// can succeed.
assert!(quotient <= std::u64::max_value!() as u128, ERatioTooLarge);
UQ32_32(quotient as u64)
}

/// Create a fixed-point value from an integer.
/// `from_integer` and `from_rational` should be preferred over using `from_raw`.
public fun from_integer(integer: u32): UQ32_32 {
UQ32_32((integer as u64) << 32)
}

/// Add two fixed-point numbers, `a + b`.
/// Aborts if the sum overflows.
public fun add(a: UQ32_32, b: UQ32_32): UQ32_32 {
let sum = a.0 as u128 + (b.0 as u128);
assert!(sum <= std::u64::max_value!() as u128, EOverflow);
UQ32_32(sum as u64)
}

/// Subtract two fixed-point numbers, `a - b`.
/// Aborts if `a < b`.
public fun sub(a: UQ32_32, b: UQ32_32): UQ32_32 {
assert!(a.0 >= b.0, EOverflow);
UQ32_32(a.0 - b.0)
}

// Multiply two fixed-point numbers, truncating any fractional part of the product.
/// Aborts if the product overflows.
public fun mul(a: UQ32_32, b: UQ32_32): UQ32_32 {
UQ32_32(int_mul(a.0, b))
}

/// Divide two fixed-point numbers, truncating any fractional part of the quotient.
/// Aborts if the divisor is zero.
/// Aborts if the quotient overflows.
public fun div(a: UQ32_32, b: UQ32_32): UQ32_32 {
UQ32_32(int_div(a.0, b))
}

/// Multiply a `u64` integer by a fixed-point number, truncating any fractional part of the product.
/// Aborts if the product overflows.
public fun int_mul(val: u64, multiplier: UQ32_32): u64 {
// The product of two 64 bit values has 128 bits, so perform the
// multiplication with u128 types and keep the full 128 bit product
// to avoid losing accuracy.
let unscaled_product = val as u128 * (multiplier.0 as u128);
// The unscaled product has 32 fractional bits (from the multiplier)
// so rescale it by shifting away the low bits.
let product = unscaled_product >> 32;
// Check whether the value is too large.
assert!(product <= std::u64::max_value!() as u128, EOverflow);
product as u64
}

/// Divide a `u64` integer by a fixed-point number, truncating any fractional part of the quotient.
/// Aborts if the divisor is zero.
/// Aborts if the quotient overflows.
public fun int_div(val: u64, divisor: UQ32_32): u64 {
// Check for division by zero.
assert!(divisor.0 != 0, EDivisionByZero);
// First convert to 128 bits and then shift left to
// add 32 fractional zero bits to the dividend.
let scaled_value = val as u128 << 32;
let quotient = scaled_value / (divisor.0 as u128);
// Check whether the value is too large.
assert!(quotient <= std::u64::max_value!() as u128, EOverflow);
// the value may be too large, which will cause the cast to fail
// with an arithmetic error.
quotient as u64
}

/// Less than or equal to. Returns `true` if and only if `a <= a`.
public fun le(a: UQ32_32, b: UQ32_32): bool {
a.0 <= b.0
}

/// Less than. Returns `true` if and only if `a < b`.
public fun lt(a: UQ32_32, b: UQ32_32): bool {
a.0 < b.0
}

/// Greater than or equal to. Returns `true` if and only if `a >= b`.
public fun ge(a: UQ32_32, b: UQ32_32): bool {
a.0 >= b.0
}

/// Greater than. Returns `true` if and only if `a > b`.
public fun gt(a: UQ32_32, b: UQ32_32): bool {
a.0 > b.0
}

/// Accessor for the raw u64 value. Can be paired with `from_raw` to perform less common operations
/// on the raw values directly.
public fun to_raw(a: UQ32_32): u64 {
a.0
}

/// Accessor for the raw u64 value. Can be paired with `to_raw` to perform less common operations
/// on the raw values directly.
public fun from_raw(raw_value: u64): UQ32_32 {
UQ32_32(raw_value)
}
Loading

0 comments on commit 45df3cb

Please sign in to comment.