|
21 | 21 | #![allow(clippy::suspicious_op_assign_impl)]
|
22 | 22 |
|
23 | 23 | #[cfg(feature = "std")]
|
24 |
| -#[cfg_attr(test, macro_use)] |
| 24 | +#[macro_use] |
25 | 25 | extern crate std;
|
26 | 26 |
|
27 | 27 | use core::cmp;
|
28 | 28 | use core::fmt;
|
| 29 | +use core::fmt::{Binary, Display, Formatter, LowerExp, LowerHex, Octal, UpperExp, UpperHex}; |
29 | 30 | use core::hash::{Hash, Hasher};
|
30 | 31 | use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
|
31 | 32 | use core::str::FromStr;
|
@@ -1000,20 +1001,71 @@ impl<T: Clone + Integer + Signed> Signed for Ratio<T> {
|
1000 | 1001 | }
|
1001 | 1002 |
|
1002 | 1003 | // 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 | + } |
1013 | 1057 | }
|
1014 |
| - } |
| 1058 | + }; |
1015 | 1059 | }
|
1016 | 1060 |
|
| 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 | + |
1017 | 1069 | impl<T: FromStr + Clone + Integer> FromStr for Ratio<T> {
|
1018 | 1070 | type Err = ParseRatioError;
|
1019 | 1071 |
|
@@ -1338,7 +1390,26 @@ mod test {
|
1338 | 1390 | numer: -2,
|
1339 | 1391 | denom: 1,
|
1340 | 1392 | };
|
| 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 | + |
1341 | 1403 | 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 | + }; |
1342 | 1413 | pub const _3_2: Rational = Ratio { numer: 3, denom: 2 };
|
1343 | 1414 | pub const _5_2: Rational = Ratio { numer: 5, denom: 2 };
|
1344 | 1415 | pub const _NEG1_2: Rational = Ratio {
|
@@ -1379,6 +1450,10 @@ mod test {
|
1379 | 1450 | numer: isize::MAX - 1,
|
1380 | 1451 | denom: 1,
|
1381 | 1452 | };
|
| 1453 | + pub const _BILLION: Rational = Ratio { |
| 1454 | + numer: 1_000_000_000, |
| 1455 | + denom: 1, |
| 1456 | + }; |
1382 | 1457 |
|
1383 | 1458 | #[cfg(feature = "bigint")]
|
1384 | 1459 | pub fn to_big(n: Rational) -> BigRational {
|
@@ -1557,14 +1632,165 @@ mod test {
|
1557 | 1632 | assert!(!_NEG1_2.is_integer());
|
1558 | 1633 | }
|
1559 | 1634 |
|
| 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 | + |
1560 | 1705 | #[test]
|
1561 |
| - #[cfg(feature = "std")] |
1562 | 1706 | 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 | + } |
1568 | 1794 | }
|
1569 | 1795 |
|
1570 | 1796 | mod arith {
|
|
0 commit comments