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

[move][std] Add std::uq64_64 #19894

Merged
merged 12 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
12 changes: 1 addition & 11 deletions crates/sui-framework-snapshot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -526,15 +526,5 @@
"0x000000000000000000000000000000000000000000000000000000000000dee9",
"0x000000000000000000000000000000000000000000000000000000000000000b"
]
},
"70": {
"git_revision": "1e7f26cc1baf",
"package_ids": [
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x000000000000000000000000000000000000000000000000000000000000dee9",
"0x000000000000000000000000000000000000000000000000000000000000000b"
]
}
}
}
98 changes: 98 additions & 0 deletions crates/sui-framework/packages/move-stdlib/sources/macros.move
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,101 @@ public macro fun try_as_u128($x: _): Option<u128> {
if (x > 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF) option::none()
else option::some(x as u128)
}

/// Creates a fixed-point value from a quotient specified by its numerator and denominator.
/// `$T` is the underlying integer type for the fixed-point value, where `$T` has `$t_bits` bits.
/// `$U` is the type used for intermediate calculations, where `$U` is the next larger integer type.
/// `$max_t` is the maximum value that can be represented by `$T`.
/// `$t_bits` (as mentioned above) is the total number of bits in the fixed-point value (integer
/// plus fractional).
/// `$fractional_bits` is the number of fractional bits in the fixed-point value.
public macro fun uq_from_quotient<$T, $U>(
tnowacki marked this conversation as resolved.
Show resolved Hide resolved
$numerator: $T,
$denominator: $T,
$max_t: $T,
$t_bits: u8,
$fractional_bits: u8,
$abort_denominator: _,
$abort_quotient_too_small: _,
$abort_quotient_too_large: _,
): $T {
let numerator = $numerator;
let denominator = $denominator;
if (denominator == 0) $abort_denominator;

// Scale the numerator to have `$t_bits` fractional bits and the denominator to have
// `$t_bits - $fractional_bits` fractional bits, so that the quotient will have
// `$fractional_bits` fractional bits.
let scaled_numerator = numerator as $U << $t_bits;
let scaled_denominator = denominator as $U << ($t_bits - $fractional_bits);
let quotient = scaled_numerator / scaled_denominator;

// The quotient can only be zero if the numerator is also zero.
if (quotient == 0 && numerator != 0) $abort_quotient_too_small;

// Return the quotient as a fixed-point number. We first need to check whether the cast
// can succeed.
if (quotient > $max_t as $U) $abort_quotient_too_large;
quotient as $T
}

public macro fun uq_from_int<$T, $U>($integer: $T, $fractional_bits: u8): $U {
($integer as $U) << $fractional_bits
}

public macro fun uq_add<$T, $U>($a: $T, $b: $T, $max_t: $T, $abort_overflow: _): $T {
let sum = $a as $U + ($b as $U);
if (sum > $max_t as $U) $abort_overflow;
sum as $T
}

public macro fun uq_sub<$T>($a: $T, $b: $T, $abort_overflow: _): $T {
let a = $a;
let b = $b;
if (a < b) $abort_overflow;
a - b
}

public macro fun uq_to_int<$T, $U>($a: $U, $fractional_bits: u8): $T {
($a >> $fractional_bits) as $T
}

public macro fun uq_int_mul<$T, $U>(
$val: $T,
$multiplier: $T,
$max_t: $T,
$fractional_bits: u8,
$abort_overflow: _,
): $T {
// The product of two `$T` bit values has the same number of bits as `$U`, so perform the
// multiplication with `$U` types and keep the full `$U` bit product
// to avoid losing accuracy.
let unscaled_product = $val as $U * ($multiplier as $U);
// The unscaled product has `$fractional_bits` fractional bits (from the multiplier)
// so rescale it by shifting away the low bits.
let product = unscaled_product >> $fractional_bits;
// Check whether the value is too large.
if (product > $max_t as $U) $abort_overflow;
product as $T
}

