Skip to content

Commit 47d63e2

Browse files
bors[bot]maxbla
andauthored
Merge #56
56: add more formatting traits r=cuviper a=maxbla fixes #1 TODO: - [X] eliminate heap use for `no_std` (does not support padding) - [x] add better formatting tests for `no_std` - [X] write a macro for formatting trait impls - [ ] ~simplify macro for formatting trait impls using `format_args!`~ Co-authored-by: Max Blachman <[email protected]>
2 parents dae2180 + 05213bd commit 47d63e2

File tree

4 files changed

+274
-21
lines changed

4 files changed

+274
-21
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,6 @@ default = ["bigint-std", "std"]
4545
std = ["num-integer/std", "num-traits/std"]
4646
bigint = ["num-bigint"]
4747
bigint-std = ["bigint", "num-bigint/std"]
48+
49+
[build-dependencies]
50+
autocfg = "1.0.0"

build.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
let ac = autocfg::new();
3+
if ac.probe_expression("format!(\"{:e}\", 0_isize)") {
4+
println!("cargo:rustc-cfg=has_int_exp_fmt");
5+
}
6+
7+
autocfg::rerun_path(file!());
8+
}

ci/test_full.sh

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
#!/bin/bash
22

3-
set -ex
3+
set -e
44

5-
echo Testing num-rational on rustc ${TRAVIS_RUST_VERSION}
5+
get_rust_version() {
6+
local array=($(rustc --version));
7+
echo "${array[1]}";
8+
return 0;
9+
}
10+
11+
if [ -z ${TRAVIS+x} ]
12+
then RUST_VERSION=$(get_rust_version) # we're not in travis
13+
else RUST_VERSION=$TRAVIS_RUST_VERSION # we're in travis
14+
fi
15+
16+
if [ -z "${RUST_VERSION}" ]
17+
then echo "WARNING: RUST_VERSION is undefined or empty string" 1>&2
18+
else echo Testing num-rational on rustc "${RUST_VERSION}"
19+
fi
20+
21+
set -x
622

723
STD_FEATURES="bigint-std serde"
824

9-
case "$TRAVIS_RUST_VERSION" in
25+
case "$RUST_VERSION" in
1026
1.3[1-5].*) NO_STD_FEATURES="serde" ;;
1127
*) NO_STD_FEATURES="bigint serde" ;;
1228
esac

src/lib.rs

Lines changed: 244 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
#![allow(clippy::suspicious_op_assign_impl)]
2222

2323
#[cfg(feature = "std")]
24-
#[cfg_attr(test, macro_use)]
24+
#[macro_use]
2525
extern crate std;
2626

2727
use core::cmp;
2828
use core::fmt;
29+
use core::fmt::{Binary, Display, Formatter, LowerExp, LowerHex, Octal, UpperExp, UpperHex};
2930
use core::hash::{Hash, Hasher};
3031
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
3132
use core::str::FromStr;
@@ -1000,20 +1001,71 @@ impl<T: Clone + Integer + Signed> Signed for Ratio<T> {
10001001
}
10011002

10021003
// String conversions
1003-
impl<T> fmt::Display for Ratio<T>
1004-
where
1005-
T: fmt::Display + Eq + One,
1006-
{
1007-
/// Renders as `numer/denom`. If denom=1, renders as numer.
1008-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1009-
if self.denom.is_one() {
1010-
write!(f, "{}", self.numer)
1011-
} else {
1012-
write!(f, "{}/{}", self.numer, self.denom)
1004+
macro_rules! impl_formatting {
1005+
($fmt_trait:ident, $prefix:expr, $fmt_str:expr, $fmt_alt:expr) => {
1006+
impl<T: $fmt_trait + Clone + Integer> $fmt_trait for Ratio<T> {
1007+
#[cfg(feature = "std")]
1008+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1009+
let pre_pad = if self.denom.is_one() {
1010+
format!($fmt_str, self.numer)
1011+
} else {
1012+
if f.alternate() {
1013+
format!(concat!($fmt_str, "/", $fmt_alt), self.numer, self.denom)
1014+
} else {
1015+
format!(concat!($fmt_str, "/", $fmt_str), self.numer, self.denom)
1016+
}
1017+
};
1018+
//TODO: replace with strip_prefix, when stabalized
1019+
let (pre_pad, non_negative) = {
1020+
if pre_pad.starts_with("-") {
1021+
(&pre_pad[1..], false)
1022+
} else {
1023+
(&pre_pad[..], true)
1024+
}
1025+
};
1026+
f.pad_integral(non_negative, $prefix, pre_pad)
1027+
}
1028+
#[cfg(not(feature = "std"))]
1029+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1030+
let plus = if f.sign_plus() && self.numer >= T::zero() {
1031+
"+"
1032+
} else {
1033+
""
1034+
};
1035+
if self.denom.is_one() {
1036+
if f.alternate() {
1037+
write!(f, concat!("{}", $fmt_alt), plus, self.numer)
1038+
} else {
1039+
write!(f, concat!("{}", $fmt_str), plus, self.numer)
1040+
}
1041+
} else {
1042+
if f.alternate() {
1043+
write!(
1044+
f,
1045+
concat!("{}", $fmt_alt, "/", $fmt_alt),
1046+
plus, self.numer, self.denom
1047+
)
1048+
} else {
1049+
write!(
1050+
f,
1051+
concat!("{}", $fmt_str, "/", $fmt_str),
1052+
plus, self.numer, self.denom
1053+
)
1054+
}
1055+
}
1056+
}
10131057
}
1014-
}
1058+
};
10151059
}
10161060

