Skip to content

Commit 3243f70

Browse files
committed
feat: enable higher expressiveness after rust 1.67
1 parent 9bea5fc commit 3243f70

File tree

3 files changed

+199
-3
lines changed

3 files changed

+199
-3
lines changed

.github/workflows/changelog.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ jobs:
3232
PATH: 'README.md'
3333
COMMIT_MESSAGE: 'docs(readme): update contributors'
3434
AVATAR_SHAPE: 'round'
35+
DEFAULT_SCOPE: '*'

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bandwidth"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2021"
55
license = "Apache-2.0"
66
description="A library for representing bandwidth speed in a variety of units, mimicking the `core::time::Duration` struct."
@@ -9,8 +9,9 @@ homepage ="https://github.com/stack-rs/bandwidth"
99
repository = "https://github.com/stack-rs/bandwidth"
1010
keywords = ["bandwidth", "data-structures", "network", "no-std", "utility"]
1111
documentation = "https://docs.rs/bandwidth"
12-
categories = ["data-structures", "network-programming", "no-std::no-alloc"]
12+
categories = ["data-structures", "network-programming", "no-std"]
1313

1414
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1515

1616
[dependencies]
17+
rustversion = "1.0"

src/lib.rs

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![cfg_attr(not(feature = "std"), no_std)]
22

3-
use core::fmt;
3+
use core::fmt::{self, Write};
44
use core::iter::Sum;
55
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
66

@@ -1072,6 +1072,7 @@ impl Bandwidth {
10721072
}
10731073
}
10741074

