diff --git a/csla_binary/binary_reader.py b/csla_binary/binary_reader.py index afed110..ec5ffa0 100644 --- a/csla_binary/binary_reader.py +++ b/csla_binary/binary_reader.py @@ -14,10 +14,12 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from decimal import Decimal import io import struct from typing import List +from .decimal import int32_to_decimal from .known_types import CslaKnownTypes from .serialization_info import ChildData, FieldData, SerializationInfo @@ -112,6 +114,14 @@ class CslaBinaryReader: shift += 7 + def read_decimal(self): + length = self.read_int32() + if length != 4: + raise ValueError('Unexpected length of Decimal, expected 4, got {}'.format(length)) + + decimal_parts = [self.read_int32() for _ in range(length)] + return int32_to_decimal(decimal_parts) + def read_int32(self): # BinaryReader.ReadInt32 return struct.unpack('. + +from decimal import Decimal, getcontext +from typing import List + +def int32_to_decimal(decimal_parts: List[int]) -> Decimal: + # System.Decimal(Int32[]) + # The binary representation of a Decimal number consists of a 1-bit sign, a 96-bit integer number, and a scaling factor used to divide the integer number and specify what portion of it is a decimal fraction. The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28. + # bits is a four-element long array of 32-bit signed integers. + # bits [0], bits [1], and bits [2] contain the low, middle, and high 32 bits of the 96-bit integer number. + # bits [3] contains the scale factor and sign, and consists of following parts: + # - Bits 0 to 15, the lower word, are unused and must be zero. + # - Bits 16 to 23 must contain an exponent between 0 and 28, which indicates the power of 10 to divide the integer number. + # - Bits 24 to 30 are unused and must be zero. + # - Bit 31 contains the sign; 0 meaning positive, and 1 meaning negative. + + if len(decimal_parts) != 4: + raise ValueError('Invalid Decimal: Must have 4 integer parts') + + getcontext().prec = 29 + + if decimal_parts[3] & 0b1111111111111111 != 0: + raise ValueError('Invalid Decimal: Nonzero unused bits') + if decimal_parts[3] & 0b1111111000000000000000000000000 != 0: + raise ValueError('Invalid Decimal: Nonzero unused bits') + + mantissa = (decimal_parts[2] << 64) | (decimal_parts[1] << 32) | decimal_parts[0] + exponent = (decimal_parts[3] >> 16) & 0b11111111 + sign = (decimal_parts[3] >> 31) & 0b1 + + if exponent < 0 or exponent > 28: + raise ValueError('Invalid Decimal: Invalid exponent') + + return Decimal(mantissa * (-1 if sign == 1 else 1)) / (Decimal(10) ** exponent) + +def test_int32_to_decimal(): + # https://learn.microsoft.com/en-us/dotnet/api/system.decimal.-ctor?view=net-9.0#system-decimal-ctor(system-int32()) + assert int32_to_decimal([0x0, 0x0, 0x0, 0x0]) == Decimal('0') + assert int32_to_decimal([0x3B9ACA00, 0x0, 0x0, 0x0]) == Decimal('1000000000') + assert int32_to_decimal([0x0, 0x3B9ACA00, 0x0, 0x0]) == Decimal('4294967296000000000') + assert int32_to_decimal([0x0, 0x0, 0x3B9ACA00, 0x0]) == Decimal('18446744073709551616000000000') + assert int32_to_decimal([0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0]) == Decimal('79228162514264337593543950335') + assert int32_to_decimal([0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x80000000]) == Decimal('-79228162514264337593543950335') + assert int32_to_decimal([0xFFFFFFFF, 0x0, 0x0, 0x100000]) == Decimal('0.0000004294967295') + assert int32_to_decimal([0xFFFFFFFF, 0x0, 0x0, 0x1C0000]) == Decimal('0.0000000000000000004294967295') + assert int32_to_decimal([0xF0000, 0xF0000, 0xF0000, 0xF0000]) == Decimal('18133887298.441562272235520')