1061+
impl_formatting!(Display, "", "{}", "{:#}");
1062+
impl_formatting!(Octal, "0o", "{:o}", "{:#o}");
1063+
impl_formatting!(Binary, "0b", "{:b}", "{:#b}");
1064+
impl_formatting!(LowerHex, "0x", "{:x}", "{:#x}");
1065+
impl_formatting!(UpperHex, "0x", "{:X}", "{:#X}");
1066+
impl_formatting!(LowerExp, "", "{:e}", "{:#e}");
1067+
impl_formatting!(UpperExp, "", "{:E}", "{:#E}");
1068+
10171069
impl<T: FromStr + Clone + Integer> FromStr for Ratio<T> {
10181070
type Err = ParseRatioError;
10191071

@@ -1338,7 +1390,26 @@ mod test {
13381390
numer: -2,
13391391
denom: 1,
13401392
};
1393+
pub const _8: Rational = Ratio { numer: 8, denom: 1 };
1394+
pub const _15: Rational = Ratio {
1395+
numer: 15,
1396+
denom: 1,
1397+
};
1398+
pub const _16: Rational = Ratio {
1399+
numer: 16,
1400+
denom: 1,
1401+
};
1402+
13411403
pub const _1_2: Rational = Ratio { numer: 1, denom: 2 };
1404+
pub const _1_8: Rational = Ratio { numer: 1, denom: 8 };
1405+
pub const _1_15: Rational = Ratio {
1406+
numer: 1,
1407+
denom: 15,
1408+
};
1409+
pub const _1_16: Rational = Ratio {
1410+
numer: 1,
1411+
denom: 16,
1412+
};
13421413
pub const _3_2: Rational = Ratio { numer: 3, denom: 2 };
13431414
pub const _5_2: Rational = Ratio { numer: 5, denom: 2 };
13441415
pub const _NEG1_2: Rational = Ratio {
@@ -1379,6 +1450,10 @@ mod test {
13791450
numer: isize::MAX - 1,
13801451
denom: 1,
13811452
};
1453+
pub const _BILLION: Rational = Ratio {
1454+
numer: 1_000_000_000,
1455+
denom: 1,
1456+
};
13821457

13831458
#[cfg(feature = "bigint")]
13841459
pub fn to_big(n: Rational) -> BigRational {
@@ -1557,14 +1632,165 @@ mod test {
15571632
assert!(!_NEG1_2.is_integer());
15581633
}
15591634

1635+
#[cfg(not(feature = "std"))]
1636+
use core::fmt::{self, Write};
1637+
#[cfg(not(feature = "std"))]
1638+
#[derive(Debug)]
1639+
struct NoStdTester {
1640+
cursor: usize,
1641+
buf: [u8; NoStdTester::BUF_SIZE],
1642+
}
1643+
1644+
#[cfg(not(feature = "std"))]
1645+
impl NoStdTester {
1646+
fn new() -> NoStdTester {
1647+
NoStdTester {
1648+
buf: [0; Self::BUF_SIZE],
1649+
cursor: 0,
1650+
}
1651+
}
1652+
1653+
fn clear(&mut self) {
1654+
self.buf = [0; Self::BUF_SIZE];
1655+
self.cursor = 0;
1656+
}
1657+
1658+
const WRITE_ERR: &'static str = "Formatted output too long";
1659+
const BUF_SIZE: usize = 32;
1660+
}
1661+
1662+
#[cfg(not(feature = "std"))]
1663+
impl Write for NoStdTester {
1664+
fn write_str(&mut self, s: &str) -> fmt::Result {
1665+
for byte in s.bytes() {
1666+
self.buf[self.cursor] = byte;
1667+
self.cursor += 1;
1668+
if self.cursor >= self.buf.len() {
1669+
return Err(fmt::Error {});
1670+
}
1671+
}
1672+
Ok(())
1673+
}
1674+
}
1675+
1676+
#[cfg(not(feature = "std"))]
1677+
impl PartialEq<str> for NoStdTester {
1678+
fn eq(&self, other: &str) -> bool {
1679+
let other = other.as_bytes();
1680+
for index in 0..self.cursor {
1681+
if self.buf.get(index) != other.get(index) {
1682+
return false;
1683+
}
1684+
}
1685+
true
1686+
}
1687+
}
1688+
1689+
macro_rules! assert_fmt_eq {
1690+
($fmt_args:expr, $string:expr) => {
1691+
#[cfg(not(feature = "std"))]
1692+
{
1693+
let mut tester = NoStdTester::new();
1694+
write!(tester, "{}", $fmt_args).expect(NoStdTester::WRITE_ERR);
1695+
assert_eq!(tester, *$string);
1696+
tester.clear();
1697+
}
1698+
#[cfg(feature = "std")]
1699+
{
1700+
assert_eq!(std::fmt::format($fmt_args), $string);
1701+
}
1702+
};
1703+
}
1704+
15601705
#[test]
1561-
#[cfg(feature = "std")]
15621706
fn test_show() {
1563-
use std::string::ToString;
1564-
assert_eq!(format!("{}", _2), "2".to_string());
1565-
assert_eq!(format!("{}", _1_2), "1/2".to_string());
1566-
assert_eq!(format!("{}", _0), "0".to_string());
1567-
assert_eq!(format!("{}", Ratio::from_integer(-2)), "-2".to_string());
1707+
// Test:
1708+
// :b :o :x, :X, :?
1709+
// alternate or not (#)
1710+
// positive and negative
1711+
// padding
1712+
// does not test precision (i.e. truncation)
1713+
assert_fmt_eq!(format_args!("{}", _2), "2");
1714+
assert_fmt_eq!(format_args!("{:+}", _2), "+2");
1715+
assert_fmt_eq!(format_args!("{:-}", _2), "2");
1716+
assert_fmt_eq!(format_args!("{}", _1_2), "1/2");
1717+
assert_fmt_eq!(format_args!("{}", -_1_2), "-1/2"); // test negatives
1718+
assert_fmt_eq!(format_args!("{}", _0), "0");
1719+
assert_fmt_eq!(format_args!("{}", -_2), "-2");
1720+
assert_fmt_eq!(format_args!("{:+}", -_2), "-2");
1721+
assert_fmt_eq!(format_args!("{:b}", _2), "10");
1722+
assert_fmt_eq!(format_args!("{:#b}", _2), "0b10");
1723+
assert_fmt_eq!(format_args!("{:b}", _1_2), "1/10");
1724+
assert_fmt_eq!(format_args!("{:+b}", _1_2), "+1/10");
1725+
assert_fmt_eq!(format_args!("{:-b}", _1_2), "1/10");
1726+
assert_fmt_eq!(format_args!("{:b}", _0), "0");
1727+
assert_fmt_eq!(format_args!("{:#b}", _1_2), "0b1/0b10");
1728+
//no std does not support padding
1729+
#[cfg(feature = "std")]
1730+
assert_eq!(&format!("{:010b}", _1_2), "0000001/10");
1731+
#[cfg(feature = "std")]
1732+
assert_eq!(&format!("{:#010b}", _1_2), "0b001/0b10");
1733+
let half_i8: Ratio<i8> = Ratio::new(1_i8, 2_i8);
1734+
assert_fmt_eq!(format_args!("{:b}", -half_i8), "11111111/10");
1735+
assert_fmt_eq!(format_args!("{:#b}", -half_i8), "0b11111111/0b10");
1736+
#[cfg(feature = "std")]
1737+
assert_eq!(&format!("{:05}", Ratio::new(-1_i8, 1_i8)), "-0001");
1738+
1739+
assert_fmt_eq!(format_args!("{:o}", _8), "10");
1740+
assert_fmt_eq!(format_args!("{:o}", _1_8), "1/10");
1741+
assert_fmt_eq!(format_args!("{:o}", _0), "0");
1742+
assert_fmt_eq!(format_args!("{:#o}", _1_8), "0o1/0o10");
1743+
#[cfg(feature = "std")]
1744+
assert_eq!(&format!("{:010o}", _1_8), "0000001/10");
1745+
#[cfg(feature = "std")]
1746+
assert_eq!(&format!("{:#010o}", _1_8), "0o001/0o10");
1747+
assert_fmt_eq!(format_args!("{:o}", -half_i8), "377/2");
1748+
assert_fmt_eq!(format_args!("{:#o}", -half_i8), "0o377/0o2");
1749+
1750+
assert_fmt_eq!(format_args!("{:x}", _16), "10");
1751+
assert_fmt_eq!(format_args!("{:x}", _15), "f");
1752+
assert_fmt_eq!(format_args!("{:x}", _1_16), "1/10");
1753+
assert_fmt_eq!(format_args!("{:x}", _1_15), "1/f");
1754+
assert_fmt_eq!(format_args!("{:x}", _0), "0");
1755+
assert_fmt_eq!(format_args!("{:#x}", _1_16), "0x1/0x10");
1756+
#[cfg(feature = "std")]
1757+
assert_eq!(&format!("{:010x}", _1_16), "0000001/10");
1758+
#[cfg(feature = "std")]
1759+
assert_eq!(&format!("{:#010x}", _1_16), "0x001/0x10");
1760+
assert_fmt_eq!(format_args!("{:x}", -half_i8), "ff/2");
1761+
assert_fmt_eq!(format_args!("{:#x}", -half_i8), "0xff/0x2");
1762+
1763+
assert_fmt_eq!(format_args!("{:X}", _16), "10");
1764+
assert_fmt_eq!(format_args!("{:X}", _15), "F");
1765+
assert_fmt_eq!(format_args!("{:X}", _1_16), "1/10");
1766+
assert_fmt_eq!(format_args!("{:X}", _1_15), "1/F");
1767+
assert_fmt_eq!(format_args!("{:X}", _0), "0");
1768+
assert_fmt_eq!(format_args!("{:#X}", _1_16), "0x1/0x10");
1769+
#[cfg(feature = "std")]
1770+
assert_eq!(format!("{:010X}", _1_16), "0000001/10");
1771+
#[cfg(feature = "std")]
1772+
assert_eq!(format!("{:#010X}", _1_16), "0x001/0x10");
1773+
assert_fmt_eq!(format_args!("{:X}", -half_i8), "FF/2");
1774+
assert_fmt_eq!(format_args!("{:#X}", -half_i8), "0xFF/0x2");
1775+
1776+
#[cfg(has_int_exp_fmt)]
1777+
{
1778+
assert_fmt_eq!(format_args!("{:e}", -_2), "-2e0");
1779+
assert_fmt_eq!(format_args!("{:#e}", -_2), "-2e0");
1780+
assert_fmt_eq!(format_args!("{:+e}", -_2), "-2e0");
1781+
assert_fmt_eq!(format_args!("{:e}", _BILLION), "1e9");
1782+
assert_fmt_eq!(format_args!("{:+e}", _BILLION), "+1e9");
1783+
assert_fmt_eq!(format_args!("{:e}", _BILLION.recip()), "1e0/1e9");
1784+
assert_fmt_eq!(format_args!("{:+e}", _BILLION.recip()), "+1e0/1e9");
1785+
1786+
assert_fmt_eq!(format_args!("{:E}", -_2), "-2E0");
1787+
assert_fmt_eq!(format_args!("{:#E}", -_2), "-2E0");
1788+
assert_fmt_eq!(format_args!("{:+E}", -_2), "-2E0");
1789+
assert_fmt_eq!(format_args!("{:E}", _BILLION), "1E9");
1790+
assert_fmt_eq!(format_args!("{:+E}", _BILLION), "+1E9");
1791+
assert_fmt_eq!(format_args!("{:E}", _BILLION.recip()), "1E0/1E9");
1792+
assert_fmt_eq!(format_args!("{:+E}", _BILLION.recip()), "+1E0/1E9");
1793+
}
15681794
}
15691795

15701796
mod arith {

0 commit comments

Comments
 (0)