0124
This commit is contained in:
Generated
+1
-1
@@ -3904,7 +3904,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rcalc"
|
||||
version = "0.1.21"
|
||||
version = "0.1.25"
|
||||
dependencies = [
|
||||
"slint",
|
||||
"slint-build",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rcalc"
|
||||
version = "0.1.21"
|
||||
version = "0.1.25"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+89
-3
@@ -391,7 +391,9 @@ impl Converter {
|
||||
if !ch.is_ascii_hexdigit() {
|
||||
return;
|
||||
}
|
||||
if self.draft == "0" {
|
||||
// A synced all-zero draft ("0", "0000") is a placeholder:
|
||||
// first digit replaces it instead of being swallowed.
|
||||
if self.draft.chars().all(|c| c == '0') {
|
||||
self.draft = ch.to_string();
|
||||
} else if self.draft.len() < 4 {
|
||||
self.draft.push(ch);
|
||||
@@ -478,6 +480,11 @@ impl Converter {
|
||||
if t.is_empty() || t == "-" || t == "." || t == "-." {
|
||||
return Ok(());
|
||||
}
|
||||
// Exponent being typed: `1E`, `1E+`, `1E-` — intermediate
|
||||
// state, keep the previous value without flagging an error.
|
||||
if t.ends_with('E') || t.ends_with("E+") || t.ends_with("E-") {
|
||||
return Ok(());
|
||||
}
|
||||
match t.parse::<f32>() {
|
||||
Ok(v) => {
|
||||
self.float_bits = v.to_bits();
|
||||
@@ -777,8 +784,20 @@ fn format_float(v: f32) -> String {
|
||||
"Inf".into()
|
||||
};
|
||||
}
|
||||
let s = format!("{v}");
|
||||
s
|
||||
if v == 0.0 {
|
||||
// Keep IEEE −0 visible (bits 0x8000_0000).
|
||||
return if v.is_sign_negative() {
|
||||
"-0".into()
|
||||
} else {
|
||||
"0".into()
|
||||
};
|
||||
}
|
||||
// Scientific notation for extremes: keeps f32::MAX readable and the
|
||||
// draft re-parsable (`3.4028235e38`.parse::<f32>() works).
|
||||
if v.abs() < 1e-10 || v.abs() >= 1e16 {
|
||||
return format!("{v:e}");
|
||||
}
|
||||
format!("{v}")
|
||||
}
|
||||
|
||||
/// IEEE float bytes in natural order A B C D (big-endian bit pattern),
|
||||
@@ -889,4 +908,71 @@ mod tests {
|
||||
assert_eq!(c.ascii_code(), b'A');
|
||||
assert_eq!(c.ascii_char_text(), "A");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_draft_exponent_in_progress() {
|
||||
// Typing `1E` is an intermediate state, not an error.
|
||||
let mut c = Converter::new();
|
||||
c.clear_active(); // draft "0"
|
||||
c.input_char('1');
|
||||
c.input_char('E');
|
||||
assert_eq!(c.error(), None, "typing `1E` must not flag invalid float");
|
||||
c.input_char('5');
|
||||
assert_eq!(c.error(), None);
|
||||
assert_eq!(c.float_value(), 1e5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_draft_exponent_sign_in_progress() {
|
||||
let mut c = Converter::new();
|
||||
c.clear_active(); // draft "0"
|
||||
c.input_char('1');
|
||||
c.input_char('E');
|
||||
c.input_char('-');
|
||||
assert_eq!(c.error(), None, "typing `1E-` must not flag invalid float");
|
||||
c.input_char('9');
|
||||
assert_eq!(c.error(), None);
|
||||
assert_eq!(c.float_value(), 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_zero_words() {
|
||||
// IEEE -0.0 must round-trip through the word view (0x8000 0000).
|
||||
let mut c = Converter::new();
|
||||
c.clear_active(); // draft "0", float +0
|
||||
c.input_char('-');
|
||||
c.input_char('0');
|
||||
assert_eq!(c.words(), (0x8000, 0x0000));
|
||||
assert_eq!(c.float_text(), "-0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_edit_updates_float() {
|
||||
let mut c = Converter::new();
|
||||
c.clear_active(); // float = 0
|
||||
c.set_active(ConvField::Word0);
|
||||
for ch in "3F80".chars() {
|
||||
c.input_char(ch);
|
||||
}
|
||||
assert_eq!(c.float_value(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_float_uses_scientific() {
|
||||
// f32::MAX plain-decimal display is 39 chars; exponent form is
|
||||
// readable and re-parsable by the float draft parser.
|
||||
let t = format_float(f32::MAX);
|
||||
assert!(t.contains('e'), "expected scientific notation, got {t}");
|
||||
assert_eq!(t.parse::<f32>().unwrap(), f32::MAX);
|
||||
|
||||
let t = format_float(1e-20);
|
||||
assert!(t.contains('e'), "expected scientific notation, got {t}");
|
||||
assert_eq!(t.parse::<f32>().unwrap(), 1e-20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_zero_displayed() {
|
||||
assert_eq!(format_float(-0.0), "-0");
|
||||
assert_eq!(format_float(0.0), "0");
|
||||
}
|
||||
}
|
||||
|
||||
+200
-38
@@ -77,18 +77,27 @@ impl Rational {
|
||||
Self { num: n, den: 1 }
|
||||
}
|
||||
|
||||
/// Parse a decimal literal like `12`, `-3.5`, `0.001` into an exact fraction.
|
||||
/// Parse a decimal literal like `12`, `-3.5`, `0.001`, `1.5e-9` into an
|
||||
/// exact fraction (exponent shifts by exact powers of ten).
|
||||
fn parse(s: &str) -> Result<Self, CalcError> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() || s == "." || s == "-" || s == "-." {
|
||||
return Err(CalcError::Invalid);
|
||||
}
|
||||
let neg = s.starts_with('-');
|
||||
let body = if neg { &s[1..] } else { s };
|
||||
// Split off an optional exponent: `1e-15`, `2.5E+3`.
|
||||
let (body, exp) = match s.split_once(['e', 'E']) {
|
||||
Some((b, e)) => {
|
||||
let exp: i32 = e.parse().map_err(|_| CalcError::Invalid)?;
|
||||
(b, exp)
|
||||
}
|
||||
None => (s, 0i32),
|
||||
};
|
||||
let neg = body.starts_with('-');
|
||||
let b = if neg { &body[1..] } else { body };
|
||||
|
||||
let (int_part, frac_part) = match body.split_once('.') {
|
||||
Some((a, b)) => (a, b),
|
||||
None => (body, ""),
|
||||
let (int_part, frac_part) = match b.split_once('.') {
|
||||
Some((a, c)) => (a, c),
|
||||
None => (b, ""),
|
||||
};
|
||||
if int_part.is_empty() && frac_part.is_empty() {
|
||||
return Err(CalcError::Invalid);
|
||||
@@ -107,6 +116,27 @@ impl Rational {
|
||||
.and_then(|v| v.checked_add(frac_val))
|
||||
.ok_or(CalcError::Overflow)?;
|
||||
let num = if neg { -num } else { num };
|
||||
|
||||
// Apply the exponent with exact power-of-ten arithmetic:
|
||||
// value·10^exp = num·10^exp / 10^frac_digits.
|
||||
let (num, den) = if exp >= 0 {
|
||||
let e = exp as usize;
|
||||
if e >= frac_digits {
|
||||
(
|
||||
num.checked_mul(pow10(e - frac_digits)?)
|
||||
.ok_or(CalcError::Overflow)?,
|
||||
1,
|
||||
)
|
||||
} else {
|
||||
(num, den / pow10(e)?)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
num,
|
||||
den.checked_mul(pow10((-exp) as usize)?)
|
||||
.ok_or(CalcError::Overflow)?,
|
||||
)
|
||||
};
|
||||
Self::new(num, den)
|
||||
}
|
||||
|
||||
@@ -175,9 +205,14 @@ impl Value {
|
||||
if !v.is_finite() {
|
||||
return Err(CalcError::Overflow);
|
||||
}
|
||||
// Prefer exact integer when float is (almost) integral.
|
||||
if v.fract().abs() < 1e-12 && v.abs() < 1e15 {
|
||||
return Ok(Value::Rat(Rational::from_i128(v.round() as i128)));
|
||||
if v == 0.0 {
|
||||
return Ok(Value::Rat(Rational::from_i128(0)));
|
||||
}
|
||||
// Prefer exact integer when float is (almost) integral. Never snap a
|
||||
// sub-unit magnitude to an integer: that turned 1e-16 into `0`.
|
||||
let r = v.round();
|
||||
if r != 0.0 && (v - r).abs() < 1e-12 && v.abs() < 1e15 {
|
||||
return Ok(Value::Rat(Rational::from_i128(r as i128)));
|
||||
}
|
||||
Ok(Value::Float(v))
|
||||
}
|
||||
@@ -394,7 +429,7 @@ impl Calculator {
|
||||
self.entry = d.to_string();
|
||||
} else if self.entry == "-0" {
|
||||
self.entry = format!("-{d}");
|
||||
} else if digit_count(&self.entry) < 16 {
|
||||
} else if digit_count(&self.entry) < 40 && significant_digits(&self.entry) < 16 {
|
||||
self.entry.push(d);
|
||||
}
|
||||
self.entry_value = None;
|
||||
@@ -911,6 +946,19 @@ fn tokenize(src: &str) -> Result<Vec<(Tok, Option<Value>)>, CalcError> {
|
||||
while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
|
||||
i += 1;
|
||||
}
|
||||
// Optional decimal exponent: `1e-9`, `2.5E+3`.
|
||||
if i < chars.len() && (chars[i] == 'e' || chars[i] == 'E') {
|
||||
let mut j = i + 1;
|
||||
if j < chars.len() && (chars[j] == '+' || chars[j] == '-') {
|
||||
j += 1;
|
||||
}
|
||||
if j < chars.len() && chars[j].is_ascii_digit() {
|
||||
i = j;
|
||||
while i < chars.len() && chars[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let lit: String = chars[start..i].iter().collect();
|
||||
let v = Value::from_entry(&lit)?;
|
||||
out.push((Tok::Num, Some(v)));
|
||||
@@ -984,6 +1032,15 @@ fn digit_count(s: &str) -> usize {
|
||||
s.chars().filter(|c| c.is_ascii_digit()).count()
|
||||
}
|
||||
|
||||
/// Significant digits (leading zeros don't count) — caps precision, not
|
||||
/// magnitude, so `0.0000000000000001` (1e-16) can be typed.
|
||||
fn significant_digits(s: &str) -> usize {
|
||||
s.chars()
|
||||
.filter(|c| c.is_ascii_digit())
|
||||
.skip_while(|&c| c == '0')
|
||||
.count()
|
||||
}
|
||||
|
||||
fn gcd(mut a: u128, mut b: u128) -> u128 {
|
||||
while b != 0 {
|
||||
let t = b;
|
||||
@@ -1016,36 +1073,36 @@ fn format_rational(r: Rational) -> String {
|
||||
}
|
||||
|
||||
fn terminating_decimal(r: Rational) -> Option<String> {
|
||||
// Terminating iff den = 2^a · 5^b. Digit count after the point is
|
||||
// k = max(a, b); scale num so the denominator becomes exactly 10^k.
|
||||
let mut den = r.den;
|
||||
let (mut a, mut b) = (0u32, 0u32);
|
||||
while den % 2 == 0 {
|
||||
den /= 2;
|
||||
a += 1;
|
||||
}
|
||||
while den % 5 == 0 {
|
||||
den /= 5;
|
||||
b += 1;
|
||||
}
|
||||
if den != 1 {
|
||||
return None;
|
||||
}
|
||||
// Scale denominator to a power of 10.
|
||||
let mut d = r.den;
|
||||
let mut n = r.num;
|
||||
let neg = n < 0;
|
||||
n = n.abs();
|
||||
while d % 10 != 0 {
|
||||
if d % 2 == 0 {
|
||||
d *= 5;
|
||||
n *= 5;
|
||||
} else if d % 5 == 0 {
|
||||
d *= 2;
|
||||
n *= 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
let k = a.max(b);
|
||||
let mut n = r.num.abs();
|
||||
for _ in 0..(k - a) {
|
||||
n = n.checked_mul(2)?;
|
||||
}
|
||||
for _ in 0..(k - b) {
|
||||
n = n.checked_mul(5)?;
|
||||
}
|
||||
let mut d: i128 = 1;
|
||||
for _ in 0..k {
|
||||
d = d.checked_mul(10)?;
|
||||
}
|
||||
let int_part = n / d;
|
||||
let mut frac = (n % d).to_string();
|
||||
let width = d.to_string().len().saturating_sub(1);
|
||||
while frac.len() < width {
|
||||
while frac.len() < k as usize {
|
||||
frac.insert(0, '0');
|
||||
}
|
||||
// trim trailing zeros
|
||||
@@ -1057,24 +1114,26 @@ fn terminating_decimal(r: Rational) -> Option<String> {
|
||||
} else {
|
||||
format!("{int_part}.{frac}")
|
||||
};
|
||||
Some(if neg { format!("-{body}") } else { body })
|
||||
Some(if r.num < 0 { format!("-{body}") } else { body })
|
||||
}
|
||||
|
||||
fn format_float(v: f64) -> String {
|
||||
if !v.is_finite() {
|
||||
if !v.is_finite() || v == 0.0 {
|
||||
return "0".into();
|
||||
}
|
||||
// Snap near-integers (guards any float path residue).
|
||||
if (v - v.round()).abs() < 1e-10 && v.abs() < 1e15 {
|
||||
return format!("{}", v.round() as i64);
|
||||
// Snap near-integers (guards any float path residue); never for |v| < 1,
|
||||
// otherwise tiny results like 1e-16 collapse to `0`.
|
||||
let r = v.round();
|
||||
if r != 0.0 && (v - r).abs() < 1e-10 && v.abs() < 1e15 {
|
||||
return format!("{}", r as i64);
|
||||
}
|
||||
let s = format!("{:.12}", v);
|
||||
let s = s.trim_end_matches('0').trim_end_matches('.').to_string();
|
||||
if s.is_empty() || s == "-" {
|
||||
"0".into()
|
||||
} else {
|
||||
s
|
||||
// Very small / large magnitudes: scientific notation keeps the value
|
||||
// visible and re-parseable (`Rational::parse` understands exponents).
|
||||
if v.abs() < 1e-10 || v.abs() >= 1e16 {
|
||||
return format!("{v:e}");
|
||||
}
|
||||
// Shortest representation that round-trips back to `v` exactly.
|
||||
format!("{v}")
|
||||
}
|
||||
|
||||
impl fmt::Display for Rational {
|
||||
@@ -1173,4 +1232,107 @@ mod tests {
|
||||
let r = Rational::parse("1.25").unwrap();
|
||||
assert_eq!(r, Rational::new(5, 4).unwrap());
|
||||
}
|
||||
|
||||
// --- regression tests (rounding / formatting bugs) ---
|
||||
|
||||
#[test]
|
||||
fn quarter_and_eighth_display() {
|
||||
// terminating_decimal scaled the fraction wrong: 1/4 displayed as "0.5".
|
||||
let mut c = Calculator::new();
|
||||
c.input_digit('1');
|
||||
c.set_op(Op::Div);
|
||||
c.input_digit('4');
|
||||
c.equals();
|
||||
assert_eq!(c.display(), "0.25");
|
||||
|
||||
c.clear_all();
|
||||
c.input_digit('1');
|
||||
c.set_op(Op::Div);
|
||||
c.input_digit('8');
|
||||
c.equals();
|
||||
assert_eq!(c.display(), "0.125");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_hundredths_display() {
|
||||
// 0.05 must survive exact formatting (was displayed as "0.1").
|
||||
let mut c = Calculator::new();
|
||||
c.input_digit('0');
|
||||
c.input_dot();
|
||||
c.input_digit('0');
|
||||
c.input_digit('5'); // 0.05
|
||||
c.set_op(Op::Mul);
|
||||
c.input_digit('1');
|
||||
c.equals();
|
||||
assert_eq!(c.display(), "0.05");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_reciprocal_not_zero() {
|
||||
// 1/9999999999999999 ≈ 1e-16 was snapped to "0" by format_float.
|
||||
let mut c = Calculator::new();
|
||||
for d in "9999999999999999".chars() {
|
||||
c.input_digit(d);
|
||||
}
|
||||
c.reciprocal();
|
||||
let disp = c.display();
|
||||
assert!(disp != "0", "tiny reciprocal must not display as 0, got {disp}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_result_reusable_standard() {
|
||||
// Float result in scientific notation must be re-parsable as operand.
|
||||
let mut c = Calculator::new();
|
||||
for d in "9999999999999999".chars() {
|
||||
c.input_digit(d);
|
||||
}
|
||||
c.reciprocal();
|
||||
c.set_op(Op::Mul);
|
||||
c.input_digit('2');
|
||||
c.equals();
|
||||
let disp = c.display();
|
||||
assert!(disp != "0" && disp != "Invalid input", "got {disp}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqrt_square_roundtrip_tiny() {
|
||||
// √(1e-30) = 1e-15 was snapped to 0 by Value::from_f64.
|
||||
let mut c = Calculator::new();
|
||||
c.input_dot(); // "0."
|
||||
for _ in 0..14 {
|
||||
c.input_digit('0');
|
||||
}
|
||||
c.input_digit('1'); // 0.000000000000001 = 1e-15
|
||||
c.square();
|
||||
c.sqrt();
|
||||
let disp = c.display();
|
||||
assert!(disp != "0", "sqrt(1e-30) must not display as 0, got {disp}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rational_parse_exponent() {
|
||||
// Scientific-notation entries must parse to exact fractions.
|
||||
let r = Rational::parse("1e-15").unwrap();
|
||||
assert_eq!(r, Rational::new(1, 1_000_000_000_000_000).unwrap());
|
||||
let r = Rational::parse("1.5e3").unwrap();
|
||||
assert_eq!(r, Rational::new(1500, 1).unwrap());
|
||||
let r = Rational::parse("2.5e-2").unwrap();
|
||||
assert_eq!(r, Rational::new(1, 40).unwrap());
|
||||
let r = Rational::parse("-1E+3").unwrap();
|
||||
assert_eq!(r, Rational::new(-1000, 1).unwrap());
|
||||
assert!(Rational::parse("1e2e3").is_err());
|
||||
assert!(Rational::parse("1e").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_type_one_e_minus_16() {
|
||||
// Leading zeros must not eat the 16-significant-digit budget.
|
||||
let mut c = Calculator::new();
|
||||
c.input_dot();
|
||||
for _ in 0..15 {
|
||||
c.input_digit('0');
|
||||
}
|
||||
c.input_digit('1');
|
||||
assert_eq!(c.display(), "0.0000000000000001");
|
||||
}
|
||||
}
|
||||
|
||||
+81
-10
@@ -199,7 +199,7 @@ impl Engineering {
|
||||
self.entry = d.to_string();
|
||||
} else if self.entry == "-0" {
|
||||
self.entry = format!("-{d}");
|
||||
} else if digit_count(&self.entry) < 16 {
|
||||
} else if digit_count(&self.entry) < 40 && significant_digits(&self.entry) < 16 {
|
||||
self.entry.push(d);
|
||||
}
|
||||
self.sync_expr();
|
||||
@@ -550,20 +550,31 @@ fn digit_count(s: &str) -> usize {
|
||||
s.chars().filter(|c| c.is_ascii_digit()).count()
|
||||
}
|
||||
|
||||
/// Significant digits (leading zeros don't count) — caps precision, not
|
||||
/// magnitude, so `0.0000000000000001` (1e-16) can be typed.
|
||||
fn significant_digits(s: &str) -> usize {
|
||||
s.chars()
|
||||
.filter(|c| c.is_ascii_digit())
|
||||
.skip_while(|&c| c == '0')
|
||||
.count()
|
||||
}
|
||||
|
||||
fn format_num(v: f64) -> String {
|
||||
if !v.is_finite() {
|
||||
if !v.is_finite() || v == 0.0 {
|
||||
return "0".into();
|
||||
}
|
||||
if (v - v.round()).abs() < 1e-10 && v.abs() < 1e15 {
|
||||
return format!("{}", v.round() as i64);
|
||||
// Snap near-integers; never for |v| < 1 (kept 10^−50 displayed as "0").
|
||||
let r = v.round();
|
||||
if r != 0.0 && (v - r).abs() < 1e-10 && v.abs() < 1e15 {
|
||||
return format!("{}", r as i64);
|
||||
}
|
||||
let s = format!("{:.12}", v);
|
||||
let s = s.trim_end_matches('0').trim_end_matches('.').to_string();
|
||||
if s.is_empty() || s == "-" {
|
||||
"0".into()
|
||||
} else {
|
||||
s
|
||||
// Scientific notation for extremes; `entry.parse::<f64>()` and the
|
||||
// tokenizer both accept it, so the result stays reusable.
|
||||
if v.abs() < 1e-10 || v.abs() >= 1e16 {
|
||||
return format!("{v:e}");
|
||||
}
|
||||
// Shortest round-trip representation.
|
||||
format!("{v}")
|
||||
}
|
||||
|
||||
// --- expression evaluator ---
|
||||
@@ -639,6 +650,20 @@ fn tokenize(src: &str) -> Result<Vec<Tok>, SciError> {
|
||||
while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
|
||||
i += 1;
|
||||
}
|
||||
// Optional decimal exponent: `1e-9`, `2.5E+3`. The standalone
|
||||
// constant `e` stays an Ident (needs no digit right after).
|
||||
if i < chars.len() && (chars[i] == 'e' || chars[i] == 'E') {
|
||||
let mut j = i + 1;
|
||||
if j < chars.len() && (chars[j] == '+' || chars[j] == '-') {
|
||||
j += 1;
|
||||
}
|
||||
if j < chars.len() && chars[j].is_ascii_digit() {
|
||||
i = j;
|
||||
while i < chars.len() && chars[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let lit: String = chars[start..i].iter().collect();
|
||||
let v: f64 = lit.parse().map_err(|_| SciError::Invalid)?;
|
||||
out.push(Tok::Num(v));
|
||||
@@ -858,4 +883,50 @@ mod tests {
|
||||
e.equals();
|
||||
assert_eq!(e.display(), "1024");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten_pow_negative_not_zero() {
|
||||
// 10 ^ −50 ≈ 1e-50 was snapped to "0" by format_num.
|
||||
let mut e = Engineering::new();
|
||||
e.input_digit('1');
|
||||
e.input_digit('0');
|
||||
e.set_op("^");
|
||||
e.input_digit('5');
|
||||
e.input_digit('0');
|
||||
e.negate();
|
||||
e.equals();
|
||||
let disp = e.display();
|
||||
assert!(disp != "0", "10^−50 must not display as 0, got {disp}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_float_result_continues_expression() {
|
||||
// Result in scientific notation must be re-parsable by the tokenizer.
|
||||
let mut e = Engineering::new();
|
||||
e.input_digit('1');
|
||||
e.input_digit('0');
|
||||
e.set_op("^");
|
||||
e.input_digit('1');
|
||||
e.input_digit('6');
|
||||
e.negate();
|
||||
e.equals();
|
||||
assert_eq!(e.display(), "1e-16");
|
||||
e.set_op("+");
|
||||
e.input_digit('1');
|
||||
e.equals();
|
||||
assert_eq!(e.display(), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_type_one_e_minus_16() {
|
||||
// Leading zeros must not eat the 16-significant-digit budget.
|
||||
let mut e = Engineering::new();
|
||||
e.input_digit('0');
|
||||
e.input_dot();
|
||||
for _ in 0..15 {
|
||||
e.input_digit('0');
|
||||
}
|
||||
e.input_digit('1');
|
||||
assert_eq!(e.display(), "0.0000000000000001");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,4 +699,35 @@ mod tests {
|
||||
assert_eq!(p.word_size(), WordSize::Dint);
|
||||
assert_eq!(p.dec_text().replace(' ', ""), "-1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_count_mods_word_size() {
|
||||
// Locks hardware-style semantics: shift count is taken modulo the
|
||||
// word size (1 << 100000000 → 1e8 % 64 == 0 → no shift).
|
||||
let mut p = Programmer::new();
|
||||
p.input_digit('1');
|
||||
p.set_op(ProgOp::Lsh);
|
||||
for ch in "100000000".chars() {
|
||||
p.input_digit(ch);
|
||||
}
|
||||
p.equals();
|
||||
assert_eq!(p.current_bits(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_out_of_word_gives_zero() {
|
||||
// 0xFFFF_FFFF_FFFF_FFFF Lsh 63 → only bit 0 survives at bit 63.
|
||||
let mut p = Programmer::new();
|
||||
p.set_base(Base::Hex);
|
||||
for ch in "FFFFFFFFFFFFFFFF".chars() {
|
||||
p.input_digit(ch);
|
||||
}
|
||||
p.set_op(ProgOp::Lsh);
|
||||
p.set_base(Base::Dec); // type the shift count in decimal
|
||||
p.input_digit('6');
|
||||
p.input_digit('3');
|
||||
p.equals();
|
||||
// (u64::MAX << 63) & u64::MAX = 0x8000_0000_0000_0000
|
||||
assert_eq!(p.current_bits(), 0x8000_0000_0000_0000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user