difftreelog
fix float formatting precision for values > u64::MAX
in: master
2 files changed
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth47 "jrsonnet-ir-parser?/exp-object-iteration",47 "jrsonnet-ir-parser?/exp-object-iteration",48]48]49# Bigint type49# Bigint type50exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]50exp-bigint = ["jrsonnet-types/exp-bigint"]51# obj?.field, obj?.['field']51# obj?.field, obj?.['field']52exp-null-coaelse = [52exp-null-coaelse = [53 "jrsonnet-peg-parser?/exp-null-coaelse",53 "jrsonnet-peg-parser?/exp-null-coaelse",78# Better explaining traces78# Better explaining traces79hi-doc = { workspace = true, optional = true }79hi-doc = { workspace = true, optional = true }80# Bigint80# Bigint81num-bigint = { workspace = true, features = ["serde"], optional = true }81num-bigint = { workspace = true, features = ["serde"] }828283stacker.workspace = true83stacker.workspace = true84educe = { workspace = true, features = [84educe = { workspace = true, features = [crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth6 reason = "many safe integer casts, behavior on overflow is not specified"6 reason = "many safe integer casts, behavior on overflow is not specified"7)]7)]89use std::fmt::Write as _;8109use jrsonnet_gcmodule::Trace;11use jrsonnet_gcmodule::Trace;10use jrsonnet_interner::IStr;12use jrsonnet_interner::IStr;11use jrsonnet_types::ValType;13use jrsonnet_types::ValType;14use num_bigint::BigUint;12use thiserror::Error;15use thiserror::Error;131614use crate::{17use crate::{300303301const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";304const NUMBERS: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";305306#[allow(clippy::cast_precision_loss, reason = "fits exactly")]307const TWO_POW_64: f64 = (1u128 << 64) as f64;302308303#[inline]309#[inline]304#[allow(clippy::fn_params_excessive_bools)]310#[allow(clippy::fn_params_excessive_bools)]316 caps: bool,322 caps: bool,317) {323) {318 debug_assert!(iv >= 0.0, "render_integer receives sign using arg");324 debug_assert!(iv >= 0.0, "render_integer receives sign using arg");319 let iv = iv.floor() as i64;325 let iv = iv.floor();320 // Digit char indexes in reverse order, i.e326 // Digit char indexes in reverse order, i.e321 // for radix = 16 and n = 12f: [15, 2, 1]327 // for radix = 16 and n = 12f: [15, 2, 1]322 let digits = if iv == 0 {328 let digits = if iv == 0.0 {323 vec![0u8]329 vec![0u8]324 } else {330 } else if iv < TWO_POW_64 {325 let mut v = iv.abs();331 let mut v = iv as u64;332 let radix = radix as u64;326 let mut nums = Vec::with_capacity(1);333 let mut nums = Vec::with_capacity(1);327 while v != 0 {334 while v != 0 {328 nums.push((v % radix) as u8);335 nums.push((v % radix) as u8);329 v /= radix;336 v /= radix;330 }337 }331 nums338 nums332 };339 } else {340 let (mantissa, exp) = decompose_f64(iv);341 let mut v = BigUint::from(mantissa) << u32::try_from(exp).expect("huge f64 exp fits u32");342 let radix = BigUint::from(radix as u64);343 let mut nums = Vec::new();344 while v != BigUint::ZERO {345 nums.push(u8::try_from(&v % &radix).expect("digit < radix"));346 v /= &radix;347 }348 nums349 };333 #[allow(clippy::bool_to_int_with_if)]350 #[allow(clippy::bool_to_int_with_if)]334 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });351 let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });335352348 }365 }349366350 out.reserve(zp2 as usize);367 out.reserve(zp2 as usize);351 if iv != 0 {368 if iv != 0.0 {352 out.push_str(zero_prefix);369 out.push_str(zero_prefix);353 }370 }354 for _ in 0..zp2 {371 for _ in 0..zp2 {441 ensure_pt: bool,458 ensure_pt: bool,442 trailing: bool,459 trailing: bool,443) {460) {444 // Represent the rounded number as an integer * 1/10**prec.445 // Note that it can also be equal to 10**prec and we'll need to carry446 // over to the wholes. We operate on the absolute numbers, so that we447 // don't have trouble with the rounding direction.448 let denominator = 10.0f64.powi(i32::from(precision));449 let numerator = n.abs().mul_add(denominator, 0.5);461 let abs = n.abs();450 let whole = (numerator / denominator).floor();462 let (frac, carry) = round_frac(abs, precision);451 let frac = numerator.floor() % denominator;463 let whole = if carry {464 abs.floor() + 1.0465 } else {466 abs.floor()467 };452468453 #[allow(clippy::bool_to_int_with_if)]469 #[allow(clippy::bool_to_int_with_if)]454 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };470 let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };460 }477 }461 return;478 return;462 }479 }463 if trailing || frac > 0.0 {480481 // For %g (trailing == false) trailing zeros are then trimmed in place482 let dot_at = out.len();464 out.push('.');483 out.push('.');465 let mut frac_str = String::new();484 let digits_at = out.len();466 render_decimal(&mut frac_str, false, frac, precision, 0, false, false);485 frac.write_padded(out, precision);486 if !trailing {467 let mut trim = frac_str.len();487 let kept = digits_at + out[digits_at..].trim_end_matches('0').len();468 if !trailing {469 for b in frac_str.as_bytes().iter().rev() {470 if *b == b'0' {471 trim -= 1;472 } else {473 break;474 }475 }476 }477 out.push_str(&frac_str[..trim]);488 out.truncate(kept);478 } else if ensure_pt {489 if out.len() == digits_at && !ensure_pt {479 out.push('.');490 out.truncate(dot_at);480 }491 }492 }481}493}494495fn decompose_f64(n: f64) -> (u64, i32) {496 const BITS: u32 = f64::MANTISSA_DIGITS - 1;497 const BIAS: i32 = 1023;498499 let pattern = n.to_bits();500 let exp_field = ((pattern >> BITS) & 0x7ff) as i32;501 let mantissa = pattern & ((1u64 << BITS) - 1);502 if exp_field == 0 {503 (mantissa, 1 - BIAS - BITS.cast_signed())504 } else {505 (506 mantissa | (1u64 << BITS),507 exp_field - BIAS - BITS.cast_signed(),508 )509 }510}511512enum Frac {513 Small(u128),514 Big(BigUint),515}516impl Frac {517 fn write_padded(&self, out: &mut String, width: u16) {518 let width = usize::from(width);519 match self {520 Self::Small(v) => write!(out, "{v:0width$}"),521 Self::Big(v) => write!(out, "{v:0width$}"),522 }523 .expect("writing to a String never fails");524 }525}526527/// bool signifies carry, e.g 0.999 at precision 2 results in 1.000, thus carry.528// floor((2 * low_bits * 10**prec + 2**k) / 2**(k+1))529fn round_frac(abs: f64, precision: u16) -> (Frac, bool) {530 let prec = usize::from(precision);531 let (mantissa, exp) = decompose_f64(abs);532 if exp >= 0 {533 // abs is integral: no fractional bits.534 return (Frac::Small(0), false);535 }536 let k = u32::try_from(-exp).expect("exp fits u32");537 // Fractional part is exactly `low_bits / 2**k` (mantissa is 53 bits).538 let low_bits = if k >= 64 {539 mantissa540 } else {541 mantissa & ((1u64 << k) - 1)542 };543 if low_bits == 0 {544 return (Frac::Small(0), false);545 }546 round_frac_u128(low_bits, k, prec).unwrap_or_else(|| round_frac_big(low_bits, k, prec))547}548549fn round_frac_u128(low_bits: u64, k: u32, prec: usize) -> Option<(Frac, bool)> {550 if k > 126 {551 return None;552 }553 let mut ten_pow = 1u128;554 for _ in 0..prec {555 ten_pow = ten_pow.checked_mul(10)?;556 }557 let numerator = u128::from(low_bits)558 .checked_mul(ten_pow)?559 .checked_mul(2)?560 .checked_add(1u128 << k)?;561 let frac = numerator >> (k + 1);562 Some(if frac == ten_pow {563 (Frac::Small(0), true)564 } else {565 (Frac::Small(frac), false)566 })567}568569fn round_frac_big(low_bits: u64, k: u32, prec: usize) -> (Frac, bool) {570 let mut ten_pow = BigUint::from(1u32);571 let ten = BigUint::from(10u32);572 for _ in 0..prec {573 ten_pow *= &ten;574 }575 let numerator = ((BigUint::from(low_bits) * &ten_pow) << 1u32) + (BigUint::from(1u32) << k);576 let frac = numerator >> (k + 1);577 if frac == ten_pow {578 (Frac::Small(0), true)579 } else {580 (Frac::Big(frac), false)581 }582}482583483#[allow(clippy::fn_params_excessive_bools)]584#[allow(clippy::fn_params_excessive_bools)]484pub fn render_float_sci(585pub fn render_float_sci(