#![cfg_attr(not(feature = "std"), no_std)]
use core::fmt::{self, Write};
use core::iter::Sum;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
const BPS_PER_GBPS: u32 = 1_000_000_000;
const BPS_PER_MBPS: u32 = 1_000_000;
const BPS_PER_KBPS: u32 = 1_000;
const MBPS_PER_GBPS: u64 = 1_000;
const KBPS_PER_GBPS: u64 = 1_000_000;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(transparent)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
struct BitPerSec(u32);
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Bandwidth {
gbps: u64,
bps: BitPerSec, }
impl Bandwidth {
pub const MAX: Bandwidth = Bandwidth::new(u64::MAX, BPS_PER_GBPS - 1);
pub const ZERO: Bandwidth = Bandwidth::new(0, 0);
#[inline]
pub const fn new(gbps: u64, bps: u32) -> Bandwidth {
let gbps = match gbps.checked_add((bps / BPS_PER_GBPS) as u64) {
Some(gbps) => gbps,
None => panic!("overflow in Bandwidth::new"),
};
let bps = bps % BPS_PER_GBPS;
Bandwidth {
gbps,
bps: BitPerSec(bps),
}
}
#[inline]
pub const fn from_gbps(gbps: u64) -> Bandwidth {
Bandwidth::new(gbps, 0)
}
#[inline]
pub const fn from_mbps(mbps: u64) -> Bandwidth {
Bandwidth::new(
mbps / MBPS_PER_GBPS,
((mbps % MBPS_PER_GBPS) as u32) * BPS_PER_MBPS,
)
}
#[inline]
pub const fn from_kbps(kbps: u64) -> Bandwidth {
Bandwidth::new(
kbps / KBPS_PER_GBPS,
((kbps % KBPS_PER_GBPS) as u32) * BPS_PER_KBPS,
)
}
#[inline]
pub const fn from_bps(bps: u64) -> Bandwidth {
Bandwidth::new(
bps / (BPS_PER_GBPS as u64),
(bps % (BPS_PER_GBPS as u64)) as u32,
)
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.gbps == 0 && self.bps.0 == 0
}
#[inline]
pub const fn as_gbps(&self) -> u64 {
self.gbps
}
#[inline]
pub const fn subgbps_mbps(&self) -> u32 {
self.bps.0 / BPS_PER_MBPS
}
#[inline]
pub const fn subgbps_kbps(&self) -> u32 {
self.bps.0 / BPS_PER_KBPS
}
#[inline]
pub const fn subgbps_bps(&self) -> u32 {
self.bps.0
}
#[inline]
pub const fn as_mbps(&self) -> u128 {
self.gbps as u128 * MBPS_PER_GBPS as u128 + (self.bps.0 / BPS_PER_MBPS) as u128
}
#[inline]
pub const fn as_kbps(&self) -> u128 {
self.gbps as u128 * KBPS_PER_GBPS as u128 + (self.bps.0 / BPS_PER_KBPS) as u128
}
#[inline]
pub const fn as_bps(&self) -> u128 {
self.gbps as u128 * BPS_PER_GBPS as u128 + self.bps.0 as u128
}
#[inline]
pub const fn checked_add(self, rhs: Bandwidth) -> Option<Bandwidth> {
if let Some(mut gbps) = self.gbps.checked_add(rhs.gbps) {
let mut bps = self.bps.0 + rhs.bps.0;
if bps >= BPS_PER_GBPS {
bps -= BPS_PER_GBPS;
if let Some(new_gbps) = gbps.checked_add(1) {
gbps = new_gbps;
} else {
return None;
}
}
debug_assert!(bps < BPS_PER_GBPS);
Some(Bandwidth::new(gbps, bps))
} else {
None
}
}
#[inline]
pub const fn saturating_add(self, rhs: Bandwidth) -> Bandwidth {
match self.checked_add(rhs) {
Some(res) => res,
None => Bandwidth::MAX,
}
}
#[inline]
pub const fn checked_sub(self, rhs: Bandwidth) -> Option<Bandwidth> {
if let Some(mut gbps) = self.gbps.checked_sub(rhs.gbps) {
let bps = if self.bps.0 >= rhs.bps.0 {
self.bps.0 - rhs.bps.0
} else if let Some(sub_gbps) = gbps.checked_sub(1) {
gbps = sub_gbps;
self.bps.0 + BPS_PER_GBPS - rhs.bps.0
} else {
return None;
};
debug_assert!(bps < BPS_PER_GBPS);
Some(Bandwidth::new(gbps, bps))
} else {
None
}
}
#[inline]
pub const fn saturating_sub(self, rhs: Bandwidth) -> Bandwidth {
match self.checked_sub(rhs) {
Some(res) => res,
None => Bandwidth::ZERO,
}
}
#[inline]
pub const fn checked_mul(self, rhs: u32) -> Option<Bandwidth> {
let total_bps = self.bps.0 as u64 * rhs as u64;
let extra_gbps = total_bps / (BPS_PER_GBPS as u64);
let bps = (total_bps % (BPS_PER_GBPS as u64)) as u32;
if let Some(s) = self.gbps.checked_mul(rhs as u64) {
if let Some(gbps) = s.checked_add(extra_gbps) {
debug_assert!(bps < BPS_PER_GBPS);
return Some(Bandwidth::new(gbps, bps));
}
}
None
}
#[inline]
pub const fn saturating_mul(self, rhs: u32) -> Bandwidth {
match self.checked_mul(rhs) {
Some(res) => res,
None => Bandwidth::MAX,
}
}
#[inline]
pub const fn checked_div(self, rhs: u32) -> Option<Bandwidth> {
if rhs != 0 {
let gbps = self.gbps / (rhs as u64);
let carry = self.gbps - gbps * (rhs as u64);
let extra_bps = carry * (BPS_PER_GBPS as u64) / (rhs as u64);
let bps = self.bps.0 / rhs + (extra_bps as u32);
debug_assert!(bps < BPS_PER_GBPS);
Some(Bandwidth::new(gbps, bps))
} else {
None
}
}
#[inline]
pub fn as_gbps_f64(&self) -> f64 {
(self.gbps as f64) + (self.bps.0 as f64) / (BPS_PER_GBPS as f64)
}
#[inline]
pub fn as_gbps_f32(&self) -> f32 {
(self.gbps as f32) + (self.bps.0 as f32) / (BPS_PER_GBPS as f32)
}
#[inline]
pub fn from_gbps_f64(gbps: f64) -> Bandwidth {
match Bandwidth::try_from_gbps_f64(gbps) {
Ok(v) => v,
Err(e) => panic!("{}", e.description()),
}
}
#[inline]
pub fn from_gbps_f32(gbps: f32) -> Bandwidth {
match Bandwidth::try_from_gbps_f32(gbps) {
Ok(v) => v,
Err(e) => panic!("{}", e.description()),
}
}
#[inline]
pub fn mul_f64(self, rhs: f64) -> Bandwidth {
Bandwidth::from_gbps_f64(rhs * self.as_gbps_f64())
}
#[inline]
pub fn mul_f32(self, rhs: f32) -> Bandwidth {
Bandwidth::from_gbps_f32(rhs * self.as_gbps_f32())
}
#[inline]
pub fn div_f64(self, rhs: f64) -> Bandwidth {
Bandwidth::from_gbps_f64(self.as_gbps_f64() / rhs)
}
#[inline]
pub fn div_f32(self, rhs: f32) -> Bandwidth {
Bandwidth::from_gbps_f32(self.as_gbps_f32() / rhs)
}
#[inline]
pub fn div_bandwidth_f64(self, rhs: Bandwidth) -> f64 {
self.as_gbps_f64() / rhs.as_gbps_f64()
}
#[inline]
pub fn div_bandwidth_f32(self, rhs: Bandwidth) -> f32 {
self.as_gbps_f32() / rhs.as_gbps_f32()
}
}
impl Add for Bandwidth {
type Output = Bandwidth;
fn add(self, rhs: Bandwidth) -> Bandwidth {
self.checked_add(rhs)
.expect("overflow when adding Bandwidths")
}
}
impl AddAssign for Bandwidth {
fn add_assign(&mut self, rhs: Bandwidth) {
*self = *self + rhs;
}
}
impl Sub for Bandwidth {
type Output = Bandwidth;
fn sub(self, rhs: Bandwidth) -> Bandwidth {
self.checked_sub(rhs)
.expect("overflow when subtracting Bandwidths")
}
}
impl SubAssign for Bandwidth {
fn sub_assign(&mut self, rhs: Bandwidth) {
*self = *self - rhs;
}
}
impl Mul<u32> for Bandwidth {
type Output = Bandwidth;
fn mul(self, rhs: u32) -> Bandwidth {
self.checked_mul(rhs)
.expect("overflow when multiplying Bandwidth by scalar")
}
}
impl Mul<Bandwidth> for u32 {
type Output = Bandwidth;
fn mul(self, rhs: Bandwidth) -> Bandwidth {
rhs * self
}
}
impl MulAssign<u32> for Bandwidth {
fn mul_assign(&mut self, rhs: u32) {
*self = *self * rhs;
}
}
impl Div<u32> for Bandwidth {
type Output = Bandwidth;
fn div(self, rhs: u32) -> Bandwidth {
self.checked_div(rhs)
.expect("divide by zero error when dividing Bandwidth by scalar")
}
}
impl DivAssign<u32> for Bandwidth {
fn div_assign(&mut self, rhs: u32) {
*self = *self / rhs;
}
}
macro_rules! sum_Bandwidths {
($iter:expr) => {{
let mut total_gbps: u64 = 0;
let mut total_bps: u64 = 0;
for entry in $iter {
total_gbps = total_gbps
.checked_add(entry.gbps)
.expect("overflow in iter::sum over bandwidths");
total_bps = match total_bps.checked_add(entry.bps.0 as u64) {
Some(n) => n,
None => {
total_gbps = total_gbps
.checked_add(total_bps / BPS_PER_GBPS as u64)
.expect("overflow in iter::sum over bandwidths");
(total_bps % BPS_PER_GBPS as u64) + entry.bps.0 as u64
}
};
}
total_gbps = total_gbps
.checked_add(total_bps / BPS_PER_GBPS as u64)
.expect("overflow in iter::sum over bandwidths");
total_bps %= BPS_PER_GBPS as u64;
Bandwidth::new(total_gbps, total_bps as u32)
}};
}
impl Sum for Bandwidth {
fn sum<I: Iterator<Item = Bandwidth>>(iter: I) -> Bandwidth {
sum_Bandwidths!(iter)
}
}
impl<'a> Sum<&'a Bandwidth> for Bandwidth {
fn sum<I: Iterator<Item = &'a Bandwidth>>(iter: I) -> Bandwidth {
sum_Bandwidths!(iter)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TryFromFloatGbpsError {
kind: TryFromFloatGbpsErrorKind,
}
impl TryFromFloatGbpsError {
const fn description(&self) -> &'static str {
match self.kind {
TryFromFloatGbpsErrorKind::Negative => {
"can not convert float gbps to Bandwidth: value is negative"
}
TryFromFloatGbpsErrorKind::OverflowOrNan => {
"can not convert float gbps to Bandwidth: value is either too big or NaN"
}
}
}
}
impl fmt::Display for TryFromFloatGbpsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.description().fmt(f)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum TryFromFloatGbpsErrorKind {
Negative,
OverflowOrNan,
}
macro_rules! try_from_gbps {
(
gbps = $gbps: expr,
mantissa_bits = $mant_bits: literal,
exponent_bits = $exp_bits: literal,
offset = $offset: literal,
bits_ty = $bits_ty:ty,
double_ty = $double_ty:ty,
) => {{
const MIN_EXP: i16 = 1 - (1i16 << $exp_bits) / 2;
const MANT_MASK: $bits_ty = (1 << $mant_bits) - 1;
const EXP_MASK: $bits_ty = (1 << $exp_bits) - 1;
if $gbps < 0.0 {
return Err(TryFromFloatGbpsError {
kind: TryFromFloatGbpsErrorKind::Negative,
});
}
let bits = $gbps.to_bits();
let mant = (bits & MANT_MASK) | (MANT_MASK + 1);
let exp = ((bits >> $mant_bits) & EXP_MASK) as i16 + MIN_EXP;
let (gbps, bps) = if exp < -31 {
(0u64, 0u32)
} else if exp < 0 {
let t = <$double_ty>::from(mant) << ($offset + exp);
let bps_offset = $mant_bits + $offset;
let bps_tmp = u128::from(BPS_PER_GBPS) * u128::from(t);
let bps = (bps_tmp >> bps_offset) as u32;
let rem_mask = (1 << bps_offset) - 1;
let rem_msb_mask = 1 << (bps_offset - 1);
let rem = bps_tmp & rem_mask;
let is_tie = rem == rem_msb_mask;
let is_even = (bps & 1) == 0;
let rem_msb = bps_tmp & rem_msb_mask == 0;
let add_bps = !(rem_msb || (is_even && is_tie));
let bps = bps + add_bps as u32;
if ($mant_bits == 23) || (bps != BPS_PER_GBPS) {
(0, bps)
} else {
(1, 0)
}
} else if exp < $mant_bits {
let gbps = u64::from(mant >> ($mant_bits - exp));
let t = <$double_ty>::from((mant << exp) & MANT_MASK);
let bps_offset = $mant_bits;
let bps_tmp = <$double_ty>::from(BPS_PER_GBPS) * t;
let bps = (bps_tmp >> bps_offset) as u32;
let rem_mask = (1 << bps_offset) - 1;
let rem_msb_mask = 1 << (bps_offset - 1);
let rem = bps_tmp & rem_mask;
let is_tie = rem == rem_msb_mask;
let is_even = (bps & 1) == 0;
let rem_msb = bps_tmp & rem_msb_mask == 0;
let add_bps = !(rem_msb || (is_even && is_tie));
let bps = bps + add_bps as u32;
if ($mant_bits == 23) || (bps != BPS_PER_GBPS) {
(gbps, bps)
} else {
(gbps + 1, 0)
}
} else if exp < 64 {
let gbps = u64::from(mant) << (exp - $mant_bits);
(gbps, 0)
} else {
return Err(TryFromFloatGbpsError {
kind: TryFromFloatGbpsErrorKind::OverflowOrNan,
});
};
Ok(Bandwidth::new(gbps, bps))
}};
}
impl Bandwidth {
#[inline]
pub fn try_from_gbps_f32(gbps: f32) -> Result<Bandwidth, TryFromFloatGbpsError> {
try_from_gbps!(
gbps = gbps,
mantissa_bits = 23,
exponent_bits = 8,
offset = 41,
bits_ty = u32,
double_ty = u64,
)
}
#[inline]
pub fn try_from_gbps_f64(gbps: f64) -> Result<Bandwidth, TryFromFloatGbpsError> {
try_from_gbps!(
gbps = gbps,
mantissa_bits = 52,
exponent_bits = 11,
offset = 44,
bits_ty = u64,
double_ty = u128,
)
}
}
#[rustversion::before(1.67)]
impl fmt::Debug for Bandwidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn fmt_decimal(
f: &mut fmt::Formatter<'_>,
mut integer_part: u64,
mut fractional_part: u32,
mut divisor: u32,
) -> fmt::Result {
let mut buf = [b'0'; 9];
let mut pos = 0;
while fractional_part > 0 && pos < f.precision().unwrap_or(9) {
buf[pos] = b'0' + (fractional_part / divisor) as u8;
fractional_part %= divisor;
divisor /= 10;
pos += 1;
}
if fractional_part > 0 && fractional_part >= divisor * 5 {
let mut rev_pos = pos;
let mut carry = true;
while carry && rev_pos > 0 {
rev_pos -= 1;
if buf[rev_pos] < b'9' {
buf[rev_pos] += 1;
carry = false;
} else {
buf[rev_pos] = b'0';
}
}
if carry {
integer_part += 1;
}
}
let end = f.precision().map(|p| core::cmp::min(p, 9)).unwrap_or(pos);
if end == 0 {
write!(f, "{}", integer_part)
} else {
let s = unsafe { core::str::from_utf8_unchecked(&buf[..end]) };
let w = f.precision().unwrap_or(pos);
write!(f, "{}.{:0<width$}", integer_part, s, width = w)
}
}
if f.sign_plus() {
write!(f, "+")?;
}
if self.gbps > 0 {
fmt_decimal(f, self.gbps, self.bps.0, BPS_PER_GBPS / 10)?;
f.write_str("gbps")
} else if self.bps.0 >= BPS_PER_MBPS {
fmt_decimal(
f,
(self.bps.0 / BPS_PER_MBPS) as u64,
self.bps.0 % BPS_PER_MBPS,
BPS_PER_MBPS / 10,
)?;
f.write_str("mbps")
} else if self.bps.0 >= BPS_PER_KBPS {
fmt_decimal(
f,
(self.bps.0 / BPS_PER_KBPS) as u64,
self.bps.0 % BPS_PER_KBPS,
BPS_PER_KBPS / 10,
)?;
f.write_str("kbps")
} else {
fmt_decimal(f, self.bps.0 as u64, 0, 1)?;
f.write_str("bps")
}
}
}
#[rustversion::since(1.67)]
impl fmt::Debug for Bandwidth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn fmt_decimal(
f: &mut fmt::Formatter<'_>,
integer_part: u64,
mut fractional_part: u32,
mut divisor: u32,
prefix: &str,
postfix: &str,
) -> fmt::Result {
let mut buf = [b'0'; 9];
let mut pos = 0;
while fractional_part > 0 && pos < f.precision().unwrap_or(9) {
buf[pos] = b'0' + (fractional_part / divisor) as u8;
fractional_part %= divisor;
divisor /= 10;
pos += 1;
}
let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {
let mut rev_pos = pos;
let mut carry = true;
while carry && rev_pos > 0 {
rev_pos -= 1;
if buf[rev_pos] < b'9' {
buf[rev_pos] += 1;
carry = false;
} else {
buf[rev_pos] = b'0';
}
}
if carry {
integer_part.checked_add(1)
} else {
Some(integer_part)
}
} else {
Some(integer_part)
};
let end = f.precision().map(|p| core::cmp::min(p, 9)).unwrap_or(pos);
let emit_without_padding = |f: &mut fmt::Formatter<'_>| {
if let Some(integer_part) = integer_part {
write!(f, "{prefix}{integer_part}")?;
} else {
write!(f, "{prefix}18446744073709551616")?;
}
if end > 0 {
let s = unsafe { core::str::from_utf8_unchecked(&buf[..end]) };
let w = f.precision().unwrap_or(pos);
write!(f, ".{s:0<w$}")?;
}
write!(f, "{postfix}")
};
match f.width() {
None => {
emit_without_padding(f)
}
Some(requested_w) => {
let mut actual_w = prefix.len() + postfix.chars().count();
if let Some(integer_part) = integer_part {
if let Some(log) = integer_part.checked_ilog10() {
actual_w += 1 + log as usize;
} else {
actual_w += 1;
}
} else {
actual_w += 20;
}
if end > 0 {
let frac_part_w = f.precision().unwrap_or(pos);
actual_w += 1 + frac_part_w;
}
if requested_w <= actual_w {
emit_without_padding(f)
} else {
let post_padding_len = requested_w - actual_w;
emit_without_padding(f)?;
for _ in 0..post_padding_len {
f.write_char(f.fill())?;
}
Ok(())
}
}
}
}
let prefix = if f.sign_plus() { "+" } else { "" };
if self.gbps > 0 {
fmt_decimal(f, self.gbps, self.bps.0, BPS_PER_GBPS / 10, prefix, "gbps")
} else if self.bps.0 >= BPS_PER_MBPS {
fmt_decimal(
f,
(self.bps.0 / BPS_PER_MBPS) as u64,
self.bps.0 % BPS_PER_MBPS,
BPS_PER_MBPS / 10,
prefix,
"mbps",
)
} else if self.bps.0 >= BPS_PER_KBPS {
fmt_decimal(
f,
(self.bps.0 / BPS_PER_KBPS) as u64,
self.bps.0 % BPS_PER_KBPS,
BPS_PER_KBPS / 10,
prefix,
"kbps",
)
} else {
fmt_decimal(f, self.bps.0 as u64, 0, 1, prefix, "bps")
}
}
}