///|
fn percent_python_integer_double(
value : @bigint.BigInt,
) -> Double raise @core.DataError {
let number = index_round_binary(value, 0)
if number.is_inf() {
raise @core.DataError::InvalidTicks(
message="OverflowError: int too large to convert to float",
)
}
number
}
///|
// Only construction evaluates the original xmax + 0.0 operation.
fn percent_xmax_normalize(
value : PercentXMax,
) -> PercentXMax raise @core.DataError {
match value {
Number(number) =>
PercentXMax::Number(if number == 0.0 { 0.0 } else { number + 0.0 })
Integer(integer) =>
PercentXMax::Number(percent_python_integer_double(integer) + 0.0)
Boolean(boolean) => PercentXMax::Number(if boolean { 1.0 } else { 0.0 })
NumpyFloat64(number) =>
PercentXMax::NumpyFloat64(if number == 0.0 { 0.0 } else { number + 0.0 })
NumpyInt64(number) => PercentXMax::NumpyFloat64(number.to_double() + 0.0)
}
}
///|
// Scalar origin is needed at zero even though all successful quotients are
// binary64. Keep the source division and following multiplication separate.
fn percent_convert_to_pct(
xmax : PercentXMax,
value : NamedFormatValue,
) -> Double raise @core.DataError {
if value is PythonInteger(integer) {
// Integer true division can be finite even when neither operand fits a
// Double. For floating xmax the helper converts the numerator first, just
// as Python does before checking floating zero division.
return percent_integer_range_to_pct(xmax, integer)
}
let denominator = match xmax {
Number(number) | NumpyFloat64(number) => number
Integer(integer) => percent_python_integer_double(integer)
Boolean(boolean) => if boolean { 1.0 } else { 0.0 }
NumpyInt64(number) => number.to_double()
}
let numpy = !(value is (PythonFloat(_) | PythonInteger(_))) ||
xmax is (PercentXMax::NumpyFloat64(_) | PercentXMax::NumpyInt64(_))
if denominator == 0.0 && !numpy {
raise @core.DataError::InvalidTicks(
message="ZeroDivisionError: float division by zero",
)
}
let quotient = named_format_value_double(value) / denominator
100.0 * quotient
}
///|
fn percent_auto_decimals(
xmax : PercentXMax,
display_range : PercentDisplayRange,
) -> Int32 raise @core.DataError {
let scaled = match display_range {
Scalar(value) => percent_convert_to_pct(xmax, value)
PythonInteger(value) => percent_integer_range_to_pct(xmax, value)
}
if scaled <= 0.0 {
return 0
}
// Preserve every source rounding, including overflow in the factor of two.
let exponent = 2.0 - asinh_math_log10(2.0 * scaled)
if exponent.is_nan() {
raise @core.DataError::InvalidTicks(
message="ValueError: cannot convert float NaN to integer",
)
}
if exponent.is_inf() {
raise @core.DataError::InvalidTicks(
message="OverflowError: cannot convert float infinity to integer",
)
}
Int32::max(0, Int32::min(5, exponent.ceil().to_int()))
}
///|
// Escaping only adds one unit per ORIGINAL reserved ASCII character. Checking
// this size reserves symbol space without materializing an oversized symbol.
fn percent_symbol_units(state : PercentFormatterState) -> Int64 {
let symbol = state.symbol.unwrap_or("")
let mut units = Int64::from_int(symbol.length())
if !state.is_latex && @config.text_usetex() {
for character in symbol {
if character
is ('\\' | '#' | '$' | '%' | '&' | '~' | '_' | '^' | '{' | '}') {
units += 1L
}
}
}
units
}
///|
fn percent_format_pct(
state : PercentFormatterState,
value : NamedFormatValue,
display_range : NamedFormatValue,
) -> String raise @core.DataError {
percent_format_pct_with_range(
state,
value,
PercentDisplayRange::Scalar(display_range),
)
}
///|
priv enum PercentDisplayRange {
Scalar(NamedFormatValue)
PythonInteger(@bigint.BigInt)
}
///|
// Python integer values and axis intervals retain integer true division with a
// raw Integer/Boolean xmax. Huge operands can have a finite quotient without
// either operand being representable as a Double.
fn percent_integer_range_to_pct(
xmax : PercentXMax,
numerator : @bigint.BigInt,
) -> Double raise @core.DataError {
let denominator = match xmax {
Integer(integer) => Some(integer)
Boolean(boolean) =>
Some(@bigint.BigInt::from_int(if boolean { 1 } else { 0 }))
_ => None
}
match denominator {
None =>
percent_convert_to_pct(
xmax,
PythonFloat(percent_python_integer_double(numerator)),
)
Some(denominator) => {
if denominator.is_zero() {
raise @core.DataError::InvalidTicks(
message="ZeroDivisionError: division by zero",
)
}
let quotient = log_grid_integer_ratio(numerator, denominator) catch {
@core.DataError::InvalidTicks(
message="log grid integer ratio overflows float"
) =>
raise @core.DataError::InvalidTicks(
message="OverflowError: integer division result too large for a float",
)
error => raise error
}
100.0 * quotient
}
}
}
///|
fn percent_format_pct_with_range(
state : PercentFormatterState,
value : NamedFormatValue,
display_range : PercentDisplayRange,
) -> String raise @core.DataError {
let converted = percent_convert_to_pct(state.xmax, value)
let decimals = match state.decimals {
Automatic => percent_auto_decimals(state.xmax, display_range)
explicit => percent_explicit_decimals(explicit)
}
// Source precision conversion/grammar errors precede symbol handling. The
// symbol getter is pure in this typed model; reserve its exact size only now.
let symbol_units = percent_symbol_units(state)
let number = named_format_float_digits(
converted,
'f',
Some(decimals),
false,
available=2147483647L - symbol_units,
)
number + percent_symbol(state)
}