Issues with 'percent' units
I'm not sure why this occurs, but:
foo = pint.Quantity(20, "%") * pint.Quantity(5, "%") / pint.Quantity(1, "%")
foo
<Quantity(100.0, 'percent')>
Is not the same as:
foo = 20 * ureg.percent * 5 * ureg.percent / 1 * ureg.percent
foo
<Quantity(100.0, 'percent ** 3')>
In addition, .to_reduced_units() scales and reduces to 'dimensionless' when not combining with other units:
ratio = 20 * ureg.percent * 5
ratio
<Quantity(100, 'percent')>
ratio.to_reduced_units()
<Quantity(1.0, 'dimensionless')>
But when combining they don't 'scale' or reduce to dimensionless:
foo = ratio * 1 * ureg.meter
foo
<Quantity(100.0, 'percent * meter')>
foo.to_reduced_units()
<Quantity(100.0, 'percent * meter')>
Finally, if I define my own 'percent' units like: ureg.define("percent = 0.01 * dimensionless = % = pct")
I get errors on accessing the dimensionality of the result:
foo = 20 * ureg.percent * 5 * ureg.percent * 1 * ureg.meter
foo
<Quantity(100, 'percent ** 2 * meter')>
foo.dimensionality
Traceback (most recent call last):
File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3579, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-42-6e4a081873fb>", line 1, in <module>
foo.dimensionality
File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/quantity.py", line 354, in dimensionality
self._dimensionality = self._REGISTRY._get_dimensionality(self._units)
File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/registry.py", line 722, in _get_dimensionality
self._get_dimensionality_recurse(input_units, 1, accumulator)
File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/registry.py", line 754, in _get_dimensionality_recurse
self._get_dimensionality_recurse(reg.reference, exp2, accumulator)
File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/registry.py", line 752, in _get_dimensionality_recurse
reg = self._units[self.get_name(key)]
File "/Users/daniel/.pyenv/versions/3.10.15/lib/python3.10/collections/__init__.py", line 986, in __getitem__
return self.__missing__(key) # support subclasses that define __missing__
File "/Users/daniel/.pyenv/versions/3.10.15/lib/python3.10/collections/__init__.py", line 978, in __missing__
raise KeyError(key)
KeyError: ''
What would be the best way to have 'percent' units act as proper 1/100s of dimensionless units?
AFAICT, YOU are missing a parenthesis grouping everything after the "/" operator
On 11 August 2025 13:04:23 GMT+08:00, Daniel Fink @.***> wrote:
daniel-fink created an issue (hgrecco/pint#2205)
I'm not sure why this occurs, but:
foo = pint.Quantity(20, "%") * pint.Quantity(5, "%") / pint.Quantity(1, "%") foo <Quantity(100.0, 'percent')>Is not the same as:
foo = 20 * ureg.percent * 5 * ureg.percent / 1 * ureg.percent foo <Quantity(100.0, 'percent ** 3')>In addition,
.to_reduced_units()scales and reduces to 'dimensionless' when not combining with other units:ratio = 20 * ureg.percent * 5 ratio <Quantity(100, 'percent')> ratio.to_reduced_units() <Quantity(1.0, 'dimensionless')>But when combining they don't 'scale' or reduce to dimensionless:
foo = ratio * 1 * ureg.meter foo <Quantity(100.0, 'percent * meter')> foo.to_reduced_units() <Quantity(100.0, 'percent * meter')>Finally, if I define my own 'percent' units like:
ureg.define("percent = 0.01 * dimensionless = % = pct")I get errors on accessing the dimensionality of the result:
foo = 20 * ureg.percent * 5 * ureg.percent * 1 * ureg.meter foo <Quantity(100, 'percent ** 2 * meter')> foo.dimensionality Traceback (most recent call last): File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3579, in run_code exec(code_obj, self.user_global_ns, self.user_ns) File "<ipython-input-42-6e4a081873fb>", line 1, in <module> foo.dimensionality File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/quantity.py", line 354, in dimensionality self._dimensionality = self._REGISTRY._get_dimensionality(self._units) File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/registry.py", line 722, in _get_dimensionality self._get_dimensionality_recurse(input_units, 1, accumulator) File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/registry.py", line 754, in _get_dimensionality_recurse self._get_dimensionality_recurse(reg.reference, exp2, accumulator) File "/Volumes/Data/Projects/Rangekeeper/src/.venv/lib/python3.10/site-packages/pint/facets/plain/registry.py", line 752, in _get_dimensionality_recurse reg = self._units[self.get_name(key)] File "/Users/daniel/.pyenv/versions/3.10.15/lib/python3.10/collections/__init__.py", line 986, in __getitem__ return self.__missing__(key) # support subclasses that define __missing__ File "/Users/daniel/.pyenv/versions/3.10.15/lib/python3.10/collections/__init__.py", line 978, in __missing__ raise KeyError(key) KeyError: ''What would be the best way to have 'percent' units act as proper 1/100s of dimensionless units?
-- Reply to this email directly or view it on GitHub: https://github.com/hgrecco/pint/issues/2205 You are receiving this because you are subscribed to this thread.
Message ID: @.***>
Apologies; you are correct for that first example.
But I don't think that error is necessarily connected to the other issue (re: .to_reduced_units())?
Hi, regarding the behaviour of to_reduced_units when multiplying percent (dimensionless) and meter, I cannot help ...
But regarding the KeyError exception , I remember that using % (which happens to be a builtin operator in python) as the symbol in custom defined units was problematic in early versions of pint. Maybe it still is.
On 11 August 2025 13:47:21 GMT+08:00, Daniel Fink @.***> wrote:
daniel-fink left a comment (hgrecco/pint#2205)
Apologies; you are correct for that first example. But I don't think that error is necessarily connected to the other issue (re:
.to_reduced_units())?-- Reply to this email directly or view it on GitHub: https://github.com/hgrecco/pint/issues/2205#issuecomment-3173341003 You are receiving this because you commented.
Message ID: @.***>
percent is already defined https://github.com/hgrecco/pint/blob/88f2ff02921b6fae82185c966a865a6fa592ed12/pint/default_en.txt#L151C9-L152C19
percent = 0.01 = %
you can modify that file locally if you wish
i suspect it does not like you redefining units
the reduce units issue is difficult. there are many times you'd want to keep the dimensionless unit, eg if it were radians. Here you may want to_base_units to get meters. Any help documenting the to...units functions or improvments to them would be appreciated
Thanks @andrewgsavage Understood re: keeping dimensionless units for domain-specific calculations.
Could a solution (hack) could be defining a function to 'filter'/'exclude' specific units in a reduction...? e.g.
def to_filtered(
quantity: pint.Quantity,
exclude=("percent",),
) -> pint.Quantity:
units = quantity._units
excluded = [name for name in units if name in set(exclude)]
new_units = units.remove(excluded) if excluded else units
return quantity.to(new_units)
I think (not 100% sure) this only applies to dimensionless units in to_reduce_units. So you could add a parameter to to_reduce_units for how to deal with them - whether to remove them or keep them
To get a result that is percent * meter, you must previously have multiplied percent and meter. Instead of calling to_reduce_units on percent * meter, you could call it in a previous step, so percent becomes dimensionless, or you the auto_reduce_units option