-
Notifications
You must be signed in to change notification settings - Fork 11.6k
Extend fixed-point numbers module #19336
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
Changes from 1 commit
3f40d5e
436dcd4
13d2ca4
eddb734
e5eb2a4
fc25dc3
d3c6513
ef4aabf
d091899
e5c59ee
ef87bad
b6b5f1e
45df3cb
6be8905
eb20a8e
e5678fd
b623732
9cf82dd
f1a0391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,24 @@ | ||
// 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 | ||
/// Defines an unsigned, 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. | ||
/// [Q notation](https://en.wikipedia.org/wiki/Q_(number_format)). `q` indicates it a fixed-point | ||
/// number. The `u` prefix indicates it is unsigned. The `32_32` suffix indicates the 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 zero denominator"; | ||
const EDenominator: vector<u8> = b"Quotient specified with a zero denominator"; | ||
|
||
#[error] | ||
const ERatioTooSmall: vector<u8> = | ||
b"`from_rational` called with a ratio that is too small, and is outside of the supported range"; | ||
const EQuotientTooSmall: vector<u8> = | ||
b"Quotient specified 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"; | ||
const EQuotientTooLarge: vector<u8> = | ||
b"Quotient specified is too large, and is outside of the supported range"; | ||
|
||
#[error] | ||
const EOverflow: vector<u8> = b"Overflow from an arithmetic operation"; | ||
|
@@ -32,14 +32,15 @@ const EDivisionByZero: vector<u8> = b"Division by zero"; | |
/// 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`. | ||
/// Create a fixed-point value from a quotient specified by its numerator and denominator. | ||
/// `from_quotient` and `from_int` should be preferred over using `from_raw`. | ||
/// Unless the denominator is a power of two, fractions can not be represented accurately, | ||
/// so be careful about rounding errors. | ||
/// Aborts if the denominator is zero. | ||
/// Aborts if the input is non-zero but so small that it will be represented as zero, e.g. smaller than 2^{-32}. | ||
/// Aborts if the input is non-zero but so small that it will be represented as zero, e.g. smaller | ||
/// than 2^{-32}. | ||
/// Aborts if the input is too large, e.g. larger than or equal to 2^32. | ||
public fun from_rational(numerator: u64, denominator: u64): UQ32_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 | ||
|
@@ -49,17 +50,17 @@ public fun from_rational(numerator: u64, denominator: u64): UQ32_32 { | |
let quotient = scaled_numerator / scaled_denominator; | ||
|
||
// The quotient can only be zero if the numerator is also zero. | ||
assert!(quotient != 0 || numerator == 0, ERatioTooSmall); | ||
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, ERatioTooLarge); | ||
assert!(quotient <= std::u64::max_value!() as u128, EQuotientTooLarge); | ||
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 { | ||
/// `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) | ||
} | ||
|
||
|
@@ -78,7 +79,7 @@ public fun sub(a: UQ32_32, b: UQ32_32): UQ32_32 { | |
UQ32_32(a.0 - b.0) | ||
} | ||
|
||
// Multiply two fixed-point numbers, truncating any fractional part of the product. | ||
/// 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)) | ||
|
@@ -91,6 +92,11 @@ public fun div(a: UQ32_32, b: UQ32_32): UQ32_32 { | |
UQ32_32(int_div(a.0, b)) | ||
} | ||
|
||
/// 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 | ||
} | ||
|
||
/// 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea behind My main question is whether or not we should have:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of the options above, I prefer the second option ( Now, lets enter the land of pedantry (but I think it's useful pedantry): I'm wondering if Similarly, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think having everything be "int" even while imprecise, will make everything line up nicely with the signed version |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,145 +13,145 @@ use std::uq32_32::{ | |
div, | ||
int_div, | ||
int_mul, | ||
from_integer, | ||
from_rational, | ||
from_int, | ||
from_quotient, | ||
from_raw, | ||
to_raw | ||
to_raw, | ||
}; | ||
|
||
#[test] | ||
fun from_rational_zero() { | ||
let x = from_rational(0, 1); | ||
fun from_quotient_zero() { | ||
let x = from_quotient(0, 1); | ||
assert_eq!(x.to_raw(), 0); | ||
} | ||
|
||
#[test] | ||
fun from_rational_max_numerator_denominator() { | ||
fun from_quotient_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 f = from_quotient(std::u64::max_value!(), std::u64::max_value!()); | ||
let one = f.to_raw(); | ||
assert_eq!(one, 1 << 32); // 0x1.00000000 | ||
} | ||
|
||
#[test] | ||
#[expected_failure(abort_code = uq32_32::EDenominator)] | ||
fun from_rational_div_zero() { | ||
fun from_quotient_div_zero() { | ||
// A denominator of zero should cause an arithmetic error. | ||
from_rational(2, 0); | ||
from_quotient(2, 0); | ||
} | ||
|
||
#[test] | ||
#[expected_failure(abort_code = uq32_32::ERatioTooLarge)] | ||
fun from_rational_ratio_too_large() { | ||
#[expected_failure(abort_code = uq32_32::EQuotientTooLarge)] | ||
fun from_quotient_ratio_too_large() { | ||
// The maximum value is 2^32 - 1. Check that anything larger aborts | ||
// with an overflow. | ||
from_rational(1 << 32, 1); // 2^32 | ||
from_quotient(1 << 32, 1); // 2^32 | ||
} | ||
|
||
#[test] | ||
#[expected_failure(abort_code = uq32_32::ERatioTooSmall)] | ||
fun from_rational_ratio_too_small() { | ||
#[expected_failure(abort_code = uq32_32::EQuotientTooSmall)] | ||
fun from_quotient_ratio_too_small() { | ||
// The minimum non-zero value is 2^-32. Check that anything smaller | ||
// aborts. | ||
from_rational(1, (1 << 32) + 1); // 1/(2^32 + 1) | ||
from_quotient(1, (1 << 32) + 1); // 1/(2^32 + 1) | ||
} | ||
|
||
#[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); | ||
fun test_from_int() { | ||
assert_eq!(from_int(0).to_raw(), 0); | ||
assert_eq!(from_int(1).to_raw(), 0x1_0000_0000); | ||
assert_eq!(from_int(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 a = from_quotient(3, 4); | ||
assert!(a.add(from_int(0)) == a); | ||
|
||
let c = a.add(from_integer(1)); | ||
assert!(from_rational(7, 4) == c); | ||
let c = a.add(from_int(1)); | ||
assert!(from_quotient(7, 4) == c); | ||
|
||
let b = from_rational(1, 4); | ||
let b = from_quotient(1, 4); | ||
let c = a.add(b); | ||
assert!(from_integer(1) == c); | ||
assert!(from_int(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 = from_int(1 << 31); | ||
let b = from_int(1 << 31); | ||
let _ = a.add(b); | ||
} | ||
|
||
#[test] | ||
fun test_sub() { | ||
let a = from_integer(5); | ||
assert_eq!(a.sub(from_integer(0)), a); | ||
let a = from_int(5); | ||
assert_eq!(a.sub(from_int(0)), a); | ||
|
||
let b = from_integer(4); | ||
let b = from_int(4); | ||
let c = a.sub(b); | ||
assert_eq!(from_integer(1), c); | ||
assert_eq!(from_int(1), c); | ||
} | ||
|
||
#[test] | ||
#[expected_failure(abort_code = uq32_32::EOverflow)] | ||
fun test_sub_underflow() { | ||
let a = from_integer(3); | ||
let b = from_integer(5); | ||
let a = from_int(3); | ||
let b = from_int(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 a = from_quotient(3, 4); | ||
assert!(a.mul(from_int(0)) == from_int(0)); | ||
assert!(a.mul(from_int(1)) == a); | ||
|
||
let b = from_rational(3, 2); | ||
let b = from_quotient(3, 2); | ||
let c = a.mul(b); | ||
let expected = from_rational(9, 8); | ||
let expected = from_quotient(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 = from_int(1 << 16); | ||
let b = from_int(1 << 16); | ||
let _ = a.mul(b); | ||
} | ||
|
||
#[test] | ||
fun test_div() { | ||
let a = from_rational(3, 4); | ||
assert!(a.div(from_integer(1)) == a); | ||
let a = from_quotient(3, 4); | ||
assert!(a.div(from_int(1)) == a); | ||
|
||
let b = from_integer(8); | ||
let b = from_int(8); | ||
let c = a.div(b); | ||
let expected = from_rational(3, 32); | ||
let expected = from_quotient(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 = from_int(7); | ||
let b = from_int(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 = from_int(1 << 31); | ||
let b = from_quotient(1, 2); | ||
let _ = a.div(b); | ||
} | ||
|
||
#[test] | ||
fun exact_int_div() { | ||
let f = from_rational(3, 4); // 0.75 | ||
let f = from_quotient(3, 4); // 0.75 | ||
let twelve = int_div(9, f); // 9 / 0.75 | ||
assert_eq!(twelve, 12); | ||
} | ||
|
@@ -175,21 +175,21 @@ fun int_div_overflow_small_divisor() { | |
#[test] | ||
#[expected_failure(abort_code = uq32_32::EOverflow)] | ||
fun int_div_overflow_large_numerator() { | ||
let f = from_rational(1, 2); // 0.5 | ||
let f = from_quotient(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 f = from_quotient(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 f = from_quotient(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. | ||
|
@@ -204,7 +204,7 @@ fun int_mul_truncates() { | |
#[test] | ||
#[expected_failure(abort_code = uq32_32::EOverflow)] | ||
fun int_mul_overflow_small_multiplier() { | ||
let f = from_rational(3, 2); // 1.5 | ||
let f = from_quotient(3, 2); // 1.5 | ||
// Multiply the maximum u64 value by 1.5. This should overflow. | ||
int_mul(std::u64::max_value!(), f); | ||
} | ||
|
@@ -219,20 +219,39 @@ fun int_mul_overflow_large_multiplier() { | |
|
||
#[test] | ||
fun test_comparison() { | ||
let a = from_rational(5, 2); | ||
let b = from_rational(5, 3); | ||
let c = from_rational(5, 2); | ||
let a = from_quotient(5, 2); | ||
let b = from_quotient(5, 3); | ||
let c = from_quotient(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)); | ||
assert!(from_int(0).le(a)); | ||
} | ||
|
||
#[random_test] | ||
fun test_raw(raw: u64) { | ||
assert_eq!(from_raw(raw).to_raw(), raw); | ||
} | ||
|
||
#[random_test] | ||
fun test_int_roundtrip(c: u32) { | ||
assert_eq!(from_int(c).to_int(), c); | ||
} | ||
|
||
#[random_test] | ||
fun test_mul_rand(n: u16, d: u16, c: u16) { | ||
if (d == 0) return; | ||
let q = from_quotient(n as u64, d as u64); | ||
assert_eq!(int_mul(c as u64, q), q.mul(from_int(c as u32)).to_int() as u64); | ||
} | ||
|
||
#[random_test] | ||
fun test_div_rand(n: u16, d: u16, c: u16) { | ||
if (d == 0) return; | ||
let q = from_quotient(n as u64, d as u64); | ||
assert_eq!(int_div(c as u64, q), from_int(c as u32).div(q).to_int() as u64); | ||
} | ||
Comment on lines
+240
to
+257
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!