refactor(voice_hybrid): Phase B language-advantage pass — dichotomy wave
The duplicated exact-decimal readers behind round3/format_percent fold
into one Decimal6 struct (single {:.6} expansion + one banker's
round_up decision); the format_percent odd-kept-digit ladder
(end_with 1/3/5/7/9) becomes int parity. Behavior identical: battery
11 green, clippy -D warnings 0, live parity 1140/1140.
This commit is contained in:
1 parent
833d7af476
commit
6c1c4fea97
1 file changed
+48
-43
@@ -208,34 +208,55 @@ const DIMENSIONS: [Dimension; 5] = [
|
||||
// an integral value to i64/u64 on the Value path. i64 appears only for the
|
||||
// `turns` counter, which the oracle keeps an int.)
|
||||
|
||||
/// A fixed 6-digit decimal expansion: the exact-decimal reader both
|
||||
/// banker's-rounding helpers share. Trailing binary noise lives past digit
|
||||
/// 12, so 6 digits capture the decision digit plus genuine nonzero tails
|
||||
/// without ever flipping a halfway case.
|
||||
struct Decimal6 {
|
||||
neg: bool,
|
||||
int_part: i64,
|
||||
frac: [u8; 6],
|
||||
}
|
||||
|
||||
impl Decimal6 {
|
||||
fn of(x: f64, scale: f64) -> Option<Self> {
|
||||
if !x.is_finite() {
|
||||
return None;
|
||||
}
|
||||
let neg = x < 0.0;
|
||||
let s = format!("{:.6}", (x * scale).abs());
|
||||
let (int_s, frac_s) = match s.split_once('.') {
|
||||
Some((i, f)) => (i, f),
|
||||
None => (s.as_str(), "000000"),
|
||||
};
|
||||
let mut frac = [b'0'; 6];
|
||||
for (i, b) in frac_s.bytes().take(6).enumerate() {
|
||||
frac[i] = b;
|
||||
}
|
||||
Some(Self { neg, int_part: int_s.parse::<i64>().unwrap_or(0), frac })
|
||||
}
|
||||
|
||||
/// Banker's decision at `pos`: round up iff the decision digit exceeds
|
||||
/// 5, or equals 5 with a nonzero tail or an odd kept digit.
|
||||
fn round_up(&self, pos: usize, kept_odd: bool) -> bool {
|
||||
let d = (self.frac[pos] - b'0') as i64;
|
||||
d > 5 || (d == 5 && (self.frac.iter().skip(pos + 1).any(|&b| b != b'0') || kept_odd))
|
||||
}
|
||||
}
|
||||
|
||||
/// Python `round(x, 3)` — banker's rounding (ties-to-even) on the decimal
|
||||
/// representation. Implemented on the exact decimal expansion so halfway
|
||||
/// cases (e.g. 0.0625 → 0.062, 0.1875 → 0.188) round the way CPython does,
|
||||
/// independent of binary-float representation noise.
|
||||
fn round3(x: f64) -> f64 {
|
||||
if !x.is_finite() {
|
||||
return x;
|
||||
}
|
||||
let neg = x < 0.0;
|
||||
// 6 decimals capture the exact binary value's decimal expansion far
|
||||
// beyond the 4th-decimal decision digit; trailing binary noise lives
|
||||
// past digit 12 and never flips a genuine halfway case.
|
||||
let s = format!("{:.6}", x.abs());
|
||||
let (int_part, frac_part) = match s.split_once('.') {
|
||||
Some((i, f)) => (i, f),
|
||||
None => (s.as_str(), "000000"),
|
||||
};
|
||||
let frac: Vec<u8> = frac_part.bytes().take(6).collect();
|
||||
// Decision digit (4th decimal) + whether any later digit is nonzero.
|
||||
let d3 = (frac[3] - b'0') as i64;
|
||||
let rest_nonzero = frac.iter().skip(4).any(|&b| b != b'0');
|
||||
let round_up = d3 > 5 || (d3 == 5 && (rest_nonzero || (frac[2] - b'0') % 2 == 1));
|
||||
let mut thousandths =
|
||||
int_part.parse::<i64>().unwrap_or(0) * 1000 + (frac[0] - b'0') as i64 * 100
|
||||
+ (frac[1] - b'0') as i64 * 10
|
||||
+ (frac[2] - b'0') as i64
|
||||
+ i64::from(round_up);
|
||||
if neg {
|
||||
let Some(dec) = Decimal6::of(x, 1.0) else { return x };
|
||||
let kept_odd = (dec.frac[2] - b'0') % 2 == 1;
|
||||
let mut thousandths = dec.int_part * 1000
|
||||
+ (dec.frac[0] - b'0') as i64 * 100
|
||||
+ (dec.frac[1] - b'0') as i64 * 10
|
||||
+ (dec.frac[2] - b'0') as i64
|
||||
+ i64::from(dec.round_up(3, kept_odd));
|
||||
if dec.neg {
|
||||
thousandths = -thousandths;
|
||||
}
|
||||
thousandths as f64 / 1000.0
|
||||
@@ -246,26 +267,10 @@ fn round3(x: f64) -> f64 {
|
||||
/// 289.999...), so the decision digit is read from the exact decimal
|
||||
/// expansion exactly like `round3`.
|
||||
fn format_percent(x: f64) -> String {
|
||||
if !x.is_finite() {
|
||||
return format!("{x:.0}%");
|
||||
}
|
||||
let neg = x < 0.0;
|
||||
let scaled = (x * 100.0).abs();
|
||||
let s = format!("{scaled:.6}");
|
||||
let (int_part, frac_part) = match s.split_once('.') {
|
||||
Some((i, f)) => (i, f),
|
||||
None => (s.as_str(), "000000"),
|
||||
};
|
||||
let frac: Vec<u8> = frac_part.bytes().take(6).collect();
|
||||
let d0 = (frac[0] - b'0') as i64;
|
||||
let rest_nonzero = frac.iter().skip(1).any(|&b| b != b'0');
|
||||
let round_up = d0 > 5 || (d0 == 5 && (rest_nonzero || int_part.ends_with('1')
|
||||
|| int_part.ends_with('3')
|
||||
|| int_part.ends_with('5')
|
||||
|| int_part.ends_with('7')
|
||||
|| int_part.ends_with('9')));
|
||||
let mut whole = int_part.parse::<i64>().unwrap_or(0) + i64::from(round_up);
|
||||
if neg {
|
||||
let Some(dec) = Decimal6::of(x, 100.0) else { return format!("{x:.0}%") };
|
||||
let kept_odd = dec.int_part % 2 != 0;
|
||||
let mut whole = dec.int_part + i64::from(dec.round_up(0, kept_odd));
|
||||
if dec.neg {
|
||||
whole = -whole;
|
||||
}
|
||||
format!("{whole}%")
|
||||
|
||||
Reference in new issue
Block a user