mirror of
https://github.com/tiffany352/rink-rs
synced 2024-11-10 05:34:14 +00:00
Display recurring digits (#162)
Implements #72. ``` > 1/7 0.[142857]... (dimensionless) ``` Also changes the behavior of `to digits` so that it will attempt to find long-period recurring digits and show the entire sequence. The new default for `to digits` is to show up to 1000 recurring digits. ``` > googol/7 to digits 1428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428571428.[571428]... (dimensionless) > surveyfoot to digits 0.[304800609601219202438404876809753619507239014478028956057912115824231648463296926593853187706375412750825501651003302006604013208026416052832105664211328422656845313690627381254762509525019050038100076200152400, period 210]... millimeter (length) ```
This commit is contained in:
parent
6ce8d91b97
commit
92c58488d4
6 changed files with 467 additions and 95 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -425,7 +425,7 @@ dependencies = [
|
|||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex",
|
||||
"indexmap",
|
||||
"indexmap 1.9.3",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
|
@ -617,6 +617,12 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
|
@ -816,6 +822,12 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
@ -883,7 +895,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1334,6 +1356,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"chrono-humanize",
|
||||
"chrono-tz",
|
||||
"indexmap 2.2.6",
|
||||
"num-bigint",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
|
|
|
@ -23,6 +23,7 @@ chrono-tz = { version = "0.5.2", default-features = false }
|
|||
chrono-humanize = { version = "0.1.2", optional = true }
|
||||
serde = { version = "1", features = ["rc"], default-features = false }
|
||||
serde_derive = "1"
|
||||
indexmap = "2"
|
||||
|
||||
[dev_dependencies]
|
||||
serde_json = { version = "1", default-features = false }
|
||||
|
|
|
@ -3,10 +3,10 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use num_bigint::BigInt as NumInt;
|
||||
use num_traits::{Num, One, ToPrimitive, Zero};
|
||||
use num_traits::{Num, One, Signed, ToPrimitive, Zero};
|
||||
use std::cmp::Ord;
|
||||
use std::fmt;
|
||||
use std::ops::{BitAnd, BitOr, BitXor, Div, Mul, Rem};
|
||||
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Sub};
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct BigInt {
|
||||
|
@ -56,9 +56,28 @@ impl BigInt {
|
|||
as usize
|
||||
}
|
||||
|
||||
pub fn next_power_of(&self, base: u8) -> usize {
|
||||
let mut value = BigInt::one();
|
||||
let base = BigInt::from(base as i64);
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if self <= &value {
|
||||
break i;
|
||||
}
|
||||
value = &value * &base;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_int(&self) -> Option<i64> {
|
||||
self.inner.to_i64()
|
||||
}
|
||||
|
||||
pub fn abs(&self) -> BigInt {
|
||||
BigInt {
|
||||
inner: self.inner.abs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BigInt {
|
||||
|
@ -89,6 +108,32 @@ impl From<i64> for BigInt {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<i32> for BigInt {
|
||||
fn from(value: i32) -> BigInt {
|
||||
(value as i64).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add for &'a BigInt {
|
||||
type Output = BigInt;
|
||||
|
||||
fn add(self, rhs: &'a BigInt) -> BigInt {
|
||||
BigInt {
|
||||
inner: &self.inner + &rhs.inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sub for &'a BigInt {
|
||||
type Output = BigInt;
|
||||
|
||||
fn sub(self, rhs: &'a BigInt) -> BigInt {
|
||||
BigInt {
|
||||
inner: &self.inner - &rhs.inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Mul for &'a BigInt {
|
||||
type Output = BigInt;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use num_rational::BigRational as NumRat;
|
||||
use num_traits::{sign::Signed, One, ToPrimitive, Zero};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
@ -9,9 +10,11 @@ use std::cmp::Ord;
|
|||
use std::fmt;
|
||||
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
|
||||
|
||||
use crate::output::Digits;
|
||||
|
||||
use super::BigInt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)]
|
||||
pub struct BigRat {
|
||||
inner: NumRat,
|
||||
}
|
||||
|
@ -65,6 +68,193 @@ impl BigRat {
|
|||
pub fn as_float(&self) -> f64 {
|
||||
self.inner.to_f64().unwrap()
|
||||
}
|
||||
|
||||
// Checks if this is a small-period recurring number. Returns the
|
||||
// digits that recur as well as the period.
|
||||
//
|
||||
// Indicate recurring decimal sequences up to 10 digits long. The
|
||||
// rule here checks if the current remainder (`cursor`) divides
|
||||
// cleanly into b^N-1 where b is the base and N is the number of
|
||||
// digits to check, then the result of that division is the digits
|
||||
// that recur. So for example in 1/7, after the decimal point the
|
||||
// remainder will be 1/7, and 7 divides cleanly into 999999 to
|
||||
// produce 142857, the digits which recur. For remainders with a
|
||||
// numerator other than 1, the numerator is ignored during this
|
||||
// check, and then multiply the digits by it afterwards.
|
||||
//
|
||||
// This is done in machine ints because the extra range isn't
|
||||
// necessary.
|
||||
fn is_recurring(&self, base: u8, max_period: u32) -> Option<(i64, u32)> {
|
||||
assert!(max_period < 18);
|
||||
let numer = self.numer().as_int()?;
|
||||
let denom = self.denom().as_int()?;
|
||||
for i in 1..max_period {
|
||||
let test = (base as i64).pow(i) - 1;
|
||||
if test % denom == 0 {
|
||||
// Recurring digits
|
||||
let digits = (test / denom) * numer;
|
||||
return Some((digits, i));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn to_digits_impl(&self, base: u8, digits: Digits) -> (bool, String) {
|
||||
let sign = *self < BigRat::zero();
|
||||
let rational = self.abs();
|
||||
let num = rational.numer();
|
||||
let den = rational.denom();
|
||||
let intdigits = (&num / &den).size_in_base(base) as u32;
|
||||
let mut buf = String::new();
|
||||
if sign {
|
||||
buf.push('-');
|
||||
}
|
||||
let zero = BigRat::zero();
|
||||
let one = BigInt::one();
|
||||
let ten = BigInt::from(base as u64);
|
||||
let ten_rational = BigRat::ratio(&ten, &one);
|
||||
let mut cursor = &rational / &BigRat::ratio(&ten.pow(intdigits), &one);
|
||||
let mut n = 0;
|
||||
let mut only_zeros = true;
|
||||
let mut zeros = 0;
|
||||
let mut placed_decimal = false;
|
||||
let mut seen_remainders = IndexSet::new();
|
||||
loop {
|
||||
let exact = cursor == zero;
|
||||
let placed_ints = n >= intdigits;
|
||||
let ndigits = match digits {
|
||||
Digits::Default => 6,
|
||||
Digits::FullInt => 1000,
|
||||
Digits::Digits(n) => intdigits as i32 + n as i32,
|
||||
};
|
||||
// Conditions for exiting:
|
||||
// 1. The number is already exact and all the integer
|
||||
// positions have been placed, or
|
||||
// 2. The number is not exact, but all the integer positions
|
||||
// have been placed, and no more digits should be added
|
||||
// as the number is getting too long.
|
||||
let after_radix = n as i32 - zeros as i32;
|
||||
let max_radix = std::cmp::max(intdigits as i32, ndigits);
|
||||
let bail = (exact && placed_ints) || after_radix > max_radix;
|
||||
|
||||
// Before bailing, first check if adding a few
|
||||
// extra digits would yield a recurring decimal.
|
||||
if bail && !exact {
|
||||
if let Some((digits, period)) = cursor.is_recurring(base, 4) {
|
||||
buf.push('[');
|
||||
for n in 1..=period {
|
||||
let digit = digits / (base as i64).pow(period - n) % base as i64;
|
||||
buf.push(std::char::from_digit(digit as u32, base as u32).unwrap());
|
||||
}
|
||||
buf.push_str("]...");
|
||||
return (true, buf);
|
||||
}
|
||||
}
|
||||
|
||||
if bail {
|
||||
return (exact, buf);
|
||||
}
|
||||
if n == intdigits {
|
||||
buf.push('.');
|
||||
placed_decimal = true;
|
||||
}
|
||||
|
||||
// Handle recurring decimals
|
||||
if placed_decimal {
|
||||
// This catches really long period ones like 1/3937.
|
||||
if let (index, false) = seen_remainders.insert_full(cursor.clone()) {
|
||||
// If the remainder is the same as a previous one, then it's recurring.
|
||||
let period = n - intdigits - index as u32;
|
||||
buf.insert(buf.len() - period as usize, '[');
|
||||
if period > 10 {
|
||||
buf.push_str(", period ");
|
||||
buf.push_str(&period.to_string());
|
||||
}
|
||||
buf.push_str("]...");
|
||||
return (true, buf);
|
||||
}
|
||||
|
||||
// This catches really short period ones that would be
|
||||
// missed from bailing early.
|
||||
if let Some((digits, period)) = cursor.is_recurring(base, 10) {
|
||||
buf.push('[');
|
||||
for n in 1..=period {
|
||||
let digit = digits / (base as i64).pow(period - n) % base as i64;
|
||||
buf.push(std::char::from_digit(digit as u32, base as u32).unwrap());
|
||||
}
|
||||
buf.push_str("]...");
|
||||
return (true, buf);
|
||||
}
|
||||
}
|
||||
|
||||
let digit = &(&(&cursor.numer() * &ten) / &cursor.denom()) % &ten;
|
||||
let v: Option<i64> = digit.as_int();
|
||||
let v = v.unwrap();
|
||||
if v != 0 {
|
||||
only_zeros = false
|
||||
} else if only_zeros {
|
||||
zeros += 1;
|
||||
}
|
||||
if v != 0 || !only_zeros || n >= intdigits - 1 {
|
||||
buf.push(std::char::from_digit(v as u32, base as u32).unwrap());
|
||||
}
|
||||
cursor = &cursor * &ten_rational;
|
||||
cursor = &cursor - &BigRat::ratio(&digit, &one);
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_scientific(&self, base: u8, digits: Digits) -> (bool, String) {
|
||||
let num = self.numer();
|
||||
let den = self.denom();
|
||||
let absnum = num.abs();
|
||||
let intdigits = if &absnum > &den {
|
||||
(&absnum / &den).next_power_of(base) as i64 - 1
|
||||
} else {
|
||||
-((&den / &absnum).next_power_of(base) as i64)
|
||||
};
|
||||
let absexp = BigInt::from(base as i64).pow((intdigits as i64).abs() as u32);
|
||||
|
||||
let rational = if intdigits > 0 {
|
||||
self * &BigRat::ratio(&BigInt::one(), &absexp)
|
||||
} else {
|
||||
self * &BigRat::ratio(&absexp, &BigInt::one())
|
||||
};
|
||||
let ten = BigRat::small_ratio(base as i64, 1);
|
||||
let (rational, intdigits) = if rational.abs() == ten {
|
||||
(&rational / &ten, intdigits + 1)
|
||||
} else {
|
||||
(rational, intdigits)
|
||||
};
|
||||
let (is_exact, mut result) = rational.to_digits_impl(base, digits);
|
||||
if !result.contains('.') {
|
||||
result.push('.');
|
||||
result.push('0');
|
||||
}
|
||||
result.push('e');
|
||||
result.push_str(&format!("{}", intdigits));
|
||||
(is_exact, result)
|
||||
}
|
||||
|
||||
pub fn to_string(&self, base: u8, digits: Digits) -> (bool, String) {
|
||||
if self == &BigRat::small_ratio(0, 1) {
|
||||
return (true, "0".to_owned());
|
||||
}
|
||||
|
||||
let abs = self.abs();
|
||||
let is_computer_base = base == 2 || base == 8 || base == 16 || base == 32;
|
||||
let is_computer_integer = is_computer_base && self.denom() == BigInt::one();
|
||||
let can_use_sci = digits == Digits::Default && !is_computer_integer;
|
||||
|
||||
if can_use_sci
|
||||
&& (&abs >= &BigRat::small_ratio(1_000_000_000, 1)
|
||||
|| &abs <= &BigRat::small_ratio(1, 1_000_000_000))
|
||||
{
|
||||
self.to_scientific(base, digits)
|
||||
} else {
|
||||
self.to_digits_impl(base, digits)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NumRat> for BigRat {
|
||||
|
@ -145,3 +335,66 @@ impl<'a> Rem for &'a BigRat {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
output::Digits,
|
||||
types::{BigInt, BigRat},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_scientific_small() {
|
||||
let googol = BigRat::ratio(&BigInt::pow(&BigInt::from(10), 100), &BigInt::one());
|
||||
assert_eq!(
|
||||
googol.to_scientific(10, Digits::Default),
|
||||
(true, "1.0e100".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(&BigRat::one() / &googol).to_scientific(10, Digits::Default),
|
||||
(true, "1.0e-100".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(-&googol).to_scientific(10, Digits::Default),
|
||||
(true, "-1.0e100".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(&-&BigRat::one() / &googol).to_scientific(10, Digits::Default),
|
||||
(true, "-1.0e-100".to_owned())
|
||||
);
|
||||
let googol_plus = &googol + &BigRat::one();
|
||||
assert_eq!(
|
||||
googol_plus.to_scientific(10, Digits::Default),
|
||||
(false, "1.000000e100".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(&BigRat::one() / &googol_plus).to_scientific(10, Digits::Default),
|
||||
(false, "9.999999e-101".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(-&googol_plus).to_scientific(10, Digits::Default),
|
||||
(false, "-1.000000e100".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(&-&BigRat::one() / &googol_plus).to_scientific(10, Digits::Default),
|
||||
(false, "-9.999999e-101".to_owned())
|
||||
);
|
||||
let googol_minus = &googol - &BigRat::one();
|
||||
assert_eq!(
|
||||
googol_minus.to_scientific(10, Digits::Default),
|
||||
(false, "9.999999e99".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(&BigRat::one() / &googol_minus).to_scientific(10, Digits::Default),
|
||||
(false, "1.000000e-100".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(-&googol_minus).to_scientific(10, Digits::Default),
|
||||
(false, "-9.999999e99".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
(&-&BigRat::one() / &googol_minus).to_scientific(10, Digits::Default),
|
||||
(false, "-1.000000e-100".to_owned())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,10 @@ impl Numeric {
|
|||
Numeric::Rational(BigRat::zero())
|
||||
}
|
||||
|
||||
pub fn from_frac(num: impl Into<BigInt>, den: impl Into<BigInt>) -> Numeric {
|
||||
Numeric::Rational(BigRat::ratio(&num.into(), &den.into()))
|
||||
}
|
||||
|
||||
pub fn abs(&self) -> Numeric {
|
||||
match *self {
|
||||
Numeric::Rational(ref rational) => Numeric::Rational(rational.abs()),
|
||||
|
@ -114,93 +118,18 @@ impl Numeric {
|
|||
|
||||
/// Returns (is_exact, repr).
|
||||
pub fn to_string(&self, base: u8, digits: Digits) -> (bool, String) {
|
||||
use std::char::from_digit;
|
||||
use std::num::FpCategory;
|
||||
|
||||
if let Numeric::Float(value) = *self {
|
||||
match value.classify() {
|
||||
match *self {
|
||||
Numeric::Rational(ref rational) => rational.to_string(base, digits),
|
||||
Numeric::Float(value) => match value.classify() {
|
||||
FpCategory::Nan => return (false, "NaN".to_owned()),
|
||||
FpCategory::Infinite if value.is_sign_positive() => {
|
||||
return (false, "Inf".to_owned())
|
||||
}
|
||||
FpCategory::Infinite => return (false, "-Inf".to_owned()),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let sign = *self < Numeric::zero();
|
||||
let rational = self.abs();
|
||||
let (num, den) = rational.to_rational();
|
||||
let rational = match rational {
|
||||
Numeric::Rational(rational) => rational,
|
||||
Numeric::Float(f) => BigRat::from(f),
|
||||
};
|
||||
let intdigits = (&num / &den).size_in_base(base) as u32;
|
||||
let mut buf = String::new();
|
||||
if sign {
|
||||
buf.push('-');
|
||||
}
|
||||
let zero = BigRat::zero();
|
||||
let one = BigInt::one();
|
||||
let ten = BigInt::from(base as u64);
|
||||
let ten_rational = BigRat::ratio(&ten, &one);
|
||||
let mut cursor = &rational / &BigRat::ratio(&ten.pow(intdigits), &one);
|
||||
let mut n = 0;
|
||||
let mut only_zeros = true;
|
||||
let mut zeros = 0;
|
||||
let mut placed_decimal = false;
|
||||
loop {
|
||||
let exact = cursor == zero;
|
||||
let use_sci = if digits != Digits::Default
|
||||
|| den == one && (base == 2 || base == 8 || base == 16 || base == 32)
|
||||
{
|
||||
false
|
||||
} else {
|
||||
intdigits + zeros > 9 * 10 / base as u32
|
||||
};
|
||||
let placed_ints = n >= intdigits;
|
||||
let ndigits = match digits {
|
||||
Digits::Default | Digits::FullInt => 6,
|
||||
Digits::Digits(n) => intdigits as i32 + n as i32,
|
||||
};
|
||||
let bail = (exact && (placed_ints || use_sci))
|
||||
|| (n as i32 - zeros as i32 > ndigits && use_sci)
|
||||
|| n as i32 - zeros as i32 > ::std::cmp::max(intdigits as i32, ndigits);
|
||||
if bail && use_sci {
|
||||
// scientific notation
|
||||
let off = if n < intdigits { 0 } else { zeros };
|
||||
buf = buf[off as usize + placed_decimal as usize + sign as usize..].to_owned();
|
||||
buf.insert(1, '.');
|
||||
if buf.len() == 2 {
|
||||
buf.insert(2, '0');
|
||||
}
|
||||
if sign {
|
||||
buf.insert(0, '-');
|
||||
}
|
||||
buf.push_str(&*format!("e{}", intdigits as i32 - zeros as i32 - 1));
|
||||
return (exact, buf);
|
||||
}
|
||||
if bail {
|
||||
return (exact, buf);
|
||||
}
|
||||
if n == intdigits {
|
||||
buf.push('.');
|
||||
placed_decimal = true;
|
||||
}
|
||||
let digit = &(&(&cursor.numer() * &ten) / &cursor.denom()) % &ten;
|
||||
let v: Option<i64> = digit.as_int();
|
||||
let v = v.unwrap();
|
||||
if v != 0 {
|
||||
only_zeros = false
|
||||
} else if only_zeros {
|
||||
zeros += 1;
|
||||
}
|
||||
if !(v == 0 && only_zeros && n < intdigits - 1) {
|
||||
buf.push(from_digit(v as u32, base as u32).unwrap());
|
||||
}
|
||||
cursor = &cursor * &ten_rational;
|
||||
cursor = &cursor - &BigRat::ratio(&digit, &one);
|
||||
n += 1;
|
||||
_ => BigRat::from(value).to_string(base, digits),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,3 +237,127 @@ impl<'a> Neg for &'a Numeric {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{output::Digits, types::Numeric};
|
||||
|
||||
#[test]
|
||||
fn test_tostring_simple() {
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 1).to_string(10, Digits::Default),
|
||||
(true, "1".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 2).to_string(10, Digits::Default),
|
||||
(true, "0.5".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 8).to_string(10, Digits::Default),
|
||||
(true, "0.125".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(7, 8).to_string(10, Digits::Default),
|
||||
(true, "0.875".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(123456, 100).to_string(10, Digits::Default),
|
||||
(true, "1234.56".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recurring_fraction() {
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 3).to_string(10, Digits::Default),
|
||||
(true, "0.[3]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(2, 3).to_string(10, Digits::Default),
|
||||
(true, "0.[6]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 7).to_string(10, Digits::Default),
|
||||
(true, "0.[142857]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1000, 3).to_string(10, Digits::Default),
|
||||
(true, "333.[3]...".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exponent() {
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1_000_000_000_000_000i64, 1).to_string(10, Digits::Default),
|
||||
(true, "1.0e15".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1_000_000_000_000_000i64, 3).to_string(10, Digits::Default),
|
||||
(true, "3.[3]...e14".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negatives() {
|
||||
assert_eq!(
|
||||
Numeric::from_frac(-123, 1).to_string(10, Digits::Default),
|
||||
(true, "-123".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(-1000, 3).to_string(10, Digits::Default),
|
||||
(true, "-333.[3]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(-1_000_000_000_000_000i64, 1).to_string(10, Digits::Default),
|
||||
(true, "-1.0e15".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_base2() {
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 1).to_string(2, Digits::Default),
|
||||
(true, "1".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(2, 1).to_string(2, Digits::Default),
|
||||
(true, "10".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(3, 1).to_string(2, Digits::Default),
|
||||
(true, "11".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 2).to_string(2, Digits::Default),
|
||||
(true, "0.1".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 3).to_string(2, Digits::Default),
|
||||
(true, "0.[01]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 5).to_string(2, Digits::Default),
|
||||
(true, "0.[0011]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 7).to_string(2, Digits::Default),
|
||||
(true, "0.[001]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(1, 9).to_string(2, Digits::Default),
|
||||
(true, "0.[000111]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(-1000, 3).to_string(2, Digits::Default),
|
||||
(true, "-101001101.[01]...".to_owned())
|
||||
);
|
||||
assert_eq!(
|
||||
Numeric::from_frac(-1_000_000_000_000_000i64, 1).to_string(2, Digits::Default),
|
||||
(
|
||||
true,
|
||||
"-11100011010111111010100100110001101000000000000000".to_owned()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ fn test_sqrt_errors() {
|
|||
|
||||
#[test]
|
||||
fn test_number_regress() {
|
||||
test("953 mega", "9.53e8 (dimensionless)");
|
||||
test("953 mega", "953000000 (dimensionless)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -265,7 +265,7 @@ fn test_convert_to_substances() {
|
|||
"egg: USA large egg. \
|
||||
mass = 1 kilogram; \
|
||||
egg_shelled = 20 egg; \
|
||||
egg_white = 100/3, approx. 33.33333 egg; \
|
||||
egg_white = 33.[3]... egg; \
|
||||
egg_yolk = 5000/93, approx. 53.76344 egg",
|
||||
);
|
||||
}
|
||||
|
@ -393,7 +393,7 @@ fn test_functions() {
|
|||
|
||||
#[test]
|
||||
fn test_equal_rhs() {
|
||||
test("1 -> a=3", "1/3, approx. 0.3333333 a (dimensionless)");
|
||||
test("1 -> a=3", "0.[3]... a (dimensionless)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -520,14 +520,11 @@ fn test_digits() {
|
|||
"ln(1234) -> digits 100",
|
||||
"approx. 7.11801620446533345187845043255947530269622802734375 (dimensionless)",
|
||||
);
|
||||
test(
|
||||
"1/7 -> digits 50",
|
||||
"1/7, approx. 0.1428571428571428571428571428571428571428571428571428 (dimensionless)",
|
||||
);
|
||||
test("trillion / 7", "approx. 1.428571e11 (dimensionless)");
|
||||
test("1/7 -> digits 50", "0.[142857]... (dimensionless)");
|
||||
test("trillion / 7", "1.[428571]...e11 (dimensionless)");
|
||||
test(
|
||||
"trillion / 7 to digits",
|
||||
"approx. 142857142857.1 (dimensionless)",
|
||||
"142857142857.[142857]... (dimensionless)",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -742,7 +739,7 @@ fn test_tim() {
|
|||
// Issue #151, rink crashing due to stack overflow
|
||||
test(
|
||||
"Tim",
|
||||
"Definition: Tim = 12^-4 hour = 3125/18, approx. 173.6111 millisecond (time; s)",
|
||||
"Definition: Tim = 12^-4 hour = 173.6[1]... millisecond (time; s)",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue