Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import re
from typing import List, Sequence, Tuple
from .agri_yield_per_hectare_types import YieldPerArea
from .units_convert import convert_units ← from units.convert ^1.0.0 · built alongside by fune
DECIMAL = re.compile(r"[0-9]+(\.[0-9]+)?")
Decimal = Tuple[int, int] # digits, scale: digits / 10^scale
def _parse(name: str, text: str, limited: bool) -> Decimal:
if not isinstance(text, str) or not DECIMAL.fullmatch(text):
raise ValueError('%s must be plain non-negative decimal text like "12.5", received "%s"' % (name, text))
whole, _, raw_fraction = text.partition(".")
fraction = raw_fraction.rstrip("0")
significant = (whole + fraction).lstrip("0")
if limited and (len(significant) > 12 or len(fraction) > 6):
raise ValueError('%s "%s" has too many digits: at most 12 significant digits and 6 decimal places' % (name, text))
return (int(significant) if significant else 0, len(fraction))
def _ratio(top: Sequence[Decimal], bottom: Sequence[Decimal]) -> Tuple[int, int]:
"""(product of top) / (product of bottom), each a decimal, as integers."""
num, den, exponent = 1, 1, 0
for digits, scale in top:
num *= digits
exponent -= scale
for digits, scale in bottom:
den *= digits
exponent += scale
if exponent > 0:
num *= 10**exponent
else:
den *= 10 ** (-exponent)
return num, den
def _to_text(fraction_pair: Tuple[int, int], decimals: int) -> str:
"""Long division, one digit at a time, rounded half up, trailing zeros trimmed."""
num, den = fraction_pair
whole, remainder = divmod(num, den)
fraction = 0
for _ in range(decimals):
remainder *= 10
fraction = fraction * 10 + remainder // den
remainder %= den
if remainder * 2 >= den:
fraction += 1
if fraction == 10**decimals:
fraction = 0
whole += 1
text = str(whole)
if decimals > 0:
shown = str(fraction).rjust(decimals, "0").rstrip("0")
if shown:
text += "." + shown
return text
def yield_per_hectare(quantity: str, area: str, area_unit: str, decimals: int) -> YieldPerArea:
"""Yield per hectare and per acre, exactly.
The area unit's size comes from units.convert as exact decimal text
(1 acre = 4046.8564224 m2), so every answer is one exact fraction rounded
once. In floats, 1.005 t from 1 ha is 1.00 t/ha to two places; here it is
1.01.
"""
q = _parse("quantity", quantity, True)
a = _parse("area", area, True)
if isinstance(decimals, bool) or not isinstance(decimals, int) or decimals < 0 or decimals > 12:
raise ValueError("decimals must be a whole number from 0 to 12, received %r" % (decimals,))
unit = _parse("unit size", convert_units("1", area_unit, "m2", 12), False)
if a[0] == 0:
raise ValueError("area must be greater than zero")
acre = _parse("acre", convert_units("1", "acre", "m2", 12), False)
hectare: Decimal = (10000, 0)
parts: List[str] = [
_to_text(_ratio([q, hectare], [a, unit]), decimals),
_to_text(_ratio([q, acre], [a, unit]), decimals),
_to_text(_ratio([a, unit], [hectare]), decimals),
_to_text(_ratio([a, unit], [acre]), decimals),
]
return YieldPerArea(per_hectare=parts[0], per_acre=parts[1], hectares=parts[2], acres=parts[3])