Skip to content

Commit

Permalink
Implement from/toBinaryString()
Browse files Browse the repository at this point in the history
  • Loading branch information
BenMorel committed Aug 9, 2020
1 parent 8b29637 commit 4760b13
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 0 deletions.
98 changes: 98 additions & 0 deletions src/BigInteger.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,104 @@ public static function fromArbitraryBase(string $number, string $alphabet) : Big
return new BigInteger($number);
}

/**
* Translates a string containing the binary representation of a BigInteger into a BigInteger.
*
* The input string is assumed to be in big-endian byte-order: the most significant byte is in the zeroth element.
*
* If `$signed` is true, the input is assumed to be in two's-complement representation, and the leading bit is
* interpreted as a sign bit. If `$signed` is false, the input is interpreted as an unsigned number, and the
* resulting BigInteger will always be positive or zero.
*
* This method can be used to retrieve a number exported by `toBinaryString()`, as long as the `$signed` flags
* match.
*
* @param string $value The binary string value.
* @param bool $signed Whether to interpret as a signed number in two's-complement representation with a leading
* sign bit.
*
* @return BigInteger
*
* @throws NumberFormatException If the string is empty.
*/
public static function fromBinaryString(string $value, bool $signed = true) : BigInteger
{
if ($value === '') {
throw new NumberFormatException('The binary string must not be empty.');
}

$twosComplement = false;

if ($signed) {
$x = ord($value[0]);

if (($twosComplement = ($x >= 0x80))) {
$value = ~$value;
}
}

$number = self::fromBase(bin2hex($value), 16);

if ($twosComplement) {
return $number->plus(1)->negated();
}

return $number;
}

/**
* Returns a string containing the binary representation of this BigInteger.
*
* The binary string is in big-endian byte-order: the most significant byte is in the zeroth element.
*
* If `$signed` is true, the output will be in two's-complement representation, and a sign bit will be prepended to
* the output. If `$signed` is false, no sign bit will be prepended, and this method will throw an exception if the
* number is negative.
*
* The string will contain the minimum number of bytes required to represent this BigInteger, including a sign bit
* if `$signed` is true.
*
* This representation is compatible with the `fromBinaryString()` factory method, as long as the `$signed` flags
* match.
*
* @param bool $signed Whether to output a signed number in two's-complement representation with a leading sign bit.
*
* @return string
*
* @throws NegativeNumberException If $signed is false, and the number is negative.
*/
public function toBinaryString(bool $signed = true) : string
{
if (! $signed && $this->isNegative()) {
throw new NegativeNumberException();
}

$pad = function(string $hex) : string {
return (strlen($hex) % 2 !== 0) ? '0' . $hex : $hex;
};

$hex = $this->abs()->toBase(16);
$hex = $pad($hex);

if ($signed) {
if ($this->isNegative()) {
$hex = bin2hex(~hex2bin($hex));
$hex = self::fromBase($hex, 16)->plus(1)->toBase(16);
$hex = $pad($hex);

if ($hex[0] < '8') {
$hex = 'FF' . $hex;
}
} else {
if ($hex[0] >= '8') {
$hex = '00' . $hex;
}
}
}

return hex2bin($hex);
}

/**
* Returns a BigInteger representing zero.
*
Expand Down
196 changes: 196 additions & 0 deletions tests/BigIntegerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Brick\Math\Internal\Calculator;
use Brick\Math\RoundingMode;

use Generator;

/**
* Unit tests for class BigInteger.
*/
Expand Down Expand Up @@ -3271,6 +3273,200 @@ public function testToArbitraryBaseOnNegativeNumber() : void
$number->toArbitraryBase('01');
}

/**
* @dataProvider providerFromBinaryString
*/
public function testFromBinaryString(string $binaryStringHex, bool $signed, string $expectedNumber) : void
{
$number = BigInteger::fromBinaryString(hex2bin($binaryStringHex), $signed);
self::assertSame($expectedNumber, (string) $number);
}

