From 45df3cb2797ac27543a7444cf12b6a3853b1fd50 Mon Sep 17 00:00:00 2001 From: Todd Nowacki Date: Mon, 7 Oct 2024 23:05:36 -0700 Subject: [PATCH] Migrate fixed_point32 to uq32_32 (#19741) ## 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: --- .../move-stdlib/sources/fixed_point32.move | 111 +------- .../packages/move-stdlib/sources/uq32_32.move | 154 ++++++++++++ .../move-stdlib/tests/fixedpoint32_tests.move | 152 ++--------- .../move-stdlib/tests/uq32_32_tests.move | 238 ++++++++++++++++++ .../packages_compiled/move-stdlib | Bin 14348 -> 15598 bytes crates/sui-framework/published_api.txt | 45 ++++ 6 files changed, 463 insertions(+), 237 deletions(-) create mode 100644 crates/sui-framework/packages/move-stdlib/sources/uq32_32.move create mode 100644 crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move diff --git a/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move b/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move index 2212d446eddfb..9b1a2fe577010 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move +++ b/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move @@ -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. @@ -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 @@ -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. @@ -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. @@ -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 @@ -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 } @@ -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) -} diff --git a/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move b/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move new file mode 100644 index 0000000000000..8002ee7755cfd --- /dev/null +++ b/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move @@ -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 = b"`from_rational` called with a denominator of zero"; + +#[error] +const ERatioTooSmall: vector = + b"`from_rational` called with a ratio that is too small, and is outside of the supported range"; + +#[error] +const ERatioTooLarge: vector = + b"`from_rational` called with a ratio that is too large, and is outside of the supported range"; + +#[error] +const EOverflow: vector = b"Overflow from an arithmetic operation"; + +#[error] +const EDivisionByZero: vector = 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) +} diff --git a/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move b/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move index ab0ded7c33f32..f3a42e898ed11 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move @@ -3,17 +3,16 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -#[test_only] +#[test_only, allow(deprecated_usage)] module std::fixed_point32_tests; use std::fixed_point32; -use std::fixed_point32::{add, sub, mul, div, divide_u64, multiply_u64, from_integer, from_rational, from_raw, to_raw, one, zero}; #[test] #[expected_failure(abort_code = fixed_point32::EDENOMINATOR)] fun create_div_zero() { // A denominator of zero should cause an arithmetic error. - from_rational(2, 0); + fixed_point32::create_from_rational(2, 0); } #[test] @@ -21,7 +20,7 @@ fun create_div_zero() { fun create_overflow() { // The maximum value is 2^32 - 1. Check that anything larger aborts // with an overflow. - from_rational(4294967296, 1); // 2^32 + fixed_point32::create_from_rational(4294967296, 1); // 2^32 } #[test] @@ -29,12 +28,12 @@ fun create_overflow() { fun create_underflow() { // The minimum non-zero value is 2^-32. Check that anything smaller // aborts. - from_rational(1, 8589934592); // 2^-33 + fixed_point32::create_from_rational(1, 8589934592); // 2^-33 } #[test] fun create_zero() { - let x = from_rational(0, 1); + let x = fixed_point32::create_from_rational(0, 1); assert!(x.is_zero()); } @@ -42,175 +41,74 @@ fun create_zero() { #[expected_failure(abort_code = fixed_point32::EDIVISION_BY_ZERO)] fun divide_by_zero() { // Dividing by zero should cause an arithmetic error. - let f = from_raw(0); - divide_u64(1, f); + let f = fixed_point32::create_from_raw_value(0); + fixed_point32::divide_u64(1, f); } #[test] #[expected_failure(abort_code = fixed_point32::EDIVISION)] fun divide_overflow_small_divisore() { - let f = from_raw(1); // 0x0.00000001 + let f = fixed_point32::create_from_raw_value(1); // 0x0.00000001 // Divide 2^32 by the minimum fractional value. This should overflow. - divide_u64(4294967296, f); + fixed_point32::divide_u64(4294967296, f); } #[test] #[expected_failure(abort_code = fixed_point32::EDIVISION)] fun divide_overflow_large_numerator() { - let f = from_rational(1, 2); // 0.5 + let f = fixed_point32::create_from_rational(1, 2); // 0.5 // Divide the maximum u64 value by 0.5. This should overflow. - divide_u64(18446744073709551615, f); + fixed_point32::divide_u64(18446744073709551615, f); } #[test] #[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] fun multiply_overflow_small_multiplier() { - let f = from_rational(3, 2); // 1.5 + let f = fixed_point32::create_from_rational(3, 2); // 1.5 // Multiply the maximum u64 value by 1.5. This should overflow. - multiply_u64(18446744073709551615, f); + fixed_point32::multiply_u64(18446744073709551615, f); } #[test] #[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] fun multiply_overflow_large_multiplier() { - let f = from_raw(18446744073709551615); + let f = fixed_point32::create_from_raw_value(18446744073709551615); // Multiply 2^33 by the maximum fixed-point value. This should overflow. - multiply_u64(8589934592, f); + fixed_point32::multiply_u64(8589934592, f); } #[test] fun exact_multiply() { - let f = from_rational(3, 4); // 0.75 - let nine = multiply_u64(12, f); // 12 * 0.75 + let f = fixed_point32::create_from_rational(3, 4); // 0.75 + let nine = fixed_point32::multiply_u64(12, f); // 12 * 0.75 assert!(nine == 9); } #[test] fun exact_divide() { - let f = from_rational(3, 4); // 0.75 - let twelve = divide_u64(9, f); // 9 / 0.75 + let f = fixed_point32::create_from_rational(3, 4); // 0.75 + let twelve = fixed_point32::divide_u64(9, f); // 9 / 0.75 assert!(twelve == 12); } #[test] fun multiply_truncates() { - let f = from_rational(1, 3); // 0.333... - let not_three = multiply_u64(9, copy f); // 9 * 0.333... + let f = fixed_point32::create_from_rational(1, 3); // 0.333... + let not_three = fixed_point32::multiply_u64(9, copy f); // 9 * 0.333... // multiply_u64 does NOT round -- it truncates -- so values that // are not perfectly representable in binary may be off by one. assert!(not_three == 2); // Try again with a fraction slightly larger than 1/3. - let f = from_raw(f.to_raw() + 1); - let three = multiply_u64(9, f); + let f = fixed_point32::create_from_raw_value(f.get_raw_value() + 1); + let three = fixed_point32::multiply_u64(9, f); assert!(three == 3); } #[test] fun create_from_rational_max_numerator_denominator() { // Test creating a 1.0 fraction from the maximum u64 value. - let f = from_rational(18446744073709551615, 18446744073709551615); - let one = f.to_raw(); + let f = fixed_point32::create_from_rational(18446744073709551615, 18446744073709551615); + let one = f.get_raw_value(); assert!(one == 4294967296); // 0x1.00000000 } - -#[test] -#[expected_failure(abort_code = fixed_point32::ESUBTRACTION)] -fun test_subtraction_underflow() { - let a = from_integer(3); - let b = from_integer(5); - let _ = a.sub(b); -} - -#[test] -fun test_subtraction() { - let a = from_integer(5); - assert!(a.sub(zero()) == a); - - let b = from_integer(4); - let c = a.sub(b); - assert!(one() == c); -} - -#[test] -#[expected_failure(abort_code = fixed_point32::EADDITION)] -fun test_addition_overflow() { - let a = from_integer(1 << 31); - let b = from_integer(1 << 31); - let _ = a.add(b); -} - -#[test] -fun test_addition() { - let a = from_rational(3, 4); - assert!(a.add(zero()) == a); - - let c = a.add(one()); - assert!(from_rational(7, 4) == c); - - let b = from_rational(1, 4); - let c = a.add(b); - assert!(one() == c); -} - -#[test] -#[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] -fun test_multiplication_overflow() { - let a = from_integer(1 << 16); - let b = from_integer(1 << 16); - let _ = a.mul(b); -} - -#[test] -fun test_multiplication() { - let a = from_rational(3, 4); - assert!(a.mul(zero()) == zero()); - assert!(a.mul(one()) == a); - - let b = from_rational(3, 2); - let c = a.mul(b); - let expected = from_rational(9, 8); - assert!(c.eq(expected)); -} - -#[test] -#[expected_failure(abort_code = fixed_point32::EDIVISION_BY_ZERO)] -fun test_division_by_zero() { - let a = from_integer(7); - let b = zero(); - let _ = a.div(b); -} - -#[test] -fun test_division() { - let a = from_rational(3, 4); - assert!(a.div(one()) == a); - - let b = from_integer(8); - let c = a.div(b); - let expected = from_rational(3, 32); - assert!(c.eq(expected)); -} - -#[test] -#[expected_failure(abort_code = fixed_point32::EDIVISION)] -fun test_division_overflow() { - let a = from_integer(1 << 31); - let b = from_rational(1, 2); - let _ = a.div(b); -} - -#[test] -fun test_comparison() { - let a = from_rational(5, 2); - let b = from_rational(5, 3); - let c = from_rational(5, 2); - - assert!(b.le(a)); - assert!(b.lt(a)); - assert!(c.le(a)); - assert!(c.eq(a)); - assert!(a.ge(b)); - assert!(a.gt(b)); - assert!(zero().le(a)); -} diff --git a/crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move b/crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move new file mode 100644 index 0000000000000..996791a62e58e --- /dev/null +++ b/crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move @@ -0,0 +1,238 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module std::uq32_32_tests; + +use std::unit_test::assert_eq; +use std::uq32_32::{ + Self, + add, + sub, + mul, + div, + int_div, + int_mul, + from_integer, + from_rational, + from_raw, + to_raw +}; + +#[test] +fun from_rational_zero() { + let x = from_rational(0, 1); + assert_eq!(x.to_raw(), 0); +} + +#[test] +fun from_rational_max_numerator_denominator() { + // Test creating a 1.0 fraction from the maximum u64 value. + let f = from_rational(std::u64::max_value!(), std::u64::max_value!()); + let one = f.to_raw(); + assert_eq!(one, 0x1_0000_0000); // 0x1.00000000 +} + +#[test] +#[expected_failure(abort_code = uq32_32::EDivisionByZero)] +fun from_rational_div_zero() { + // A denominator of zero should cause an arithmetic error. + from_rational(2, 0); +} + +#[test] +#[expected_failure(abort_code = uq32_32::ERatioTooLarge)] +fun from_rational_ratio_too_large() { + // The maximum value is 2^32 - 1. Check that anything larger aborts + // with an overflow. + from_rational(0xFFFF_FFFF, 1); // 2^32 +} + +#[test] +#[expected_failure(abort_code = uq32_32::ERatioTooSmall)] +fun from_rational_ratio_too_small() { + // The minimum non-zero value is 2^-32. Check that anything smaller + // aborts. + from_rational(1, 0x1_FFFF_FFFF); // 2^-33 +} + +#[test] +fun test_from_integer() { + assert_eq!(from_integer(0).to_raw(), 0); + assert_eq!(from_integer(1).to_raw(), 0x1_0000_0000); + assert_eq!(from_integer(std::u32::max_value!()).to_raw(), std::u32::max_value!() as u64 << 32); +} + +#[test] +fun test_add() { + let a = from_rational(3, 4); + assert!(a.add(from_integer(0)) == a); + + let c = a.add(from_integer(1)); + assert!(from_rational(7, 4) == c); + + let b = from_rational(1, 4); + let c = a.add(b); + assert!(from_integer(1) == c); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_add_overflow() { + let a = from_integer(1 << 31); + let b = from_integer(1 << 31); + let _ = a.add(b); +} + +#[test] +fun test_sub() { + let a = from_integer(5); + assert_eq!(a.sub(from_integer(0)), a); + + let b = from_integer(4); + let c = a.sub(b); + assert_eq!(from_integer(1), c); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_sub_underflow() { + let a = from_integer(3); + let b = from_integer(5); + a.sub(b); +} + +#[test] +fun test_mul() { + let a = from_rational(3, 4); + assert!(a.mul(from_integer(0)) == from_integer(0)); + assert!(a.mul(from_integer(1)) == a); + + let b = from_rational(3, 2); + let c = a.mul(b); + let expected = from_rational(9, 8); + assert_eq!(c, expected); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_mul_overflow() { + let a = from_integer(1 << 16); + let b = from_integer(1 << 16); + let _ = a.mul(b); +} + +#[test] +fun test_div() { + let a = from_rational(3, 4); + assert!(a.div(from_integer(1)) == a); + + let b = from_integer(8); + let c = a.div(b); + let expected = from_rational(3, 32); + assert_eq!(c, expected); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EDivisionByZero)] +fun test_div_by_zero() { + let a = from_integer(7); + let b = from_integer(0); + let _ = a.div(b); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_div_overflow() { + let a = from_integer(1 << 31); + let b = from_rational(1, 2); + let _ = a.div(b); +} + +#[test] +fun exact_int_div() { + let f = from_rational(3, 4); // 0.75 + let twelve = int_div(9, f); // 9 / 0.75 + assert_eq!(twelve, 12); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EDivisionByZero)] +fun int_div_by_zero() { + let f = from_raw(0); // 0 + // Dividing by zero should cause an arithmetic error. + int_div(1, f); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_div_overflow_small_divisore() { + let f = from_raw(1); // 0x0.00000001 + // Divide 2^32 by the minimum fractional value. This should overflow. + int_div(0xFFFF_FFFF, f); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_div_overflow_large_numerator() { + let f = from_rational(1, 2); // 0.5 + // Divide the maximum u64 value by 0.5. This should overflow. + int_div(std::u64::max_value!(), f); +} + +#[test] +fun exact_int_mul() { + let f = from_rational(3, 4); // 0.75 + let nine = int_mul(12, f); // 12 * 0.75 + assert_eq!(nine, 9); +} + +#[test] +fun int_mul_truncates() { + let f = from_rational(1, 3); // 0.333... + let not_three = int_mul(9, copy f); // 9 * 0.333... + // multiply_u64 does NOT round -- it truncates -- so values that + // are not perfectly representable in binary may be off by one. + assert_eq!(not_three, 2); + + // Try again with a fraction slightly larger than 1/3. + let f = from_raw(f.to_raw() + 1); + let three = int_mul(9, f); + assert_eq!(three, 3); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_mul_overflow_small_multiplier() { + let f = from_rational(3, 2); // 1.5 + // Multiply the maximum u64 value by 1.5. This should overflow. + int_mul(std::u64::max_value!(), f); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_mul_overflow_large_multiplier() { + let f = from_raw(std::u64::max_value!()); + // Multiply 2^33 by the maximum fixed-point value. This should overflow. + int_mul(0x1_FFFF_FFFF, f); +} + +#[test] +fun test_comparison() { + let a = from_rational(5, 2); + let b = from_rational(5, 3); + let c = from_rational(5, 2); + + assert!(b.le(a)); + assert!(b.lt(a)); + assert!(c.le(a)); + assert_eq!(c, a); + assert!(a.ge(b)); + assert!(a.gt(b)); + assert!(from_integer(0).le(a)); +} + +#[random_test] +fun test_raw(raw: u64) { + assert_eq!(from_raw(raw).to_raw(), raw); +} diff --git a/crates/sui-framework/packages_compiled/move-stdlib b/crates/sui-framework/packages_compiled/move-stdlib index 8526133aab6a42f2b55fcc6b66147182fd964640..756c3bdc3e5b3daee7e65f8d4d98e2a1da6a1926 100644 GIT binary patch delta 1124 zcma)6J8u&~5T1S9+m9F$h$14bLL3B&l-Nn2MG&VU5&}YlK+Yw1aZWmW&3SDrG){r2 z===-#1rg$xfJBKzj|3GJMVQ^#GD(55#douF-^@3&H+%2fKaa@OkK_v*eD^-w_z~Cu z;1i&fa%OK?+w)G6x?hQ~f0K92|HG}^v30`pCmw|<00)2r0bmq?Foc4JoCd1F;}(Fc z!DpB{&|qtDG!zNGuq>2gY(QKf1?qx}J2V*~oN$imx%2$VTD87bWnmPtDCy-6GTE#r zEfqK7EN?bhs1n%STg+!XTfkZN@FxOj@CmnNO>g-E%U#Vn0mNI1>Ft9g0vy6S9y8;!T`MpB4+Xr@E{i zDW5fw?$cK<|DVQmA=Kd@R3hmJC8g--$Yl|>BJGh~)k&gQYpBCmbh_=f%oNs zPI5cUwCA#t6U8*l295aAlD=1a+>5h=RQ7$kR36(xpBI)b!VHh78LMPp$aZ}8oqRe~ ztZgQ}q=O&r!O;tIT&1b8N0j!C%ozeDd8X&f8NQ{rJsT~k02~ib=)E1A0fjZ(-SS!W zbhMCQR+-qzFB>=<7_N;Z0M#{cm+=+Vu1(*ca6XDebg zM{&|lhg*xqhm#r_qZ+8A1Llo5UY)bGz#Yr8J-jn*LxT-$?W~u%Q7^OBoGG(6&g1G* u+)2S@>Rs^Mr1%Fo$62Dm1o++y&bJ diff --git a/crates/sui-framework/published_api.txt b/crates/sui-framework/published_api.txt index 64879af4a2a3f..b6e84e9a8c557 100644 --- a/crates/sui-framework/published_api.txt +++ b/crates/sui-framework/published_api.txt @@ -4222,6 +4222,51 @@ sha2_256 sha3_256 public fun 0x1::hash +UQ32_32 + public struct + 0x1::uq32_32 +from_rational + public fun + 0x1::uq32_32 +from_integer + public fun + 0x1::uq32_32 +add + public fun + 0x1::uq32_32 +sub + public fun + 0x1::uq32_32 +mul + public fun + 0x1::uq32_32 +div + public fun + 0x1::uq32_32 +int_mul + public fun + 0x1::uq32_32 +int_div + public fun + 0x1::uq32_32 +le + public fun + 0x1::uq32_32 +lt + public fun + 0x1::uq32_32 +ge + public fun + 0x1::uq32_32 +gt + public fun + 0x1::uq32_32 +to_raw + public fun + 0x1::uq32_32 +from_raw + public fun + 0x1::uq32_32 empty public fun 0x1::vector