Source code for scientific_spinbox.backend.utils
from typing import List
from decimal import Decimal, ROUND_UP, setcontext
from functools import partial
from scientific_spinbox.backend.interfaces import PintInterface
from scientific_spinbox.validators import ScientificInputValidator
[docs]
def quantityChangeUnitFormatter(backend: PintInterface,
input_validator: ScientificInputValidator,
default_decimals: int,
unit_separator: str,
old_quantity: PintInterface.QuantityType,
new_quantity: PintInterface.QuantityType) -> PintInterface.QuantityType:
"""
Formats `new_quantity` to match the decimal precision of `old_quantity`.
Args:
backend (PintInterface): Interface for unit and quantity operations.
input_validator (ScientificInputValidator): Validator for parsing numeric values.
default_decimals (int): Default decimal places if `old_quantity` has none.
unit_separator (str): Text separator for units.
old_quantity (PintInterface.QuantityType): Quantity to determine initial precision.
new_quantity (PintInterface.QuantityType): Quantity to reformat.
Returns:
PintInterface.QuantityType: Formatted `new_quantity` with matched decimal precision.
"""
setcontext(backend.decimalsContext)
# Gets decimal places of original quantity
old_quantity_text = backend.quantityTextRepr(old_quantity, unit_separator)
old_captured = input_validator.getCapturedGroups(old_quantity_text)
old_real_text = f"{Decimal(old_captured.scientificRepresentation):f}"
old_decimals = len(input_validator.getCapturedGroups(old_real_text).decimalNumbers)
# Creates a Decimal only to work with decimal places
old_unit = backend.getQuantityUnitStr(old_quantity)
new_unit = backend.getQuantityUnitStr(new_quantity)
if old_decimals is None or old_decimals == 0:
old_decimals = default_decimals
control_places = Decimal("0." + '0' * (old_decimals - 1) + '1')
control_quantity = backend.quantityFromDecimal(control_places, old_unit)
new_control_quantity = backend.changeQuantityUnit(control_quantity, new_unit)
new_control_places = Decimal(f"{backend.getQuantityValueNumeric(new_control_quantity):f}")
captured_control = input_validator.getCapturedGroups(f"{new_control_places:f}")
new_decimals = len(captured_control.decimalNumbers)
#_logger.debug(f"{captured_control=}, {new_decimals=}, {new_control_places=}")
if new_decimals is not None and new_decimals == 0:
new_decimals = default_decimals
if new_decimals:
# Formats the new_quantity
new_quantity_text = backend.quantityTextRepr(new_quantity, unit_separator)
new_captured = input_validator.getCapturedGroups(new_quantity_text)
new_scientific = new_captured.scientificRepresentation
formatted_scientific = f"{Decimal(new_scientific).quantize(new_control_places, ROUND_UP):f}"
formatted_quantity = backend.quantityFromText(formatted_scientific, new_unit)
#_logger.debug(f"{new_decimals=}, {old_quantity_text=}, {new_control_quantity=}, {new_control_places=}, {new_scientific=}, {formatted_quantity=}")
return formatted_quantity
else:
return new_quantity
[docs]
def convert_to_nearest_preferred_unit(
quantity: PintInterface.QuantityType,
preferred_units: List[str],
unit_separator: str,
default_decimals: int,
validator: ScientificInputValidator,
interface: PintInterface
) -> PintInterface.QuantityType:
"""
Converts a PintInterface's quantity to the nearest preferred unit.
Args:
quantity (PintInterface.QuantityType): quantity object.
preferred_units (list[str]): list of preferred units.
unit_separator (str): unit separator.
validator (ScientificInputValidator): validator object.
interface (PintInterface): interface object.
Returns:
PintInterface.QuantityType: converted quantity object.
"""
selected_quantity = quantity
# Formatter setup
mockedQuantityChangeUnitFormatter = partial(
quantityChangeUnitFormatter,
interface,
validator,
default_decimals,
unit_separator
)
# Populate the data list with converted quantities and their integer/decimal sizes
data = []
for unit in preferred_units:
converted_quantity = interface.changeQuantityUnit(
quantity, unit, formatter=mockedQuantityChangeUnitFormatter
)
captured = validator.getCapturedGroups(
interface.quantityTextRepr(
converted_quantity,
unit_separator,
normalize=True,
formatter=lambda x: f"{x:f}"
)
)
integer_part = captured.integerNumbers
decimal_part = captured.decimalNumbers
data.append({
'integer_size': len(integer_part) if integer_part and int(integer_part) != 0 else 0,
'decimal_size': len(decimal_part) if decimal_part and int(decimal_part) != 0 else 0,
'converted_quantity': converted_quantity
})
# Filter for entries with non-zero integer size and sort by integer size
nonzero_sorted_int = sorted(
(item for item in data if item['integer_size'] > 0),
key=lambda x: x['integer_size']
)
if nonzero_sorted_int:
# Use the smallest integer size if available
selected_quantity = nonzero_sorted_int[0]['converted_quantity']
else:
# If all integer sizes are zero, sort by decimal size
nonzero_sorted_decimal = sorted(
(item for item in data if item['decimal_size'] > 0),
key=lambda x: x['decimal_size']
)
selected_quantity = (
nonzero_sorted_decimal[0]['converted_quantity']
if nonzero_sorted_decimal else quantity
)
# Return the quantity in the selected unit
return interface.changeQuantityUnit(quantity, interface.getQuantityUnitStr(selected_quantity))