Skip to content
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

Add more number output formats #180

Merged
merged 5 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 core/src/output/numeric_parts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ pub enum Digits {
Default,
FullInt,
Digits(u64),
Fraction,
Scientific,
Engineering,
}

#[derive(Debug, Clone, Serialize)]
Expand Down
12 changes: 12 additions & 0 deletions core/src/parsing/text_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,18 @@ pub fn parse_query(iter: &mut Iter<'_>) -> Query {
_ => Digits::FullInt,
}
}
Token::Ident(ref s) if s == "frac" || s == "fraction" || s == "ratio" => {
iter.next();
Digits::Fraction
}
Token::Ident(ref s) if s == "sci" || s == "scientific" => {
iter.next();
Digits::Scientific
}
Token::Ident(ref s) if s == "eng" || s == "engineering" => {
iter.next();
Digits::Engineering
}
_ => Digits::Default,
};
let base = match iter.peek().cloned().unwrap() {
Expand Down
35 changes: 23 additions & 12 deletions core/src/runtime/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,8 +860,16 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
value: parts,
})))
}
Query::Convert(ref top, Conversion::None, base, digits @ Digits::Digits(_))
| Query::Convert(ref top, Conversion::None, base, digits @ Digits::FullInt) => {
Query::Convert(
ref top,
Conversion::None,
base,
digits @ Digits::Digits(_)
| digits @ Digits::FullInt
| digits @ Digits::Fraction
| digits @ Digits::Scientific
| digits @ Digits::Engineering,
) => {
let top = eval_expr(ctx, top)?;
let top = match top {
Value::Number(top) => top,
Expand All @@ -873,17 +881,14 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
Digits::Default => unreachable!(),
Digits::FullInt => "digits".to_owned(),
Digits::Digits(n) => format!("{} digits", n),
Digits::Fraction => "fraction".to_owned(),
Digits::Scientific => "scientific".to_owned(),
Digits::Engineering => "engineering".to_owned(),
}
)))
}
};
let (exact, approx) = top.numeric_value(base.unwrap_or(10), digits);
let parts = NumberParts {
raw_value: Some(top.clone()),
exact_value: exact,
approx_value: approx,
..top.to_parts(ctx)
};
let parts = top.to_parts_digits(ctx, base.unwrap_or(10), digits);
Ok(QueryReply::Conversion(Box::new(ConversionReply {
value: parts,
})))
Expand Down Expand Up @@ -1056,9 +1061,15 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
which, digits
)))
}
Query::Convert(ref _expr, ref which, _base, Digits::FullInt) => Err(QueryError::generic(
format!("Conversion to digits of {} is not defined", which),
)),
Query::Convert(
ref _expr,
ref which,
_base,
Digits::FullInt | Digits::Fraction | Digits::Scientific | Digits::Engineering,
) => Err(QueryError::generic(format!(
"Conversion to digits of {} is not defined",
which
))),
Query::Factorize(ref expr) => {
let mut val = None;
if let Expr::Unit { ref name } = *expr {
Expand Down
31 changes: 23 additions & 8 deletions core/src/types/bigrat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ impl BigRat {
let exact = cursor == zero;
let placed_ints = n >= intdigits;
let ndigits = match digits {
Digits::Default => 6,
Digits::FullInt => 1000,
Digits::Default | Digits::Scientific | Digits::Engineering => 6,
Digits::FullInt | Digits::Fraction => 1000,
Digits::Digits(n) => intdigits as i32 + n as i32,
};
// Conditions for exiting:
Expand Down Expand Up @@ -221,11 +221,22 @@ impl BigRat {
self * &BigRat::ratio(&absexp, &BigInt::one())
};
let ten = BigRat::small_ratio(base as i64, 1);
let (rational, intdigits) = if rational.abs() == ten {
let (mut rational, mut intdigits) = if rational.abs() == ten {
(&rational / &ten, intdigits + 1)
} else {
(rational, intdigits)
};

if digits == Digits::Engineering {
let adjust = (intdigits % 3 + 3) % 3;
rational = &rational
* &BigRat::ratio(
&BigInt::from(base as i64).pow(adjust as u32),
&BigInt::one(),
);
intdigits -= adjust;
}

let (is_exact, mut result) = rational.to_digits_impl(base, digits);
if !result.contains('.') {
result.push('.');
Expand All @@ -241,15 +252,19 @@ impl BigRat {
return (true, "0".to_owned());
}

if digits == Digits::Fraction {
return (true, format!("{}", self));
}

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;
let can_use_sci =
(digits == Digits::Default || digits == Digits::Engineering) && !is_computer_integer;
let in_range_for_sci = &abs >= &BigRat::small_ratio(1_000_000_000, 1)
|| &abs <= &BigRat::small_ratio(1, 1_000_000_000);

if can_use_sci
&& (&abs >= &BigRat::small_ratio(1_000_000_000, 1)
|| &abs <= &BigRat::small_ratio(1, 1_000_000_000))
{
if digits == Digits::Scientific || can_use_sci && in_range_for_sci {
self.to_scientific(base, digits)
} else {
self.to_digits_impl(base, digits)
Expand Down
20 changes: 18 additions & 2 deletions core/src/types/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,14 @@ impl Number {
}
}

pub fn with_pretty_unit(&self, context: &Context) -> Number {
let unit = self.pretty_unit(context);
Number {
value: self.value.clone(),
unit,
}
}

/// Convert the units of the number from base units to display
/// units, and possibly apply SI prefixes.
pub fn prettify(&self, context: &Context) -> Number {
Expand Down Expand Up @@ -330,8 +338,16 @@ impl Number {
}

pub fn to_parts(&self, context: &Context) -> NumberParts {
let value = self.prettify(context);
let (exact, approx) = value.numeric_value(10, Digits::Default);
self.to_parts_digits(context, 10, Digits::Default)
}

pub fn to_parts_digits(&self, context: &Context, base: u8, digits: Digits) -> NumberParts {
let value = if digits == Digits::Default {
self.prettify(context)
} else {
self.with_pretty_unit(context)
};
let (exact, approx) = value.numeric_value(base, digits);

let quantity = context
.registry
Expand Down
65 changes: 65 additions & 0 deletions core/tests/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,3 +806,68 @@ fn test_bytes() {
test("100 byte^2", "6400 bit^2 (bit^2)");
test("1/byte", "0.125 / bit (bit^-1)");
}

#[test]
fn test_output_formats() {
test("surveyfoot to digits", "0.[304800609601219202438404876809753619507239014478028956057912115824231648463296926593853187706375412750825501651003302006604013208026416052832105664211328422656845313690627381254762509525019050038100076200152400, period 210]... meter (length)");
test("surveyfoot to frac", "1200/3937 meter (length)");
test("surveyfoot to sci", "approx. 3.048006e-1 meter (length)");
test("foot to frac", "381/1250 meter (length)");
test("foot to sci", "3.048e-1 meter (length)");
test("1 to frac", "1 (dimensionless)");
test("1 to sci", "1.0e0 (dimensionless)");
test("1/7 to frac", "1/7 (dimensionless)");
test("1/7 to fraction", "1/7 (dimensionless)");
test("1/7 to ratio", "1/7 (dimensionless)");
test("1/7 to sci", "1.[428571]...e-1 (dimensionless)");
test("1/7 to scientific", "1.[428571]...e-1 (dimensionless)");
test("0.5 to eng", "0.5 (dimensionless)");
test("0.5 to engineering", "0.5 (dimensionless)");

// engineering
test("1e9 to eng", "1.0e9 (dimensionless)");
test("1e10 to eng", "10.0e9 (dimensionless)");
test("1e11 to eng", "100.0e9 (dimensionless)");
test("1e12 to eng", "1.0e12 (dimensionless)");
test("1e13 to eng", "10.0e12 (dimensionless)");
test("1e14 to eng", "100.0e12 (dimensionless)");
test("1e15 to eng", "1.0e15 (dimensionless)");
test("1e16 to eng", "10.0e15 (dimensionless)");
test("1e17 to eng", "100.0e15 (dimensionless)");

test("1e-9 to eng", "1.0e-9 (dimensionless)");
test("1e-10 to eng", "100.0e-12 (dimensionless)");
test("1e-11 to eng", "10.0e-12 (dimensionless)");
test("1e-12 to eng", "1.0e-12 (dimensionless)");
test("1e-13 to eng", "100.0e-15 (dimensionless)");
test("1e-14 to eng", "10.0e-15 (dimensionless)");
test("1e-15 to eng", "1.0e-15 (dimensionless)");
}

#[test]
fn conversion_to_digit_errors() {
test(
"egg to digits",
"<1 (dimensionless) egg> to digits is not defined",
);
test(
"egg to digits 50",
"<1 (dimensionless) egg> to 50 digits is not defined",
);
test(
"egg to frac",
"<1 (dimensionless) egg> to fraction is not defined",
);
test(
"egg to sci",
"<1 (dimensionless) egg> to scientific is not defined",
);
test(
"egg to eng",
"<1 (dimensionless) egg> to engineering is not defined",
);
test(
"now to digits \"US/Pacific\"",
"Conversion to digits of US/Pacific is not defined",
);
}
18 changes: 16 additions & 2 deletions docs/rink.7.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,19 @@ recognized:
`bin`, `binary`, `base2`::
Base 2.

Digits modifier
^^^^^^^^^^^^^^^
Number representation modifiers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

> 2^128 -> digits
340282366920938463463374607431768211456 (dimensionless)
> 1/3937 -> digits
0.[000254000508001016002032004064008128016256032512065024130048260096520193040386080772161544323088646177292354584709169418338836677673355346710693421386842773685547371094742189484378968757937515875031750063500127, period 210]... (dimensionless)
> googol -> digits
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 (dimensionless)
> mass of electron -> eng
approx. 910.9383e-33 kilogram (mass)
> 3 foot -> frac
1143/1250 meter (length)

Digits modifiers are specified with `digits` optionally followed by a
number, before the base modifier and before the rest of the
Expand All @@ -403,6 +407,16 @@ using a machine-float fallback, because their results cannot be
precisely represented as finite rationals. Because of this, asking for
many digits of such numbers will also produce unsatisfying results.

`frac`, `fraction`, `ratio` are all equivalent. They will print
the rational fraction that Rink internally represents the number using.

`sci`, `scientific` will force the use of scientific notation, even for
small numbers.

`eng`, `engineering` are similar to the default format, where it uses
scientific notation only for large numbers. However, when it does use
scientific notation, it rounds down to every third power.

Units for
^^^^^^^^^

Expand Down
Loading