Thanks to visit codestin.com
Credit goes to github.com

Skip to content

"Convert To Number" should use decimal.Decimal #5409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ilfirin-ms opened this issue Apr 22, 2025 · 5 comments
Open

"Convert To Number" should use decimal.Decimal #5409

ilfirin-ms opened this issue Apr 22, 2025 · 5 comments

Comments

@ilfirin-ms
Copy link

"Convert To Number" should use decimal, instead of documenting float precision problem.

    def _convert_to_number(self, item, precision=None):
        number = self._convert_to_number_without_precision(item)
        if precision is not None:
            number = float(round(number, self._convert_to_integer(precision)))
        return number

could be substituted by (not thoroughly tested, can do, if there is demand)

def _remove_exponent(self, dec):
    "Normalize decimal number"
    return dec.quantize(Decimal(1)) if dec == dec.to_integral() else dec.normalize()

def _convert_to_number(self, item, precision=None):
    if isinstance(item, Decimal):
        parsed = item
    elif isinstance(item, str):
        parsed = Decimal(number.strip().replace(",", "."))
    elif isinstance(item, int):
        parsed = Decimal(item)
    elif isinstance(item, float):
        parsed = Decimal(item)
        after_point_numbers = -parsed.as_tuple().exponent
        if precision and after_point_numbers > precision:
            parsed = round(parsed, precision)
    exponent = parsed.as_tuple().exponent
    if exponent > 0:
        return self._remove_exponent(parsed)
    return parsed
@pekkaklarck
Copy link
Member

I agree in general, but we cannot change the return value type from float to Decimal due to backwards compatibility reasons. We could add an argument for controlling the type and change the default type after a long deprecation period, but because floats typically work fine, I don't think that's worth the effort. There are, however, other alternatives:

  • Add an argument controlling the return value type without a plan to change the default. It could then also support other number types like Fraction and complex in the future.
  • Add a new keyword like Convert To Decimal. Easier to discover than the above, but less flexible. I don't want to add similar keywords for other number types.
  • Add a generic keyword for converting to any type Robot's argument conversion supports.

@ilfirin-ms
Copy link
Author

I ended up using Decimals out of necessity:

  1. Need to prevail number as it was. For example, voltmeter value 3.10000 and 3.1 is not the same. First is pretty accurate, second not so much. For accurate report, I must not loose these zeroes, I have to log it as it was. Workaround is log raw string value, convert to float and compute with it, but you don't need to with Decimals.
  2. Same problem. If device said 0.3 and I subtract 0.2, I need to have 0.1 as result. Again test_report/measure protocol look weird if result is 0.09999999999999998. Rounding is solution (if you know/remember to which level you want to round), but solution to problem, which didn't need to exist at first place.

I am aware, it's slower and more code than float, yet, especially in testing/measuring, rather slow and right.

@pekkaklarck
Copy link
Member

I'm aware of benefits of using Decimal, but it doesn't change the fact that we cannot change the default return value. Do you have opinions about the alternatives I proposed?

@ilfirin-ms
Copy link
Author

Add an argument controlling the return value type without a plan to change the default. It could then also support other number types like Fraction and complex in the future.

This I found most plausible, some "type=Decimal" keyword argument . Convert To Decimal and other would pollute keywords.
I would like still fight about default but this will do nicely too.
Maybe later implement some "Global Config" for these situations/backward compatibility and such, issues?

@pekkaklarck
Copy link
Member

Ok, let's implement type argument then. Implementation should use our type conversion logic and could be something as simple as this:

return TypeInfo.from_type_hint(type).convert(value)

The above implementation would happily convert to any type, though, and we probably wanted this to always return a number. Instead of checking the type, I believe we should check the return value with something like this:

result = TypeInfo.from_type_hint(type).convert(value)
if not isinstance(result, Number):    # `from numbers import Number` needed for this to work
    raise TypeError(...)
return result

Using the existing type conversion logic would be simple and would also automatically support all numeric types the conversion logic supports. At the moment it means int, float and Decimal, but I believe it would be a good idea to add converters also to Fraction and possibly also to complex. These would require their own issues, though.

Regarding to changing the default from float to Decimal, I pretty strongly believe it's not worth the effort (it would need a deprecation period) and I'm not sure is Decimal even better in the common case. I doubt a global config for changing argument default values is a good idea either, but at least it's not in the scope of this issue.

Are your @ilfirin-ms interested to implement this enhancement? If yes, I can add this to RF 7.4 scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants