Skip to content

add more formatting traits #56

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

Merged
merged 26 commits into from
Mar 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f8023db
WIP: add more formatting traits
maxbla Jul 22, 2019
b1a95e7
make negative check more sane
maxbla Jul 29, 2019
e96d0ed
revert to old implementation of Display
maxbla Jul 29, 2019
c1e3ab3
switch from alloc::format to std::alloc
maxbla Jul 29, 2019
a3c45e3
fix format! macro in 1.26.0 and older rust
maxbla Aug 9, 2019
ebe4b2e
remove negative signs from some formatting traits
maxbla Aug 13, 2019
272053f
add test cases
maxbla Aug 13, 2019
49b41c2
remove extra trait bounds on Display traits
maxbla Aug 13, 2019
b4b1c82
switch formatting impls to using macros
maxbla Dec 6, 2019
e582dfe
improve Exp formatting traits' tests
maxbla Dec 6, 2019
f7aea8f
remove dependency on std for Exp formatting traits
maxbla Dec 6, 2019
c7ea97a
appease rustfmt
maxbla Dec 6, 2019
a661879
attempt to support no_std
maxbla Dec 6, 2019
aa1c26b
Add breaking change to Display
maxbla Feb 19, 2020
bb3a544
fix imports in rust 1.31
maxbla Feb 19, 2020
570e401
fix typo
maxbla Feb 20, 2020
37804b4
maybe fix imports
maxbla Feb 20, 2020
ac41f59
improve no_std display tests
maxbla Feb 21, 2020
c847d58
properly handle formatter.sign_plus()
maxbla Mar 13, 2020
c67e0ae
add parsing rust version from rustc to ci script
maxbla Mar 13, 2020
09e859a
fix shellcheck suggestions on ci script
maxbla Mar 13, 2020
e985a0f
reduce code duplication in formatting code
maxbla Mar 18, 2020
92064f3
fix displaying negatives with padding
maxbla Mar 18, 2020
92f8027
Improve testing exponential formatting
maxbla Mar 18, 2020
e0e3cb9
format build.rs
maxbla Mar 18, 2020
05213bd
hasten builds by only probing isize *Exp formatting
maxbla Mar 20, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ default = ["bigint-std", "std"]
std = ["num-integer/std", "num-traits/std"]
bigint = ["num-bigint"]
bigint-std = ["bigint", "num-bigint/std"]

[build-dependencies]
autocfg = "1.0.0"
8 changes: 8 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fn main() {
let ac = autocfg::new();
if ac.probe_expression("format!(\"{:e}\", 0_isize)") {
println!("cargo:rustc-cfg=has_int_exp_fmt");
}

autocfg::rerun_path(file!());
}
22 changes: 19 additions & 3 deletions ci/test_full.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
#!/bin/bash

set -ex
set -e

echo Testing num-rational on rustc ${TRAVIS_RUST_VERSION}
get_rust_version() {
local array=($(rustc --version));
echo "${array[1]}";
return 0;
}

if [ -z ${TRAVIS+x} ]
then RUST_VERSION=$(get_rust_version) # we're not in travis
else RUST_VERSION=$TRAVIS_RUST_VERSION # we're in travis
fi

if [ -z "${RUST_VERSION}" ]
then echo "WARNING: RUST_VERSION is undefined or empty string" 1>&2
else echo Testing num-rational on rustc "${RUST_VERSION}"
fi

set -x

STD_FEATURES="bigint-std serde"

case "$TRAVIS_RUST_VERSION" in
case "$RUST_VERSION" in
1.3[1-5].*) NO_STD_FEATURES="serde" ;;
*) NO_STD_FEATURES="bigint serde" ;;
esac
Expand Down
262 changes: 244 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@
#![allow(clippy::suspicious_op_assign_impl)]

#[cfg(feature = "std")]
#[cfg_attr(test, macro_use)]
#[macro_use]
extern crate std;

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

// String conversions
impl<T> fmt::Display for Ratio<T>
where
T: fmt::Display + Eq + One,
{
/// Renders as `numer/denom`. If denom=1, renders as numer.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.denom.is_one() {
write!(f, "{}", self.numer)
} else {
write!(f, "{}/{}", self.numer, self.denom)
macro_rules! impl_formatting {
($fmt_trait:ident, $prefix:expr, $fmt_str:expr, $fmt_alt:expr) => {
impl<T: $fmt_trait + Clone + Integer> $fmt_trait for Ratio<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let pre_pad = if self.denom.is_one() {
format!($fmt_str, self.numer)
} else {
if f.alternate() {
format!(concat!($fmt_str, "/", $fmt_alt), self.numer, self.denom)
} else {
format!(concat!($fmt_str, "/", $fmt_str), self.numer, self.denom)
}
};
//TODO: replace with strip_prefix, when stabalized
let (pre_pad, non_negative) = {
if pre_pad.starts_with("-") {
(&pre_pad[1..], false)
} else {
(&pre_pad[..], true)
}
};
f.pad_integral(non_negative, $prefix, pre_pad)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let plus = if f.sign_plus() && self.numer >= T::zero() {
"+"
} else {
""
};
if self.denom.is_one() {
if f.alternate() {
write!(f, concat!("{}", $fmt_alt), plus, self.numer)
} else {
write!(f, concat!("{}", $fmt_str), plus, self.numer)
}
} else {
if f.alternate() {
write!(
f,
concat!("{}", $fmt_alt, "/", $fmt_alt),
plus, self.numer, self.denom
)
} else {
write!(
f,
concat!("{}", $fmt_str, "/", $fmt_str),
plus, self.numer, self.denom
)
}
}
}
}
}
};
}

