From 73eba8bdf0d4b37eda70871dc34b0c6f0659728f Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Sat, 25 Dec 2021 22:10:27 +0200 Subject: [PATCH] Add support for `double` data type (64bit double precision floating point) --- CHANGELOG.md | 7 ++ examples/index.php | 7 ++ src/Composer/Address.php | 2 + src/Composer/Read/ReadCoilsBuilder.php | 4 +- src/Composer/Read/ReadRegistersBuilder.php | 72 +++++++++------ .../Read/Register/ReadRegisterAddress.php | 12 ++- src/Composer/RegisterAddress.php | 1 + .../Write/Register/WriteRegisterAddress.php | 4 + src/Composer/Write/WriteRegistersBuilder.php | 8 ++ src/Packet/QuadWord.php | 12 ++- src/Utils/Types.php | 87 +++++++++++++++++-- .../Read/ReadRegistersBuilderTest.php | 5 +- .../Read/Register/ReadRegisterAddressTest.php | 22 +++++ .../Register/WriteRegisterAddressTest.php | 1 + .../Write/WriteRegistersBuilderTest.php | 8 +- tests/unit/Packet/QuadWordTest.php | 11 +++ tests/unit/Utils/TypesTest.php | 64 +++++++++++++- 17 files changed, 282 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4591b7..3dd0f8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4] - 2021-12-26 + +### Added + +* support for `double` (64bit double precision floating point) data type to API. This requires PHP 7.2+ as it uses + https://www.php.net/manual/en/function.pack.php `E` and `e` formats. + ## [2.3.1] - 2021-12-05 ### Added diff --git a/examples/index.php b/examples/index.php index 89466b5..1c073da 100644 --- a/examples/index.php +++ b/examples/index.php @@ -73,6 +73,11 @@ } catch (Exception $e) { $Int64 = '-'; } + try { + $double = $quadWord->getDouble(); + } catch (Exception $e) { + $double = '-'; + } } @@ -88,6 +93,7 @@ 'int32' => $doubleWord ? $doubleWord->getInt32() : null, 'UInt32' => $doubleWord ? $doubleWord->getUInt32() : null, 'float' => $doubleWord ? $doubleWord->getFloat() : null, + 'double' => $quadWord ? $double : null, 'Int64' => $quadWord ? $Int64 : null, 'UInt64' => $quadWord ? $UInt64 : null, ]; @@ -166,6 +172,7 @@ int32 UInt32 float + double int64 UInt64 diff --git a/src/Composer/Address.php b/src/Composer/Address.php index adb3108..675b605 100644 --- a/src/Composer/Address.php +++ b/src/Composer/Address.php @@ -14,6 +14,7 @@ interface Address const TYPE_INT64 = 'int64'; const TYPE_UINT64 = 'uint64'; const TYPE_FLOAT = 'float'; + const TYPE_DOUBLE = 'double'; const TYPE_STRING = 'string'; const TYPES = [ @@ -26,6 +27,7 @@ interface Address Address::TYPE_UINT64, Address::TYPE_INT64, Address::TYPE_FLOAT, + Address::TYPE_DOUBLE, Address::TYPE_STRING, ]; diff --git a/src/Composer/Read/ReadCoilsBuilder.php b/src/Composer/Read/ReadCoilsBuilder.php index 32facb3..8328478 100644 --- a/src/Composer/Read/ReadCoilsBuilder.php +++ b/src/Composer/Read/ReadCoilsBuilder.php @@ -106,8 +106,8 @@ public function fromArray(array $coil): ReadCoilsBuilder } public function coil( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null ): ReadCoilsBuilder diff --git a/src/Composer/Read/ReadRegistersBuilder.php b/src/Composer/Read/ReadRegistersBuilder.php index 7cab393..cf2bfe9 100644 --- a/src/Composer/Read/ReadRegistersBuilder.php +++ b/src/Composer/Read/ReadRegistersBuilder.php @@ -143,6 +143,9 @@ public function fromArray(array $register): ReadRegistersBuilder case Address::TYPE_FLOAT: $this->float($address, $register['name'] ?? null, $callback, $errorCallback, $endian); break; + case Address::TYPE_DOUBLE: + $this->double($address, $register['name'] ?? null, $callback, $errorCallback, $endian); + break; case Address::TYPE_STRING: $byteLength = $register['length'] ?? null; if ($byteLength === null) { @@ -168,11 +171,11 @@ public function byte(int $address, bool $firstByte = true, string $name = null, } public function int16( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -187,11 +190,11 @@ public function int16( } public function uint16( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -206,11 +209,11 @@ public function uint16( } public function int32( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -225,11 +228,11 @@ public function int32( } public function uint32( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -244,11 +247,11 @@ public function uint32( } public function uint64( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -263,11 +266,11 @@ public function uint64( } public function int64( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -282,11 +285,11 @@ public function int64( } public function float( - int $address, - string $name = null, + int $address, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { $r = new ReadRegisterAddress( @@ -300,13 +303,32 @@ public function float( return $this->addAddress($r); } + public function double( + int $address, + string $name = null, + callable $callback = null, + callable $errorCallback = null, + int $endian = null + ): ReadRegistersBuilder + { + $r = new ReadRegisterAddress( + $address, + ReadRegisterAddress::TYPE_DOUBLE, + $name, + $callback, + $errorCallback, + $endian + ); + return $this->addAddress($r); + } + public function string( - int $address, - int $byteLength, - string $name = null, + int $address, + int $byteLength, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ): ReadRegistersBuilder { if ($byteLength < 1 || $byteLength > 228) { diff --git a/src/Composer/Read/Register/ReadRegisterAddress.php b/src/Composer/Read/Register/ReadRegisterAddress.php index c63be7b..b569e8e 100644 --- a/src/Composer/Read/Register/ReadRegisterAddress.php +++ b/src/Composer/Read/Register/ReadRegisterAddress.php @@ -23,12 +23,12 @@ class ReadRegisterAddress extends RegisterAddress private $endian; public function __construct( - int $address, - string $type, - string $name = null, + int $address, + string $type, + string $name = null, callable $callback = null, callable $errorCallback = null, - int $endian = null + int $endian = null ) { parent::__construct($address, $type); @@ -49,6 +49,7 @@ protected function getAllowedTypes(): array Address::TYPE_INT64, Address::TYPE_UINT64, Address::TYPE_FLOAT, + Address::TYPE_DOUBLE, ]; } @@ -71,6 +72,9 @@ protected function extractInternal(ModbusResponse $response) case Address::TYPE_FLOAT: $result = $response->getDoubleWordAt($this->address)->getFloat($this->endian); break; + case Address::TYPE_DOUBLE: + $result = $response->getQuadWordAt($this->address)->getDouble($this->endian); + break; case Address::TYPE_INT64: $result = $response->getQuadWordAt($this->address)->getInt64($this->endian); break; diff --git a/src/Composer/RegisterAddress.php b/src/Composer/RegisterAddress.php index 4283b72..ff350e6 100644 --- a/src/Composer/RegisterAddress.php +++ b/src/Composer/RegisterAddress.php @@ -39,6 +39,7 @@ public function getSize(): int break; case Address::TYPE_INT64: case Address::TYPE_UINT64: + case Address::TYPE_DOUBLE: $size = 4; break; } diff --git a/src/Composer/Write/Register/WriteRegisterAddress.php b/src/Composer/Write/Register/WriteRegisterAddress.php index 87a56d6..7d52c8e 100644 --- a/src/Composer/Write/Register/WriteRegisterAddress.php +++ b/src/Composer/Write/Register/WriteRegisterAddress.php @@ -30,6 +30,7 @@ protected function getAllowedTypes(): array Address::TYPE_INT64, Address::TYPE_UINT64, Address::TYPE_FLOAT, + Address::TYPE_DOUBLE, ]; } @@ -66,6 +67,9 @@ public function toBinary(): string case Address::TYPE_FLOAT: $result = Types::toReal($this->getValue()); break; + case Address::TYPE_DOUBLE: + $result = Types::toDouble($this->getValue()); + break; } return $result; diff --git a/src/Composer/Write/WriteRegistersBuilder.php b/src/Composer/Write/WriteRegistersBuilder.php index 79d4657..3372b15 100644 --- a/src/Composer/Write/WriteRegistersBuilder.php +++ b/src/Composer/Write/WriteRegistersBuilder.php @@ -125,6 +125,9 @@ public function fromArray(array $register): WriteRegistersBuilder case Address::TYPE_FLOAT: $this->float($address, $value); break; + case Address::TYPE_DOUBLE: + $this->double($address, $value); + break; case Address::TYPE_STRING: $byteLength = $register['length'] ?? null; if ($byteLength === null) { @@ -171,6 +174,11 @@ public function float(int $address, float $value): WriteRegistersBuilder return $this->addAddress(new WriteRegisterAddress($address, Address::TYPE_FLOAT, $value)); } + public function double(int $address, float $value): WriteRegistersBuilder + { + return $this->addAddress(new WriteRegisterAddress($address, Address::TYPE_DOUBLE, $value)); + } + public function string(int $address, string $string, int $byteLength = null): WriteRegistersBuilder { return $this->addAddress(new StringWriteRegisterAddress($address, $string, $byteLength)); diff --git a/src/Packet/QuadWord.php b/src/Packet/QuadWord.php index def40b2..985084c 100644 --- a/src/Packet/QuadWord.php +++ b/src/Packet/QuadWord.php @@ -32,6 +32,16 @@ public function getInt64(int $endianness = null): int return Types::parseInt64($this->getData(), $endianness); } + /** + * @param int $endianness byte and word order for modbus binary data + * @return float + * @throws \RuntimeException + */ + public function getDouble(int $endianness = null): float + { + return Types::parseDouble($this->getData(), $endianness); + } + /** * @return DoubleWord * @throws \ModbusTcpClient\Exception\ModbusException @@ -65,4 +75,4 @@ public static function fromWords(Word $word1, Word $word2, Word $word3, Word $wo $word4->getData() ); } -} \ No newline at end of file +} diff --git a/src/Utils/Types.php b/src/Utils/Types.php index 8a64e4c..84391c3 100644 --- a/src/Utils/Types.php +++ b/src/Utils/Types.php @@ -269,7 +269,7 @@ public static function isBitSet($data, int $bit): bool } /** - * Parse binary string representing real in given endianness to float (double word/4 bytes to float) + * Parse binary string representing real (32bits) in given endianness to float (double word/4 bytes to float) * * @param string $binaryData binary byte string to be parsed to float * @param int $fromEndian byte and word order for modbus binary data @@ -298,6 +298,37 @@ public static function parseFloat(string $binaryData, int $fromEndian = null): f return unpack('f', pack('L', $pack))[1]; } + /** + * Parse binary string representing double (64bits) in given endianness to float (quad word/8 bytes to float) + * + * @param string $binaryData binary byte string to be parsed to float + * @param int $fromEndian byte and word order for modbus binary data + * @return float + * @throws \RuntimeException + */ + public static function parseDouble(string $binaryData, int $fromEndian = null): float + { + if (PHP_INT_SIZE !== 8) { + throw new ParseException('64-bit format codes are not available for 32-bit versions of PHP'); + } + if (strlen($binaryData) !== 8) { + throw new ParseException('binaryData must be 8 bytes in length'); + } + + $fromEndian = Endian::getCurrentEndianness($fromEndian); + if ($fromEndian & Endian::LOW_WORD_FIRST) { + $binaryData = ($binaryData[6] . $binaryData[7]) . + ($binaryData[4] . $binaryData[5]) . + ($binaryData[2] . $binaryData[3]) . + ($binaryData[0] . $binaryData[1]); + } + + if ($fromEndian & Endian::BIG_ENDIAN) { + return unpack('E', $binaryData)[1]; + } + return unpack('e', $binaryData)[1]; + } + /** * Parse binary string representing 64 bit unsigned integer to 64bit unsigned integer in given endianness (quad word/8 bytes to 64bit int) * @@ -317,7 +348,10 @@ public static function parseUInt64(string $binaryData, int $fromEndian = null): $fromEndian = Endian::getCurrentEndianness($fromEndian); if ($fromEndian & Endian::LOW_WORD_FIRST) { - $binaryData = implode('', array_reverse(str_split($binaryData, 2))); + $binaryData = ($binaryData[6] . $binaryData[7]) . + ($binaryData[4] . $binaryData[5]) . + ($binaryData[2] . $binaryData[3]) . + ($binaryData[0] . $binaryData[1]); } if ($fromEndian & Endian::BIG_ENDIAN) { @@ -356,7 +390,10 @@ public static function parseInt64(string $binaryData, int $fromEndian = null) $fromEndian = Endian::getCurrentEndianness($fromEndian); if ($fromEndian & Endian::LOW_WORD_FIRST) { - $binaryData = implode('', array_reverse(str_split($binaryData, 2))); + $binaryData = ($binaryData[6] . $binaryData[7]) . + ($binaryData[4] . $binaryData[5]) . + ($binaryData[2] . $binaryData[3]) . + ($binaryData[0] . $binaryData[1]); } if ($fromEndian & Endian::BIG_ENDIAN) { @@ -576,7 +613,7 @@ public static function toUint64($data, int $toEndian = null, bool $doRangeCheck } /** - * Convert Php data as it would be float to binary string with given endian order + * Convert Php data as it would be float (32bit) to binary string with given endian order * * @param float $float float to be converted to binary byte string * @param int $toEndian byte and word order for modbus binary data @@ -585,9 +622,45 @@ public static function toUint64($data, int $toEndian = null, bool $doRangeCheck */ public static function toReal($float, int $toEndian = null): string { - //pack to machine order float, unpack to machine order uint32, pack uint32 to binary big endian - //from php git seems that some day there will be 'g' and 'G' modifiers for float LE/BE conversion - return self::toInt32(unpack('L', pack('f', $float))[1], $toEndian, false); + $toEndian = Endian::getCurrentEndianness($toEndian); + $format = 'G'; // double (machine dependent size, big endian byte order) + if ($toEndian & Endian::LITTLE_ENDIAN) { + $format = 'g'; // double (machine dependent size, little endian byte order) + } + $data = pack($format, $float); + + if ($toEndian & Endian::LOW_WORD_FIRST) { + $data = ($data[2] . $data[3]) . ($data[0] . $data[1]); + } + return $data; + } + + /** + * Convert Php data as it would be double (64bit) to binary string with given endian order + * + * @param float $double float to be converted to binary byte string + * @param int $toEndian byte and word order for modbus binary data + * @return string binary string with big endian byte order + * @throws \ModbusTcpClient\Exception\ModbusException + */ + public static function toDouble($double, int $toEndian = null): string + { + $toEndian = Endian::getCurrentEndianness($toEndian); + + $format = 'E'; // double (machine dependent size, big endian byte order) + if ($toEndian & Endian::LITTLE_ENDIAN) { + $format = 'e'; // double (machine dependent size, little endian byte order) + } + $data = pack($format, $double); + + if ($toEndian & Endian::LOW_WORD_FIRST) { + $data = ($data[6] . $data[7]) . + ($data[4] . $data[5]) . + ($data[2] . $data[3]) . + ($data[0] . $data[1]); + } + + return $data; } /** diff --git a/tests/unit/Composer/Read/ReadRegistersBuilderTest.php b/tests/unit/Composer/Read/ReadRegistersBuilderTest.php index 09c89d2..c4d0225 100644 --- a/tests/unit/Composer/Read/ReadRegistersBuilderTest.php +++ b/tests/unit/Composer/Read/ReadRegistersBuilderTest.php @@ -69,6 +69,7 @@ public function testBuildAllFromArray() ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'uint64', 'address' => 271, 'name' => 'uint64_wo'], ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'int64', 'address' => 271, 'name' => 'int64_wo'], ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'float', 'address' => 271, 'name' => 'float_wo'], + ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'double', 'address' => 271, 'name' => 'double_wo'], ]) ->build(); @@ -76,7 +77,7 @@ public function testBuildAllFromArray() $this->assertCount(2, $requests[0]->getAddresses()); $this->assertCount(4, $requests[1]->getAddresses()); - $this->assertCount(7, $requests[2]->getAddresses()); + $this->assertCount(8, $requests[2]->getAddresses()); } public function testBuildAllFromArraySingle() @@ -337,6 +338,7 @@ public function typesProvider(): array 'add int32 ' => ['int32', 2], 'add uint32' => ['uint32', 2], 'add float' => ['float', 2], + 'add double' => ['double', 4], 'add uint64' => ['uint64', 4], ]; } @@ -372,6 +374,7 @@ public function typesWithEndianProvider(): array 'add int32 ' => ['int32', 2, Endian::LITTLE_ENDIAN], 'add uint32' => ['uint32', 2, Endian::LITTLE_ENDIAN], 'add float' => ['float', 2, Endian::LITTLE_ENDIAN], + 'add double' => ['double', 4, Endian::LITTLE_ENDIAN], 'add uint64' => ['uint64', 4, Endian::LOW_WORD_FIRST], ]; } diff --git a/tests/unit/Composer/Read/Register/ReadRegisterAddressTest.php b/tests/unit/Composer/Read/Register/ReadRegisterAddressTest.php index 8253489..a938503 100644 --- a/tests/unit/Composer/Read/Register/ReadRegisterAddressTest.php +++ b/tests/unit/Composer/Read/Register/ReadRegisterAddressTest.php @@ -27,8 +27,10 @@ public function sizeProvider() 'int16 size should be 1' => ['int16', 1], 'uint16 size should be 1' => ['uint16', 1], 'int32 size should be 2' => ['int32', 2], + 'float size should be 2' => ['float', 2], 'uint32 size should be 2' => ['uint32', 2], 'uint64 size should be 4' => ['uint64', 4], + 'double size should be 4' => ['double', 4], ]; } @@ -267,6 +269,26 @@ public function testExtractFloatWithCustomEndian() $this->assertEqualsWithDelta(1.85, $value, 0.0000001); } + public function testExtractDouble() + { + $responsePacket = new ReadHoldingRegistersResponse("\x08\x4d\x82\x30\x10\xcc\xc3\x41\xc1", 3, 33152); + $address = new ReadRegisterAddress(0, Address::TYPE_DOUBLE); + + $value = $address->extract($responsePacket); + + $this->assertEqualsWithDelta(597263968.12737, $value, 0.00001); + } + + public function testExtractDoubleWithCustomEndian() + { + $responsePacket = new ReadHoldingRegistersResponse("\x08\x82\x4d\x10\x30\xc3\xcc\xc1\x41", 3, 33152); + $address = new ReadRegisterAddress(0, Address::TYPE_DOUBLE, null, null, null, Endian::LITTLE_ENDIAN); + + $value = $address->extract($responsePacket); + + $this->assertEqualsWithDelta(597263968.12737, $value, 0.00001); + } + public function testExtractUInt64() { if (PHP_INT_SIZE === 4) { diff --git a/tests/unit/Composer/Write/Register/WriteRegisterAddressTest.php b/tests/unit/Composer/Write/Register/WriteRegisterAddressTest.php index 224a19d..0932cc9 100644 --- a/tests/unit/Composer/Write/Register/WriteRegisterAddressTest.php +++ b/tests/unit/Composer/Write/Register/WriteRegisterAddressTest.php @@ -44,6 +44,7 @@ public function toBinaryProvider() 'int64 to binary' => ["\x00\x00\x00\x00\x00\x00\x80\x00", Address::TYPE_INT64, -9223372036854775808, null, true], 'uint64 to binary' => ["\x00\x01\x00\x00\x00\x00\x00\x00", Address::TYPE_UINT64, 1, null, true], 'float to binary' => ["\xcc\xcd\x3f\xec", Address::TYPE_FLOAT, 1.85], + 'double to binary' => ["\x4d\xa9\x30\x10\xcc\xc3\x41\xc1", Address::TYPE_DOUBLE, 597263968.12737], 'string to binary' => ["\x00\x6E\x65\x72\xF8\x53", Address::TYPE_STRING, 'Søren', \ModbusTcpClient\Exception\InvalidArgumentException::class], ]; } diff --git a/tests/unit/Composer/Write/WriteRegistersBuilderTest.php b/tests/unit/Composer/Write/WriteRegistersBuilderTest.php index 30bfedb..1395c6a 100644 --- a/tests/unit/Composer/Write/WriteRegistersBuilderTest.php +++ b/tests/unit/Composer/Write/WriteRegistersBuilderTest.php @@ -47,7 +47,8 @@ public function testBuildSplitRequestTo3() // will be another request as uri is different for subsequent string register ->useUri('tcp://127.0.0.1:5023') ->float(270, 1.2) - ->string(272, 'Hello', 10) + ->double(272, 1) + ->string(276, 'Hello', 10) ->build(); $this->assertCount(3, $requests); @@ -65,7 +66,7 @@ public function testBuildSplitRequestTo3() $writeRequest2 = $requests[2]; $this->assertInstanceOf(WriteMultipleRegistersRequest::class, $writeRequest2->getRequest()); $this->assertEquals('tcp://127.0.0.1:5023', $writeRequest2->getUri()); - $this->assertCount(2, $writeRequest2->getAddresses()); + $this->assertCount(3, $writeRequest2->getAddresses()); } public function testBuildAllFromArray() @@ -84,6 +85,7 @@ public function testBuildAllFromArray() ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'uint64', 'value' => 1, 'address' => 276], ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'int64', 'value' => 1, 'address' => 280], ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'float', 'value' => 1, 'address' => 284], + ['uri' => 'tcp://127.0.0.1:5023', 'type' => 'double', 'value' => 1, 'address' => 286], ]) ->build(); @@ -91,7 +93,7 @@ public function testBuildAllFromArray() $this->assertCount(1, $requests[0]->getAddresses()); $this->assertCount(2, $requests[1]->getAddresses()); - $this->assertCount(7, $requests[2]->getAddresses()); + $this->assertCount(8, $requests[2]->getAddresses()); } public function testBuildAllFromArrayUsingObject() diff --git a/tests/unit/Packet/QuadWordTest.php b/tests/unit/Packet/QuadWordTest.php index 988c201..25d0980 100644 --- a/tests/unit/Packet/QuadWordTest.php +++ b/tests/unit/Packet/QuadWordTest.php @@ -53,6 +53,17 @@ public function testShouldGetUInt64() $this->assertEquals(2147483647, $quadWord->getUInt64(Endian::BIG_ENDIAN_LOW_WORD_FIRST)); } + public function testShouldGetDouble() + { + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('64-bit format codes are not available for 32-bit versions of PHP'); + } + + $quadWord = new QuadWord("\x4d\x82\x30\x10\xcc\xc3\x41\xc1"); + + $this->assertEqualsWithDelta(597263968.12737, $quadWord->getDouble(Endian::BIG_ENDIAN_LOW_WORD_FIRST), 0.00001); + } + public function testShouldGetInt64() { if (PHP_INT_SIZE === 4) { diff --git a/tests/unit/Utils/TypesTest.php b/tests/unit/Utils/TypesTest.php index f40de4f..4360183 100644 --- a/tests/unit/Utils/TypesTest.php +++ b/tests/unit/Utils/TypesTest.php @@ -369,12 +369,33 @@ public function testShouldConvertFloatToRealWithBigEndian() public function testShouldConvertFloatToRealWithLittleEndian() { - $this->assertEquals("\xec\x3f\xcd\xcc", Types::toReal(1.85, Endian::LITTLE_ENDIAN)); - $this->assertEquals("\x2a\x3f\xab\xaa", Types::toReal(0.66666666666, Endian::LITTLE_ENDIAN)); + $this->assertEquals("\xcd\xcc\xec\x3f", Types::toReal(1.85, Endian::LITTLE_ENDIAN)); + $this->assertEquals("\xab\xaa\x2a\x3f", Types::toReal(0.66666666666, Endian::LITTLE_ENDIAN)); $this->assertEquals("\x00\x00\x00\x00", Types::toReal(null, Endian::LITTLE_ENDIAN)); $this->assertEquals("\x00\x00\x00\x00", Types::toReal(0, Endian::LITTLE_ENDIAN)); } + public function testShouldConvertFloatToDoubleWithBigEndianLowWordFirst() + { + $this->assertEquals("\x4d\xa9\x30\x10\xcc\xc3\x41\xc1", Types::toDouble(597263968.12737, Endian::BIG_ENDIAN_LOW_WORD_FIRST)); + $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00", Types::toDouble(null, Endian::BIG_ENDIAN_LOW_WORD_FIRST)); + $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00", Types::toDouble(0, Endian::BIG_ENDIAN_LOW_WORD_FIRST)); + } + + public function testShouldConvertFloatToDoubleWithBigEndian() + { + $this->assertEquals("\x41\xc1\xcc\xc3\x30\x10\x4d\xa9", Types::toDouble(597263968.12737, Endian::BIG_ENDIAN)); + $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00", Types::toDouble(null, Endian::BIG_ENDIAN)); + $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00", Types::toDouble(0, Endian::BIG_ENDIAN)); + } + + public function testShouldConvertFloatToDoubleWithLittleEndian() + { + $this->assertEquals("\xa9\x4d\x10\x30\xc3\xcc\xc1\x41", Types::toDouble(597263968.12737, Endian::LITTLE_ENDIAN)); + $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00", Types::toDouble(null, Endian::LITTLE_ENDIAN)); + $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00", Types::toDouble(0, Endian::LITTLE_ENDIAN)); + } + public function testShouldParseFloatAsBigEndianLowWordFirst() { $float = Types::parseFloat("\xcc\xcd\x3f\xec", Endian::BIG_ENDIAN_LOW_WORD_FIRST); @@ -408,6 +429,45 @@ public function testShouldParseFloatAsLittleEndian() $this->assertEqualsWithDelta(0, Types::parseFloat("\x00\x00\x00\x00", Endian::LITTLE_ENDIAN), 0.0000001); } + public function testShouldParseDoubleAsBigEndianLowWordFirst() + { + $float = Types::parseDouble("\x4d\x82\x30\x10\xcc\xc3\x41\xc1", Endian::BIG_ENDIAN_LOW_WORD_FIRST); + + $this->assertTrue(is_float($float)); + $this->assertEqualsWithDelta(597263968.12737, $float, 0.00001); + } + + public function testShouldParsDoubleAsBigEndian() + { + $float = Types::parseDouble("\x41\xc1\xcc\xc3\x30\x10\x4d\x82", Endian::BIG_ENDIAN); + + $this->assertTrue(is_float($float)); + $this->assertEqualsWithDelta(597263968.12737, $float, 0.00001); + + $this->assertEqualsWithDelta(0, Types::parseDouble("\x00\x00\x00\x00\x00\x00\x00\x00", Endian::BIG_ENDIAN), 0.0000001); + } + + public function testShouldParseDoubleAsLittleEndian() + { + $float = Types::parseDouble("\x82\x4d\x10\x30\xc3\xcc\xc1\x41", Endian::LITTLE_ENDIAN); + + $this->assertTrue(is_float($float)); + $this->assertEqualsWithDelta(597263968.12737, $float, 0.00001); + + $this->assertEqualsWithDelta(0, Types::parseDouble("\x00\x00\x00\x00\x00\x00\x00\x00", Endian::LITTLE_ENDIAN), 0.0000001); + } + + public function testShouldFailToParseDoubleFromQuadWord() + { + $this->expectExceptionMessage("binaryData must be 8 bytes in length"); + $this->expectException(ParseException::class); + + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('64-bit format codes are not available for 32-bit versions of PHP'); + } + Types::parseDouble("\xFF\xFF\xFF\xFF\xFF\xFF\xFF", Endian::BIG_ENDIAN); + } + public function testShouldParseStringFromRegisterAsLittleEndian() { // null terminated data