1075+
#[rustversion::before(1.67)]
10751076
impl fmt::Debug for Bandwidth {
10761077
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10771078
/// Formats a floating point number in decimal notation.
@@ -1197,3 +1198,196 @@ impl fmt::Debug for Bandwidth {
11971198
}
11981199
}
11991200
}
1201+
1202+
#[rustversion::since(1.67)]
1203+
impl fmt::Debug for Bandwidth {
1204+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1205+
/// Formats a floating point number in decimal notation.
1206+
///
1207+
/// The number is given as the `integer_part` and a fractional part.
1208+
/// The value of the fractional part is `fractional_part / divisor`. So
1209+
/// `integer_part` = 3, `fractional_part` = 12 and `divisor` = 100
1210+
/// represents the number `3.012`. Trailing zeros are omitted.
1211+
///
1212+
/// `divisor` must not be above 100_000_000. It also should be a power
1213+
/// of 10, everything else doesn't make sense. `fractional_part` has
1214+
/// to be less than `10 * divisor`!
1215+
///
1216+
/// A prefix and postfix may be added. The whole thing is padded
1217+
/// to the formatter's `width`, if specified.
1218+
fn fmt_decimal(
1219+
f: &mut fmt::Formatter<'_>,
1220+
integer_part: u64,
1221+
mut fractional_part: u32,
1222+
mut divisor: u32,
1223+
prefix: &str,
1224+
postfix: &str,
1225+
) -> fmt::Result {
1226+
// Encode the fractional part into a temporary buffer. The buffer
1227+
// only need to hold 9 elements, because `fractional_part` has to
1228+
// be smaller than 10^9. The buffer is prefilled with '0' digits
1229+
// to simplify the code below.
1230+
let mut buf = [b'0'; 9];
1231+
1232+
// The next digit is written at this position
1233+
let mut pos = 0;
1234+
1235+
// We keep writing digits into the buffer while there are non-zero
1236+
// digits left and we haven't written enough digits yet.
1237+
while fractional_part > 0 && pos < f.precision().unwrap_or(9) {
1238+
// Write new digit into the buffer
1239+
buf[pos] = b'0' + (fractional_part / divisor) as u8;
1240+
1241+
fractional_part %= divisor;
1242+
divisor /= 10;
1243+
pos += 1;
1244+
}
1245+
1246+
// If a precision < 9 was specified, there may be some non-zero
1247+
// digits left that weren't written into the buffer. In that case we
1248+
// need to perform rounding to match the semantics of printing
1249+
// normal floating point numbers. However, we only need to do work
1250+
// when rounding up. This happens if the first digit of the
1251+
// remaining ones is >= 5.
1252+
let integer_part = if fractional_part > 0 && fractional_part >= divisor * 5 {
1253+
// Round up the number contained in the buffer. We go through
1254+
// the buffer backwards and keep track of the carry.
1255+
let mut rev_pos = pos;
1256+
let mut carry = true;
1257+
while carry && rev_pos > 0 {
1258+
rev_pos -= 1;
1259+
1260+
// If the digit in the buffer is not '9', we just need to
1261+
// increment it and can stop then (since we don't have a
1262+
// carry anymore). Otherwise, we set it to '0' (overflow)
1263+
// and continue.
1264+
if buf[rev_pos] < b'9' {
1265+
buf[rev_pos] += 1;
1266+
carry = false;
1267+
} else {
1268+
buf[rev_pos] = b'0';
1269+
}
1270+
}
1271+
1272+
// If we still have the carry bit set, that means that we set
1273+
// the whole buffer to '0's and need to increment the integer
1274+
// part.
1275+
if carry {
1276+
// If `integer_part == u64::MAX` and precision < 9, any
1277+
// carry of the overflow during rounding of the
1278+
// `fractional_part` into the `integer_part` will cause the
1279+
// `integer_part` itself to overflow. Avoid this by using an
1280+
// `Option<u64>`, with `None` representing `u64::MAX + 1`.
1281+
integer_part.checked_add(1)
1282+
} else {
1283+
Some(integer_part)
1284+
}
1285+
} else {
1286+
Some(integer_part)
1287+
};
1288+
1289+
// Determine the end of the buffer: if precision is set, we just
1290+
// use as many digits from the buffer (capped to 9). If it isn't
1291+
// set, we only use all digits up to the last non-zero one.
1292+
let end = f.precision().map(|p| core::cmp::min(p, 9)).unwrap_or(pos);
1293+
1294+
// This closure emits the formatted duration without emitting any
1295+
// padding (padding is calculated below).
1296+
let emit_without_padding = |f: &mut fmt::Formatter<'_>| {
1297+
if let Some(integer_part) = integer_part {
1298+
write!(f, "{prefix}{integer_part}")?;
1299+
} else {
1300+
// u64::MAX + 1 == 18446744073709551616
1301+
write!(f, "{prefix}18446744073709551616")?;
1302+
}
1303+
1304+
// Write the decimal point and the fractional part (if any).
1305+
if end > 0 {
1306+
// SAFETY: We are only writing ASCII digits into the buffer and
1307+
// it was initialized with '0's, so it contains valid UTF8.
1308+
let s = unsafe { core::str::from_utf8_unchecked(&buf[..end]) };
1309+
1310+
// If the user request a precision > 9, we pad '0's at the end.
1311+
let w = f.precision().unwrap_or(pos);
1312+
write!(f, ".{s:0<w$}")?;
1313+
}
1314+
1315+
write!(f, "{postfix}")
1316+
};
1317+
1318+
match f.width() {
1319+
None => {
1320+
// No `width` specified. There's no need to calculate the
1321+
// length of the output in this case, just emit it.
1322+
emit_without_padding(f)
1323+
}
1324+
Some(requested_w) => {
1325+
// A `width` was specified. Calculate the actual width of
1326+
// the output in order to calculate the required padding.
1327+
// It consists of 4 parts:
1328+
// 1. The prefix: is either "+" or "", so we can just use len().
1329+
// 2. The postfix: can be "µs" so we have to count UTF8 characters.
1330+
let mut actual_w = prefix.len() + postfix.chars().count();
1331+
// 3. The integer part:
1332+
if let Some(integer_part) = integer_part {
1333+
if let Some(log) = integer_part.checked_ilog10() {
1334+
// integer_part is > 0, so has length log10(x)+1
1335+
actual_w += 1 + log as usize;
1336+
} else {
1337+
// integer_part is 0, so has length 1.
1338+
actual_w += 1;
1339+
}
1340+
} else {
1341+
// integer_part is u64::MAX + 1, so has length 20
1342+
actual_w += 20;
1343+
}
1344+
// 4. The fractional part (if any):
1345+
if end > 0 {
1346+
let frac_part_w = f.precision().unwrap_or(pos);
1347+
actual_w += 1 + frac_part_w;
1348+
}
1349+
1350+
if requested_w <= actual_w {
1351+
// Output is already longer than `width`, so don't pad.
1352+
emit_without_padding(f)
1353+
} else {
1354+
// We need to add padding.
1355+
let post_padding_len = requested_w - actual_w;
1356+
emit_without_padding(f)?;
1357+
for _ in 0..post_padding_len {
1358+
f.write_char(f.fill())?;
1359+
}
1360+
Ok(())
1361+
}
1362+
}
1363+
}
1364+
}
1365+
1366+
// Print leading '+' sign if requested
1367+
let prefix = if f.sign_plus() { "+" } else { "" };
1368+
1369+
if self.gbps > 0 {
1370+
fmt_decimal(f, self.gbps, self.bps.0, BPS_PER_GBPS / 10, prefix, "gbps")
1371+
} else if self.bps.0 >= BPS_PER_MBPS {
1372+
fmt_decimal(
1373+
f,
1374+
(self.bps.0 / BPS_PER_MBPS) as u64,
1375+
self.bps.0 % BPS_PER_MBPS,
1376+
BPS_PER_MBPS / 10,
1377+
prefix,
1378+
"mbps",
1379+
)
1380+
} else if self.bps.0 >= BPS_PER_KBPS {
1381+
fmt_decimal(
1382+
f,
1383+
(self.bps.0 / BPS_PER_KBPS) as u64,
1384+
self.bps.0 % BPS_PER_KBPS,
1385+
BPS_PER_KBPS / 10,
1386+
prefix,
1387+
"kbps",
1388+
)
1389+
} else {
1390+
fmt_decimal(f, self.bps.0 as u64, 0, 1, prefix, "bps")
1391+
}
1392+
}
1393+
}

0 commit comments

Comments
 (0)