impl_formatting!(Display, "", "{}", "{:#}");
impl_formatting!(Octal, "0o", "{:o}", "{:#o}");
impl_formatting!(Binary, "0b", "{:b}", "{:#b}");
impl_formatting!(LowerHex, "0x", "{:x}", "{:#x}");
impl_formatting!(UpperHex, "0x", "{:X}", "{:#X}");
impl_formatting!(LowerExp, "", "{:e}", "{:#e}");
impl_formatting!(UpperExp, "", "{:E}", "{:#E}");

impl<T: FromStr + Clone + Integer> FromStr for Ratio<T> {
type Err = ParseRatioError;

Expand Down Expand Up @@ -1338,7 +1390,26 @@ mod test {
numer: -2,
denom: 1,
};
pub const _8: Rational = Ratio { numer: 8, denom: 1 };
pub const _15: Rational = Ratio {
numer: 15,
denom: 1,
};
pub const _16: Rational = Ratio {
numer: 16,
denom: 1,
};

pub const _1_2: Rational = Ratio { numer: 1, denom: 2 };
pub const _1_8: Rational = Ratio { numer: 1, denom: 8 };
pub const _1_15: Rational = Ratio {
numer: 1,
denom: 15,
};
pub const _1_16: Rational = Ratio {
numer: 1,
denom: 16,
};
pub const _3_2: Rational = Ratio { numer: 3, denom: 2 };
pub const _5_2: Rational = Ratio { numer: 5, denom: 2 };
pub const _NEG1_2: Rational = Ratio {
Expand Down Expand Up @@ -1379,6 +1450,10 @@ mod test {
numer: isize::MAX - 1,
denom: 1,
};
pub const _BILLION: Rational = Ratio {
numer: 1_000_000_000,
denom: 1,
};

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

#[cfg(not(feature = "std"))]
use core::fmt::{self, Write};
#[cfg(not(feature = "std"))]
#[derive(Debug)]
struct NoStdTester {
cursor: usize,
buf: [u8; NoStdTester::BUF_SIZE],
}

#[cfg(not(feature = "std"))]
impl NoStdTester {
fn new() -> NoStdTester {
NoStdTester {
buf: [0; Self::BUF_SIZE],
cursor: 0,
}
}

fn clear(&mut self) {
self.buf = [0; Self::BUF_SIZE];
self.cursor = 0;
}

const WRITE_ERR: &'static str = "Formatted output too long";
const BUF_SIZE: usize = 32;
}

#[cfg(not(feature = "std"))]
impl Write for NoStdTester {
fn write_str(&mut self, s: &str) -> fmt::Result {
for byte in s.bytes() {
self.buf[self.cursor] = byte;
self.cursor += 1;
if self.cursor >= self.buf.len() {
return Err(fmt::Error {});
}
}
Ok(())
}
}

#[cfg(not(feature = "std"))]
impl PartialEq<str> for NoStdTester {
fn eq(&self, other: &str) -> bool {
let other = other.as_bytes();
for index in 0..self.cursor {
if self.buf.get(index) != other.get(index) {
return false;
}
}
true
}
}

macro_rules! assert_fmt_eq {
($fmt_args:expr, $string:expr) => {
#[cfg(not(feature = "std"))]
{
let mut tester = NoStdTester::new();
write!(tester, "{}", $fmt_args).expect(NoStdTester::WRITE_ERR);
assert_eq!(tester, *$string);
tester.clear();
}
#[cfg(feature = "std")]
{
assert_eq!(std::fmt::format($fmt_args), $string);
}
};
}

#[test]
#[cfg(feature = "std")]
fn test_show() {
use std::string::ToString;
assert_eq!(format!("{}", _2), "2".to_string());
assert_eq!(format!("{}", _1_2), "1/2".to_string());
assert_eq!(format!("{}", _0), "0".to_string());
assert_eq!(format!("{}", Ratio::from_integer(-2)), "-2".to_string());
// Test:
// :b :o :x, :X, :?
// alternate or not (#)
// positive and negative
// padding
// does not test precision (i.e. truncation)
assert_fmt_eq!(format_args!("{}", _2), "2");
assert_fmt_eq!(format_args!("{:+}", _2), "+2");
assert_fmt_eq!(format_args!("{:-}", _2), "2");
assert_fmt_eq!(format_args!("{}", _1_2), "1/2");
assert_fmt_eq!(format_args!("{}", -_1_2), "-1/2"); // test negatives
assert_fmt_eq!(format_args!("{}", _0), "0");
assert_fmt_eq!(format_args!("{}", -_2), "-2");
assert_fmt_eq!(format_args!("{:+}", -_2), "-2");
assert_fmt_eq!(format_args!("{:b}", _2), "10");
assert_fmt_eq!(format_args!("{:#b}", _2), "0b10");
assert_fmt_eq!(format_args!("{:b}", _1_2), "1/10");
assert_fmt_eq!(format_args!("{:+b}", _1_2), "+1/10");
assert_fmt_eq!(format_args!("{:-b}", _1_2), "1/10");
assert_fmt_eq!(format_args!("{:b}", _0), "0");
assert_fmt_eq!(format_args!("{:#b}", _1_2), "0b1/0b10");
//no std does not support padding
#[cfg(feature = "std")]
assert_eq!(&format!("{:010b}", _1_2), "0000001/10");
#[cfg(feature = "std")]
assert_eq!(&format!("{:#010b}", _1_2), "0b001/0b10");
let half_i8: Ratio<i8> = Ratio::new(1_i8, 2_i8);
assert_fmt_eq!(format_args!("{:b}", -half_i8), "11111111/10");
assert_fmt_eq!(format_args!("{:#b}", -half_i8), "0b11111111/0b10");
#[cfg(feature = "std")]
assert_eq!(&format!("{:05}", Ratio::new(-1_i8, 1_i8)), "-0001");

assert_fmt_eq!(format_args!("{:o}", _8), "10");
assert_fmt_eq!(format_args!("{:o}", _1_8), "1/10");
assert_fmt_eq!(format_args!("{:o}", _0), "0");
assert_fmt_eq!(format_args!("{:#o}", _1_8), "0o1/0o10");
#[cfg(feature = "std")]
assert_eq!(&format!("{:010o}", _1_8), "0000001/10");
#[cfg(feature = "std")]
assert_eq!(&format!("{:#010o}", _1_8), "0o001/0o10");
assert_fmt_eq!(format_args!("{:o}", -half_i8), "377/2");
assert_fmt_eq!(format_args!("{:#o}", -half_i8), "0o377/0o2");

assert_fmt_eq!(format_args!("{:x}", _16), "10");
assert_fmt_eq!(format_args!("{:x}", _15), "f");
assert_fmt_eq!(format_args!("{:x}", _1_16), "1/10");
assert_fmt_eq!(format_args!("{:x}", _1_15), "1/f");
assert_fmt_eq!(format_args!("{:x}", _0), "0");
assert_fmt_eq!(format_args!("{:#x}", _1_16), "0x1/0x10");
#[cfg(feature = "std")]
assert_eq!(&format!("{:010x}", _1_16), "0000001/10");
#[cfg(feature = "std")]
assert_eq!(&format!("{:#010x}", _1_16), "0x001/0x10");
assert_fmt_eq!(format_args!("{:x}", -half_i8), "ff/2");
assert_fmt_eq!(format_args!("{:#x}", -half_i8), "0xff/0x2");

assert_fmt_eq!(format_args!("{:X}", _16), "10");
assert_fmt_eq!(format_args!("{:X}", _15), "F");
assert_fmt_eq!(format_args!("{:X}", _1_16), "1/10");
assert_fmt_eq!(format_args!("{:X}", _1_15), "1/F");
assert_fmt_eq!(format_args!("{:X}", _0), "0");
assert_fmt_eq!(format_args!("{:#X}", _1_16), "0x1/0x10");
#[cfg(feature = "std")]
assert_eq!(format!("{:010X}", _1_16), "0000001/10");
#[cfg(feature = "std")]
assert_eq!(format!("{:#010X}", _1_16), "0x001/0x10");
assert_fmt_eq!(format_args!("{:X}", -half_i8), "FF/2");
assert_fmt_eq!(format_args!("{:#X}", -half_i8), "0xFF/0x2");

#[cfg(has_int_exp_fmt)]
{
assert_fmt_eq!(format_args!("{:e}", -_2), "-2e0");
assert_fmt_eq!(format_args!("{:#e}", -_2), "-2e0");
assert_fmt_eq!(format_args!("{:+e}", -_2), "-2e0");
assert_fmt_eq!(format_args!("{:e}", _BILLION), "1e9");
assert_fmt_eq!(format_args!("{:+e}", _BILLION), "+1e9");
assert_fmt_eq!(format_args!("{:e}", _BILLION.recip()), "1e0/1e9");
assert_fmt_eq!(format_args!("{:+e}", _BILLION.recip()), "+1e0/1e9");

assert_fmt_eq!(format_args!("{:E}", -_2), "-2E0");
assert_fmt_eq!(format_args!("{:#E}", -_2), "-2E0");
assert_fmt_eq!(format_args!("{:+E}", -_2), "-2E0");
assert_fmt_eq!(format_args!("{:E}", _BILLION), "1E9");
assert_fmt_eq!(format_args!("{:+E}", _BILLION), "+1E9");
assert_fmt_eq!(format_args!("{:E}", _BILLION.recip()), "1E0/1E9");
assert_fmt_eq!(format_args!("{:+E}", _BILLION.recip()), "+1E0/1E9");
}
}

mod arith {
Expand Down