public function providerFromBinaryString() : Generator
{
foreach ($this->providerToBinaryString() as [$expectedNumber, $signed, $binaryStringHex]) {
yield [$binaryStringHex, $signed, $expectedNumber];

// test with extra leading bits: these should return the same number
$prefix = ($expectedNumber[0] === '-') ? 'FF' : '00';
yield [$prefix . $binaryStringHex, $signed, $expectedNumber];
}
}

public function testFromBinaryStringWithEmptyString() : void
{
$this->expectException(NumberFormatException::class);
BigInteger::fromBinaryString('');
}

/**
* @dataProvider providerToBinaryString
*/
public function testToBinaryString(string $number, bool $signed, string $expectedBinaryStringHex) : void
{
$binaryString = BigInteger::of($number)->toBinaryString($signed);
self::assertSame($expectedBinaryStringHex, strtoupper(bin2hex($binaryString)));
}

public function providerToBinaryString() : array
{
return [
['-32769', true, 'FF7FFF'],
['-32768', true, '8000'],
['-32767', true, '8001'],
['-129', true, 'FF7F'],
['-128', true, '80'],
['-2', true, 'FE'],
['-1', true, 'FF'],
['0', false, '00'],
['0', true, '00'],
['1', false, '01'],
['1', true, '01'],
['127', false, '7F'],
['127', true, '7F'],
['128', false, '80'],
['128', true, '0080'],
['32767', false, '7FFF'],
['32767', true, '7FFF'],
['32768', false, '8000'],
['32768', true, '008000'],

['-783409810830859048059', true, 'D588013FBB7EADEB85'],
['3938956672038786165637', false, 'D588013FBB7EADEB85'],
['3938956672038786165637', true, '00D588013FBB7EADEB85'],

['-91749892389539817298343095779467869273987239847934', true, 'C138E088BEDB9E9052680AA1AD2E30628126C8D802'],
['91749892389539817298343095779467869273987239847934', false, '3EC71F774124616FAD97F55E52D1CF9D7ED93727FE'],
['91749892389539817298343095779467869273987239847934', true, '3EC71F774124616FAD97F55E52D1CF9D7ED93727FE'],

['-172394526767171329761800221395900583757931491153922', true, '8A0AFA0F6D456BD1E23C617F037FBD9D7ED93727FE'],
['-201749892389539817298343095779467869273987239847934', true, 'FF75F505F092BA942E1DC39E80FC8042628126C8D802'],
['201749892389539817298343095779467869273987239847934', false, '8A0AFA0F6D456BD1E23C617F037FBD9D7ED93727FE'],
['201749892389539817298343095779467869273987239847934', true, '008A0AFA0F6D456BD1E23C617F037FBD9D7ED93727FE'],

['-1098858588416703775195092613', true, 'FC730BA48F28911AA6FEA97B'],
['-47083115471922296524820850', true, 'D90DC332265BB003412E8E'],
['-943932252431784472316642258806', true, 'F415FD8A8DB757E5DEE38D108A'],
['-341852484393431150693130917974925642410', true, 'FEFED19B3B597B4992D424EE7BD763F556'],
['-283182252236038840253195025971128832705', true, 'FF2AF51502896F66B16A61FA4BEC0FED3F'],
['-81924191665932376117353149384682167256990062702727', true, 'C7F1F831608910CA847BBA5B9DF78D73739BB48F79'],
['-2895870591297216468400157702565925141502115543810', true, 'FE04C0ABEE0B5802122B7FD1F45DED3CF86FFA84FE'],
['-3119445165109880695968047110078654901', true, 'FDA7377A9CBAD40D92C5CCE93A6F164B'],
['-2416303054131084339036838954993158636473528506132', true, 'FE58C13944FC5542742BC6EA5E664ADFA371C5A8EC'],
['-96872708052717571315623430142461871949402837084399', true, 'BDB78D88F9EF374F0B36B23A65DD2615A59BF41711'],
['-406488096345576796461719102940335', true, 'EBF5661F78276E1983AF850C1B51'],
['-1136451083331321373659312404693395966315', true, 'FCA9077B9A9F25AFE8EE567A89496BF695'],
['-9050047939578973554450039239', true, 'E2C1F9CE23F97DC6337CFE39'],
['-5020359323944618072504884280451', true, 'C0A258196411C027A1914FC37D'],
['-2328315909540727378430095354579827047745865', true, 'E545B1CAFB737E6A5D994EAA63580370B6B7'],
['-431858509646237776589820167124236054397', true, 'FEBB1B139272FB6CCCC9D2CB50DC60D083'],
['-708427585499922108210831863881916427823432', true, 'F7DE1E51F72FEE6C276F8CCA9FBC229396B8'],
['-810836550712511504071418602', true, 'FD614AAE82643E8BD8199D16'],
['-6250094231931483341539582450667557086996', true, 'EDA1F3A2E7B5E2962FB0FB18DEF76618EC'],
['-793694724265757042363818257818', true, 'F5FB6F1C9F0A05158C27ACD266'],
['-823570289597131761777978989763', true, 'F59AE69F9536B419206BC2A33D'],
['-48802520147365020361534264877157131084794152', true, 'FDCFC6487CA9AF6AD8FDC3ECD07F242B9D76D8'],
['-29698333286094898756573847056234', true, 'FE8927827B08DDF695EE01D49096'],
['-9259920943214103669706837547526283204815967139517', true, 'F9AA02C8B97A22134B8BBED296110C43D555414943'],
['-1329712773661524524246281239430805224', true, 'FEFFE8193E33131F8545B3E0F3B24918'],
['-1901878901152859921638714839336301950184917', true, 'EA2AE1575DFCFCADE087008E55F43283522B'],
['-211851840428790382900097129102333609377496150', true, 'F6800F655A76F12A036BB19813EFB9747483AA'],
['-4567388417903078950492095', true, 'FC38D160B3521CD8AD2841'],
['-369549145795669040781019236477391675536381', true, 'FBC1FE1F6B3FA12F0C15EBB2D1D8E538CC03'],
['-1576138934203079166729265319520498113775', true, 'FB5E3EAED9C484CAE2D719313D1637A311'],
['-6479164160394946817944469', true, 'FAA3FBCE9C2BC0BBB6146B'],
['-4973786449177733351701364077033', true, 'C138D4473C207E31820C000E17'],
['-711073252382098058231709543027', true, 'F70665FDD48CBF58C89DEE558D'],
['-6006367085080317337477614662241', true, 'B43061986886506ED2DB7C0D9F'],
['-7794702879691249399247142997073094893', true, 'FA22CB7A161C24BF1AA59BEFD13F9713'],
['-92678975046563530462161825', true, 'B35676B20EDD0B3DB4945F'],
['-15677635845925377762958659902912102793958926170', true, 'FD40FD9112795C35521FE70F98D7AB063937D8A6'],
['-4670095405709769649548288171872688313', true, 'FC7C9284F8F7E995E1AA712740D6E747'],
['-174516040067109253897831812572', true, 'FDCC1BB70F489CA9DAD93C7224'],
['-78545859497424473138903230802367665200', true, 'C4E89E22CED860B9E55E8CEE89272BD0'],
['-13497065900825560235714337812912830643153659021435', true, 'F6C3D2A20A423123AD1E0EFD8BDF5BB3A8AA4B3785'],
['-5596555173616744352812136520441802', true, 'FEEC118AEF4D06D0F643CD39A0A036'],
['-99255515845082927316486933856906', true, 'FB1B38219A9F958666994AC9BD76'],
['-214375103475158898441935629946159638332', true, 'FF5EB8DB5D0D435EDD4E7636018B6BD8C4'],
['-1317075184740725940950186258631', true, 'EF604CF120DE2E2811BD2D0F39'],
['-806295473821796646961606303367289386895057', true, 'F6BE82B44B55536F7AD05829B2D0E446F92F'],
['-891519425610904878565592597054470339680', true, 'FD614B9AAE3F585049CBBB14E0D0E643A0'],
['-348541235698230937744111503200245977926', true, 'FEF9C96654CDCC22ECB72A1199D5C4ECBA'],
['-92998623857448524743006503658281', true, 'FB6A313D59DB9A33234E975A9CD7'],
['-136107716767751306827369763', true, '8F6A11E3B0E7D7502672DD'],
['-26859090460874041069092334', true, 'E9C85DC9DF834329D8C212'],
['-141333871744599151791049755944272203632', true, '95AC161FF4636C67912F85933AD4F090'],
['-46954121389748005216797591', true, 'D92913FAA69544689EBC69'],
['-596904336055990562119362462070166572406242', true, 'F925DB1686886353785D68E8CA74225D6A1E'],
['-1344405286102205040892162', true, 'FEE34FA4B63686D9E0E6FE'],
['-35964005242894495033684813312891910672337147', true, 'FE6327499A42D08BAC1085C45A9407C6343705'],
['-601198613094225698564174314', true, 'FE0EB33DE1204120F92A8E16'],
['-7610885901830744730460664472229465802332051013', true, 'FEAAB7508B4AF39EBD10BB35DEE829EAA085E9BB'],
['-1306992083698354560765415156364', true, 'EF80E17CD3A4F68C57C19B9574'],
['-5928426295029435752295941280931458449876298746999', true, 'FBF1902BB8510536016F32111424AB0A6CD3383389'],
['-9896625370984316971469780243746442575622', true, 'E2EA9B48F364ADED599F896DE43FFE00FA'],
['-3443717087795173563395198312020096617495', true, 'F5E13C36CCBFD3BF48E8688F60D14773E9'],
['-2245494922030817093415463801401387705038026', true, 'E6391560F819594E246C4D49BD740B1A0B36'],
['-69920118812045303946652729', true, 'C629D6C3A1172598A453C7'],
['-4649308422069845482348722740', true, 'F0FA2EA5696E1650D31845CC'],
['-5944629567503254036792469', true, 'FB152CFC442118BA8E3B6B'],
['-127287547939906994889167955101580269246', true, 'A03D4F5B5716DB273873287F6293E942'],
['-248002284366499376793080698841650655398888883699', true, 'D48F316557240A553B65D99CA1F51451CE45BA0D'],
['-23473333872646511990633448370574', true, 'FED7B9914EFDF4213635B8A38E72'],
['-90081289425957678036534683340022400330665092189', true, 'F0389D82E9997BB33DA87E67B4EB1A6B5C2B1BA3'],
['-6131774766767685247732809530507373880274956199801', true, 'FBCDF1B5BEDFF595F230E7B317BE86109C9B69D887'],
['-61408144425269671111963032819693687372', true, 'D1CD38B99CA0A4509A2EDFE54C616DB4'],
['-941883884450958161905398530967842844601553049', true, 'D5C3B40968036422C7EEF2E68DF7334E56EF67'],
['-51724104392679606989886993', true, 'D536FEAEB06C0DAAB3E9EF'],
['-7845325374205082456600697444541989', true, 'FE7D3212874987D41E2B1FD67759DB'],
['-79955574961153464592840377369552710119732773113197', true, 'C94ACC04DB0A580C682581F29DE7F917975ED03293'],
['-532195364082905073623316433892356', true, 'E5C2C008BD72041F6A6D25018BFC'],
['-8005009342287066447355401475', true, 'E6226956042AF014723AAAFD'],
['-337940679109885279728604443841584448', true, 'BEEA3CF59F4C86BED2CC426CF122C0'],
['-4605847097510366708547861415', true, 'F11E21F0898355E7A28DC459'],
['-3670015713617629611716762176813', true, 'D1AD8A2767FDC96D50E15372D3'],
['-649830629891966673736057405236704', true, 'DFF5FBFD829F4C38BED79AC51A20'],
['-3709825918305227929540638847125221526396079333457', true, 'FD762DA7043EC89E94DADCB451C4A0ECD5701D9FAF'],
['-43178624890438204804613583850151437', true, 'F7AF212255EE0F1FBFCA5B5DB861F3'],
['-7905675446580131577998272825821630231042938673', true, 'FE9D7F4BEECA706A5190AACF62EC09E62CB2A8CF'],
['-61457863674470726296386500810', true, 'FF396B3F26641B6F2D89CA8736'],
['-49308204466516796640462839998', true, 'FF60AD358FCD59D1EF5890DF42'],
['-4979647065425719032196358813843940678', true, 'FC40F471BB8B445D875ACB04DB709ABA'],
['-72543680230619431272714746061676621865910', true, 'FF2AD03365935BE6E56CABBFE041B283DC4A'],
['-233612491615341559436220748', true, 'FF3EC2A1741B823AD9F8E6B4'],
['-410572465760268592909453253610755651060159', true, 'FB496F9B6298819BED5A2C9CE83EA41F3E41'],
['-4440664570334327036040778362262541337800286366939', true, 'FCF629B6BD303417FDD8BEE8BA60DC0032DDD89325'],
['-441843788011967692225334039493087710', true, 'AAE76B59CB82AD64D35F8B5885EE22'],
['-8368143068213706891940405000138132955224063160906', true, 'FA46377CAD1AF23A85E9E3A934EDB4475109C6CDB6'],
['-829172234357769818909006249228426999062454628039', true, 'FF6EC2A18EA2660725C6EED1C62F883976C6479139'],
['-5906586101908186721958760602074196491', true, 'FB8E6ED2410E62DB7516A0C8A3D655F5'],
['-108107432789789110223831515005384896', true, 'EB2DE45D03682F7719DE19C5790740'],
['-5689187603712743059853316', true, 'FB4B4485A30709DBDE6FFC'],
['-2099146868847492866997101785005721844566', true, 'F9D4C704CD95F8CDB3722115B394C904AA'],
['-79222222171395055207251479752239744334172', true, 'FF172FD301840D0C03A91C2B8A30C86B46A4'],

['-5522712836598149970513187952362000928321873336', true, 'FF085A5DA5FE6298EE2CC3271314D088CF435A48'],
['-5626380784733548014139646923816473885490489486', true, 'FF03B450EE0FABA8DF623760716CA5D94EA8D772'],
['-83014840727912357366146247329067270452639', true, 'FF0C0A933DC99D7267680165F0648DEF4A61'],
['-84461440308411103403421598199499894063437', true, 'FF07CA464BE094F0CA033746E2906BAB7EB3'],
['-19888885585920803236496419582873', true, 'FF04F78B70237396B8B928480C67'],
['-19350969325241189350680714728748', true, 'FF0BC1A5570DA847041C92C12AD4'],
['-75629136915615836759583377746', true, 'FF0BA10B50A4EFB0C8C4FBB6AE'],
['-5635103981671263600594400878193141287978133527', true, 'FF03502DC2145B71430CB94A065996E495A217E9'],
['-5376200015943869384474757131064327029707233723', true, 'FF0EEC402183DB518AC909E67E9F7794F38C2645'],
['-1322065057338692577952426752557825090', true, 'FF016128FE63A069FF7B1DB3A05243BE'],
['-83288572456745425681424028120956120704954', true, 'FF0B3CA46D934BA9B001BA99A25FC96E5846'],
['-304214315569945292283687790', true, 'FF045C1CE86BB9207E3A4C92'],
];
}

public function testToBinaryStringWithNegativeNumberAndNoSignBit() : void
{
$number = BigInteger::of(-1);
$this->expectException(NegativeNumberException::class);
$number->toBinaryString(false);
}

public function testSerialize() : void
{
$value = '-1234567890987654321012345678909876543210123456789';
Expand Down

0 comments on commit 4760b13

Please sign in to comment.