Skip to content

Commit

Permalink
Let some units drop preceding space, add PERCENTAGE_SPACE variable, a…
Browse files Browse the repository at this point in the history
…dd STRICT_SI variable, make AUTO_NORMALIZE consequential. (#47)
  • Loading branch information
matterhorn103 authored Jun 6, 2024
1 parent 9865818 commit 12df336
Show file tree
Hide file tree
Showing 15 changed files with 130 additions and 110 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ Quantity(4, (unitless))
>>> 2**(a/b)
Quantity(16, (unitless))
>>> (a/b).sqrt()
Quantity(2.000000000000000000000000000, (unitless))
Quantity(2, (unitless))
>>> (a/b).exp()
Quantity(54.59815003314423907811026120, (unitless))
>>> (a/b).ln()
Expand Down
29 changes: 20 additions & 9 deletions quanstants/abstract_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ class AbstractQuantity(metaclass=ABCMeta):
a single string is to call `Quantity.parse(string)`.
"""

__slots__ = ("_number", "_unit", "_uncertainty", "_value", "_pending_cancel")
__slots__ = ("_number", "_unit", "_uncertainty", "_value", "_pending_cancel", "_is_normalized")

def __init__(
self,
number: str | int | float | dec | None = None,
unit=None,
uncertainty: str | int | float | dec | Self | None = None,
_pending_cancel: bool = False,
):

# Did extensive timings for this, shown in comments in ns in format:
Expand Down Expand Up @@ -100,23 +101,31 @@ def __init__(
else:
self._uncertainty = dec(uncertainty)

if not hasattr(self, "_pending_cancel"):
self._pending_cancel = False
# Variable that indicates unit needs cancelling but is initially uncancelled
self._pending_cancel = _pending_cancel
# Variable to indicate normalization should be skipped
self._is_normalized = False


# These properties should be overridden by subclasses, but unfortunately it is no
# longer possible to create abstract properties

@property
def number(self) -> dec:
if quanfig.AUTO_NORMALIZE and not self._is_normalized:
if str(self._number)[-quanfig.AUTO_NORMALIZE:] == "0" * quanfig.AUTO_NORMALIZE:
# We can just normalize in place in this case as it doesn't change the
# value or the hash of either the number or the Quantity itself
self._number = self._number.normalize()
self._is_normalized = True
return self._number

@property
def unit(self):
return self._unit

@property
def uncertainty(self) -> Self:
def uncertainty(self):
return self._uncertainty

@property
Expand Down Expand Up @@ -280,7 +289,7 @@ def round_to_places(self, ndigits=None, pad=None, mode=None) -> Self:
number=rounding.to_places(self.number, ndigits, pad, mode),
unit=self._unit,
uncertainty=self._uncertainty,
pending_cancel=self._pending_cancel,
_pending_cancel=self._pending_cancel,
)
return rounded

Expand All @@ -299,7 +308,7 @@ def round_to_figures(self, ndigits=None, pad=None, mode=None) -> Self:
number=rounding.to_figures(self.number, ndigits, pad, mode),
unit=self._unit,
uncertainty=self._uncertainty,
pending_cancel=self._pending_cancel,
_pending_cancel=self._pending_cancel,
)

def round_to_sigfigs(self, *args, **kwargs) -> Self:
Expand Down Expand Up @@ -369,7 +378,7 @@ def with_uncertainty(self, uncertainty) -> Self:
number=self.number,
unit=self._unit,
uncertainty=uncertainty,
pending_cancel=self._pending_cancel,
_pending_cancel=self._pending_cancel,
)

def plus_minus(self, uncertainty) -> Self:
Expand All @@ -386,9 +395,11 @@ def normalize(self, threshold: int = 0) -> Self:
If a threshold is provided, only numbers with more trailing zeroes than the
threshold will be normalized.
"""
return type(self)(
normalized = type(self)(
rounding.normalize(self.number, threshold),
self._unit,
rounding.normalize(self._uncertainty, threshold),
pending_cancel=self._pending_cancel,
_pending_cancel=self._pending_cancel,
)
normalized._is_normalized = True
return normalized
4 changes: 4 additions & 0 deletions quanstants/abstract_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AbstractUnit(metaclass=ABCMeta):
"_symbol",
"_name",
"_alt_names",
"_preceding_space",
)

def __init__(
Expand All @@ -30,6 +31,7 @@ def __init__(
alt_names: list[str] | None = None,
add_to_namespace: bool = False,
canon_symbol: bool = False,
preceding_space: bool = True,
):
self._symbol = symbol
if symbol is None:
Expand All @@ -39,6 +41,8 @@ def __init__(
self._alt_names = tuple(alt_names) if alt_names is not None else None
if add_to_namespace:
self.add_to_namespace(add_symbol=canon_symbol)
# Quantities need to know if the unit should be preceded by a space or not
self._preceding_space = preceding_space

# If None was passed for the symbol, it is usually done by a subclass so that it can
# be evaluated lazily
Expand Down
14 changes: 11 additions & 3 deletions quanstants/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,18 @@ def __setattr__(self, name, value):
else:
# Some options need custom handling
if name == "ASCII_ONLY":
self.UNICODE_SUPERSCRIPTS = value
if value is True:
self.UNICODE_SUPERSCRIPTS = False
if name == "PRETTYPRINT":
self.UNICODE_SUPERSCRIPTS = value
self.GROUP_DIGITS = value
if value is True:
self.UNICODE_SUPERSCRIPTS = value
if value is False:
self.GROUP_DIGITS = 0
if name == "STRICT_SI":
if value is True:
if self.LOGARITHMIC_UNIT_STYLE == "SUFFIX":
self.LOGARITHMIC_UNIT_STYLE = "REFERENCE"
self.PERCENTAGE_SPACE = True
if name == "LITRE_SYMBOL":
# Don't recreate litre/derivatives, just empty their symbol cache
from . import units
Expand Down
19 changes: 14 additions & 5 deletions quanstants/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ default = 10
doc = """The number of trailing zeroes required before a result is automatically
normalized.
NOTE:
NOT CURRENTLY IMPLEMENTED, EXCEPT FOR Quantity.normalize() AND UNCERTAINTY CALCULATIONS
This setting ensures that exact results are presented exactly; for example, that
(100 m / 25 m).sqrt() returns Quantity(2, (unitless)), not
Quantity(2.000000000000000000000000000, (unitless)).
Expand All @@ -77,14 +74,14 @@ doc = """Whether to only use ASCII characters or allow all Unicode characters.
Unlike the other printing settings, this affects not only str() and print() but also
the strings produced by repr().
Changing this setting also changes various of the other [printing] settings to match."""
Changing this setting also changes various other [printing] settings."""

[config.printing.PRETTYPRINT]
choices = [ true, false ]
default = true
doc = """Whether to format printed strings in a nice way.
Changing this setting also changes various of the other [printing] settings to match."""
Changing this setting also changes various other [printing] settings."""

[config.printing.ROUND_BEFORE_PRINT]
choices = [ true, false ]
Expand Down Expand Up @@ -155,6 +152,18 @@ A normal space \" \" would be the alternative if only ASCII characters are desir
A comma \",\" or a full stop \".\" are commonly used, but are discouraged by the SI.
Python itself often allows or makes use of an underscore \"_\"."""

[config.misc.STRICT_SI]
choices = [ true, false ]
default = false
doc = """Whether to strictly follow SI style or not.
Changing this setting also changes various other settings."""

[config.misc.PERCENTAGE_SPACE]
choices = [ true, false ]
default = false
doc = "Whether to put a space between a number and a following % (or promille etc.) sign."

[config.misc.LITRE_SYMBOL]
choices = [ "l", "L" ]
default = "L"
Expand Down
4 changes: 2 additions & 2 deletions quanstants/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def format_quantity(
elide_any = bool(num_ellipsis) | bool(uncert_ellipsis)
if not quantity._uncertainty:
num_string = reassemble(num_integer, num_fraction, num_ellipsis, num_exponent)
return f"{num_string} {quantity.unit}"
return f"{num_string}{quantity.unit._preceding_space*" "}{quantity.unit}"

# Under certain conditions format uncertainty in style "3.023(6) m" but only if:
# - the uncertainty has the same resolution as the number (check via the exponent,
Expand All @@ -234,7 +234,7 @@ def format_quantity(
if num_exponent:
num_exponent = "E" + num_exponent
# Insert uncertainty before exponent
return f"{num_string}{bracketed_uncert}{num_exponent} {quantity.unit}"
return f"{num_string}{bracketed_uncert}{num_exponent}{quantity.unit._preceding_space*" "}{quantity.unit}"

else:
num_string = reassemble(num_integer, num_fraction, num_ellipsis, num_exponent)
Expand Down
11 changes: 2 additions & 9 deletions quanstants/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ def __init__(
number=number,
unit=unit,
uncertainty=None,
**kwargs,
)
# Assuming no value was provided, overwrite now
# The value of a logarithmic quantity should always be stored as an absolute
Expand All @@ -316,15 +317,7 @@ def __init__(
self._uncertainty = dec(0) if uncertainty is None or uncertainty == 0 else uncertainty

@property
def number(self):
return self._number

@property
def unit(self):
return self._unit

@property
def uncertainty(self):
def uncertainty(self) -> Quantity:
return self._uncertainty

@property
Expand Down
Loading

0 comments on commit 12df336

Please sign in to comment.