public macro fun uq_int_div<$T, $U>(
$val: $T,
$divisor: $T,
$max_t: $T,
$fractional_bits: u8,
$abort_division_by_zero: _,
$abort_overflow: _,
): $T {
let val = $val;
let divisor = $divisor;
// Check for division by zero.
if (divisor == 0) $abort_division_by_zero;
// First convert to $U to increase the number of bits to the next integer size
// and then shift left to add `$fractional_bits` fractional zero bits to the dividend.
let scaled_value = val as $U << $fractional_bits;
let quotient = scaled_value / (divisor as $U);
// Check whether the value is too large.
if (quotient > $max_t as $U) $abort_overflow;
quotient as $T
}
80 changes: 39 additions & 41 deletions crates/sui-framework/packages/move-stdlib/sources/uq32_32.move
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const EOverflow: vector<u8> = b"Overflow from an arithmetic operation";
#[error]
const EDivisionByZero: vector<u8> = b"Division by zero";

/// The total number of bits in the fixed-point number. Used in `macro` invocations.
const TOTAL_BITS: u8 = 64;
/// The number of fractional bits in the fixed-point number. Used in `macro` invocations.
const FRACTIONAL_BITS: u8 = 32;

/// A fixed-point numeric type with 32 integer bits and 32 fractional bits, represented by an
/// underlying 64 bit 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
Expand All @@ -41,42 +46,39 @@ public struct UQ32_32(u64) has copy, drop, store;
/// than 2^{-32}.
/// Aborts if the input is too large, e.g. larger than or equal to 2^32.
public fun from_quotient(numerator: u64, denominator: u64): UQ32_32 {
assert!(denominator != 0, EDenominator);

// 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;
let quotient = scaled_numerator / scaled_denominator;

// The quotient can only be zero if the numerator is also zero.
assert!(quotient != 0 || numerator == 0, EQuotientTooSmall);

// 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, EQuotientTooLarge);
UQ32_32(quotient as u64)
UQ32_32(std::macros::uq_from_quotient!<u64, u128>(
numerator,
denominator,
std::u64::max_value!(),
TOTAL_BITS,
FRACTIONAL_BITS,
abort EDenominator,
abort EQuotientTooSmall,
abort EQuotientTooLarge,
))
}

/// Create a fixed-point value from an integer.
/// `from_int` and `from_quotient` should be preferred over using `from_raw`.
public fun from_int(integer: u32): UQ32_32 {
UQ32_32((integer as u64) << 32)
UQ32_32(std::macros::uq_from_int!(integer, FRACTIONAL_BITS))
}

/// 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)
UQ32_32(std::macros::uq_add!<u64, u128>(
a.0,
b.0,
std::u64::max_value!(),
abort EOverflow,
))
}

/// 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)
UQ32_32(std::macros::uq_sub!(a.0, b.0, abort EOverflow))
}

/// Multiply two fixed-point numbers, truncating any fractional part of the product.
Expand All @@ -94,37 +96,33 @@ public fun div(a: UQ32_32, b: UQ32_32): UQ32_32 {

/// Convert a fixed-point number to an integer, truncating any fractional part.
public fun to_int(a: UQ32_32): u32 {
(a.0 >> 32) as u32
std::macros::uq_to_int!(a.0, FRACTIONAL_BITS)
}

/// 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
std::macros::uq_int_mul!<u64, u128>(
val,
multiplier.0,
std::u64::max_value!(),
FRACTIONAL_BITS,
abort EOverflow,
)
}

/// 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);
quotient as u64
std::macros::uq_int_div!<u64, u128>(
val,
divisor.0,
std::u64::max_value!(),
FRACTIONAL_BITS,
abort EDivisionByZero,
abort EOverflow,
)
}

/// Less than or equal to. Returns `true` if and only if `a <= a`.
Expand Down
Loading
Loading