-
Notifications
You must be signed in to change notification settings - Fork 1
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
Manged decimal #172
Manged decimal #172
Conversation
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
@@ -0,0 +1,41 @@ | |||
from decimal import Decimal | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this affect us?
>>> from decimal import *
>>> getcontext().prec
28
Maybe test the case with more decimals?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say 28 decimals is plenty given the fact that we don't compute anything using ManagedDecimal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally speaking, most operations (formatting, scaleb etc.) are influenced by this setting (as far as I know).
A double / triple check (research) is worth around this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did some reading, added a local context with 256 decimals
|
||
assert isinstance(second_input, ManagedDecimalValue) | ||
assert second_input.is_variable | ||
assert second_input.scale == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thus, scale == 0
means variable, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed.
multiversx_sdk/abi/abi.py
Outdated
if scale == "usize": | ||
scale = 0 | ||
is_variable = True | ||
else: | ||
scale = int(scale) | ||
is_variable = False | ||
|
||
return ManagedDecimalValue(scale=scale, is_variable=is_variable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be a bit more compact: if that return object, else return object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed, done
from pathlib import Path | ||
|
||
import pytest | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the PR description, please also reference the PRs that brought the implementation in JS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
def set_payload(self, value: Any): | ||
if isinstance(value, ManagedDecimalSignedValue): | ||
if self.is_variable != value.is_variable: | ||
raise Exception("Cannot set payload! Both ManagedDecimalValues should be variable.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ManagedDecimalValues
can be managed decimal values
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
if self.is_variable: | ||
# read biguint value length in bytes | ||
big_int_size = self._unsigned_from_bytes(data[:U32_SIZE_IN_BYTES]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is value_length
a better name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed.
return value_str | ||
if len(value_str) <= self.scale: | ||
# If the value is smaller than the scale, prepend zeros | ||
value_str = "0" * (self.scale - len(value_str) + 1) + value_str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this help?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it did. refactored the method
def _unsigned_from_bytes(self, data: bytes) -> int: | ||
return int.from_bytes(data, byteorder="big", signed=False) | ||
|
||
def _convert_value_to_int(self) -> int: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this help?
>>> from decimal import Decimal
>>> int(Decimal("123.456").scaleb(3))
123456
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it did. refactored this method, as well.
@@ -0,0 +1,103 @@ | |||
import io |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments as for ManagedDecimalSignedValue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
applied the same changes here, as well.
from multiversx_sdk.abi.managed_decimal_value import ManagedDecimalValue | ||
|
||
|
||
class TestManagedDecimalValueTest: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe have a single test file to test both signed and unsigned values?
Also, maybe have some unit tests around encoding and decoding, as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now having the tests in the same file. did some encoding and decoding tests, as well.
|
||
def get_precision(self) -> int: | ||
return len(str(self._convert_value_to_int()).lstrip("0")) | ||
value_str = f"{self.value:.{self.scale}f}" | ||
return len(value_str.replace(".", "")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work always / when using other locales (i.e. USA, German etc.)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well yes, because it's locale independent since we explicitly format the string using a dot and then replacing the dot.
value_str = "0" * (self.scale - len(value_str) + 1) + value_str | ||
return f"{value_str[:-self.scale]}.{value_str[-self.scale:]}" | ||
scaled_value = self._convert_value_to_int() | ||
return f"{scaled_value / (10 ** self.scale):.{self.scale}f}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes the assumption .
is the decimals separator. Please double check if all right in all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as decided, to_string()
was removed.
scaled_value = self._convert_value_to_int() | ||
return f"{scaled_value / (10 ** self.scale):.{self.scale}f}" | ||
|
||
def get_precision(self) -> int: | ||
return len(str(self._convert_value_to_int()).lstrip("0")) | ||
value_str = f"{self.value:.{self.scale}f}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A double check is necessary here, as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be alright, as well. It behaves like the JS implementation.
@@ -0,0 +1,41 @@ | |||
from decimal import Decimal | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally speaking, most operations (formatting, scaleb etc.) are influenced by this setting (as far as I know).
A double / triple check (research) is worth around this.
else: | ||
return ManagedDecimalValue(scale=int(scale), is_variable=False) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe follow the same pattern as for ManagedDecimal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed, done
The integration test requires
localnet
. To set up a localnet, check out the documentation.The implementation was inspired by this PR.