Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Jul 10, 2024
0 parents commit 43a3987
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 0 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on: [push, pull_request]

jobs:
ci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rust-src, clippy, rustfmt
targets: ${{ matrix.targets }}
- name: Check rust version
run: rustc --version --verbose
- name: Check code format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
- name: Build
run: cargo build --target ${{ matrix.targets }} --all-features
- name: Unit test
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
run: cargo test --target ${{ matrix.targets }} -- --nocapture

doc:
runs-on: ubuntu-latest
strategy:
fail-fast: false
permissions:
contents: write
env:
default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }}
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Build docs
continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
run: |
cargo doc --no-deps --all-features
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
- name: Deploy to Github Pages
if: ${{ github.ref == env.default-branch }}
uses: JamesIves/github-pages-deploy-action@v4
with:
single-commit: true
branch: gh-pages
folder: target/doc
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
/.vscode
.DS_Store
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "ratio"
version = "0.1.0"
edition = "2021"
authors = ["Yuekai Jia <[email protected]>"]
description = "The type of ratios and related operations"
license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
homepage = "https://github.com/arceos-org/arceos"
repository = "https://github.com/arceos-org/arceos/tree/main/crates/ratio"
documentation = "https://arceos-org.github.io/arceos/ratio/index.html"

[dependencies]
187 changes: 187 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! The type of ratios and related operations.
//!
//! A **ratio** is the result of dividing two integers, i.e., the numerator and
//! denominator.
//!
//! # Examples
//!
//! ```
//! use ratio::Ratio;
//!
//! let ratio = Ratio::new(1, 3); // 1 / 3
//! assert_eq!(ratio.mul_trunc(20), 6); // trunc(20 * 1 / 3) = trunc(6.66..) = 6
//! assert_eq!(ratio.mul_round(20), 7); // round(20 * 1 / 3) = round(6.66..) = 7
//! println!("{:?}", ratio); // Ratio(1/3 ~= 1431655765/4294967296)
//! ```
#![cfg_attr(not(test), no_std)]

use core::{cmp::PartialEq, fmt};

/// The ratio type.
///
/// It converts `numerator / denominator` to `mult / (1 << shift)` to avoid
/// `u128` division on calculation. The `shift` is as large as possible to
/// improve precision.
///
/// Currently, it only supports `u32` as the numerator and denominator.
pub struct Ratio {
numerator: u32,
denominator: u32,
mult: u32,
shift: u32,
}

impl Ratio {
/// The zero ratio.
pub const fn zero() -> Self {
Self {
numerator: 0,
denominator: 0,
mult: 0,
shift: 0,
}
}

/// Creates a new ratio `numerator / denominator`.
pub const fn new(numerator: u32, denominator: u32) -> Self {
assert!(!(denominator == 0 && numerator != 0));
if numerator == 0 {
return Self {
numerator,
denominator,
mult: 0,
shift: 0,
};
}

// numerator / denominator == (numerator * (1 << shift) / denominator) / (1 << shift)
let mut shift = 32;
let mut mult;
loop {
mult = (((numerator as u64) << shift) + denominator as u64 / 2) / denominator as u64;
if mult <= u32::MAX as u64 || shift == 0 {
break;
}
shift -= 1;
}

while mult % 2 == 0 && shift > 0 {
mult /= 2;
shift -= 1;
}

Self {
numerator,
denominator,
mult: mult as u32,
shift,
}
}

/// Get the inverse ratio.
///
/// # Examples
///
/// ```
/// use ratio::Ratio;
///
/// let ratio = Ratio::new(1, 2);
/// assert_eq!(ratio.inverse(), Ratio::new(2, 1));
/// ```
pub const fn inverse(&self) -> Self {
Self::new(self.denominator, self.numerator)
}

/// Multiplies the ratio by a value and rounds the result down.
///
/// # Examples
///
/// ```
/// use ratio::Ratio;
///
/// let ratio = Ratio::new(2, 3);
/// assert_eq!(ratio.mul_trunc(99), 66); // 99 * 2 / 3 = 66
/// assert_eq!(ratio.mul_trunc(100), 66); // trunc(100 * 2 / 3) = trunc(66.66...) = 66
/// ```
pub const fn mul_trunc(&self, value: u64) -> u64 {
((value as u128 * self.mult as u128) >> self.shift) as u64
}

/// Multiplies the ratio by a value and rounds the result to the nearest
/// whole number.
///
/// # Examples
///
/// ```
/// use ratio::Ratio;
///
/// let ratio = Ratio::new(2, 3);
/// assert_eq!(ratio.mul_round(99), 66); // 99 * 2 / 3 = 66
/// assert_eq!(ratio.mul_round(100), 67); // round(100 * 2 / 3) = round(66.66...) = 67
/// ```
pub const fn mul_round(&self, value: u64) -> u64 {
((value as u128 * self.mult as u128 + (1 << self.shift >> 1)) >> self.shift) as u64
}
}

impl fmt::Debug for Ratio {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Ratio({}/{} ~= {}/{})",
self.numerator,
self.denominator,
self.mult,
1u64 << self.shift
)
}
}

impl PartialEq<Ratio> for Ratio {
#[inline]
fn eq(&self, other: &Ratio) -> bool {
self.mult == other.mult && self.shift == other.shift
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_ratio() {
let a = Ratio::new(625_000, 1_000_000);
let b = Ratio::new(1, u32::MAX);
let c = Ratio::new(u32::MAX, u32::MAX);
let d = Ratio::new(u32::MAX, 1);

assert_eq!(a.mult, 5);
assert_eq!(a.shift, 3);
assert_eq!(a.mul_trunc(800), 500);

assert_eq!(b.mult, 1);
assert_eq!(b.shift, 32);
assert_eq!(b.mul_trunc(u32::MAX as _), 0);
assert_eq!(b.mul_round(u32::MAX as _), 1);

assert_eq!(c.mult, 1);
assert_eq!(c.shift, 0);
assert_eq!(c.mul_trunc(u32::MAX as _), u32::MAX as _);

println!("{:?}", a);
println!("{:?}", b);
println!("{:?}", c);
println!("{:?}", d);
}

#[test]
fn test_zero() {
let z1 = Ratio::new(0, 100);
let z2 = Ratio::zero();
let z3 = Ratio::new(0, 0);
assert_eq!(z1.mul_trunc(233), 0);
assert_eq!(z2.mul_trunc(0), 0);
assert_eq!(z3.mul_round(456), 0);
}
}

0 comments on commit 43a3987

Please sign in to comment.