Implement read for Decimal
This commit is contained in:
parent
0d25c9b6cc
commit
d2ffd10191
@ -14,10 +14,12 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
import io
|
import io
|
||||||
import struct
|
import struct
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from .decimal import int32_to_decimal
|
||||||
from .known_types import CslaKnownTypes
|
from .known_types import CslaKnownTypes
|
||||||
from .serialization_info import ChildData, FieldData, SerializationInfo
|
from .serialization_info import ChildData, FieldData, SerializationInfo
|
||||||
|
|
||||||
@ -112,6 +114,14 @@ class CslaBinaryReader:
|
|||||||
|
|
||||||
shift += 7
|
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):
|
def read_int32(self):
|
||||||
# BinaryReader.ReadInt32
|
# BinaryReader.ReadInt32
|
||||||
return struct.unpack('<i', self.stream.read(4))[0]
|
return struct.unpack('<i', self.stream.read(4))[0]
|
||||||
@ -158,7 +168,7 @@ class CslaBinaryReader:
|
|||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
if known_type == CslaKnownTypes.Decimal.value:
|
if known_type == CslaKnownTypes.Decimal.value:
|
||||||
raise NotImplementedError()
|
return self.read_decimal()
|
||||||
|
|
||||||
if known_type == CslaKnownTypes.DateTime.value:
|
if known_type == CslaKnownTypes.DateTime.value:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
57
csla_binary/decimal.py
Normal file
57
csla_binary/decimal.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# pycsla-binary: Python implementation of CSLA .NET binary serialisation
|
||||||
|
# Copyright (C) 2025 Lee Yingtong Li (RunasSudo)
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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')
|
Loading…
x
Reference in New Issue
Block a user