Skip to content

Pack/unpack double/float in Little Endian and Big Endian #1905

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

Merged
merged 10 commits into from
Jan 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ PHP 7.2 UPGRADE NOTES
];
. count() now raises a warning when an invalid parameter is passed.
Only arrays and objects implementing the Countable interface should be passed.
. pack() and unpack() now support float and double in both little and big endian.

- XML:
. utf8_encode() and utf8_decode() have been moved to the Standard extension
Expand Down
214 changes: 205 additions & 9 deletions ext/standard/pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,132 @@ static void php_pack(zval *val, size_t size, int *map, char *output)
}
/* }}} */

/* {{{ php_pack_reverse_int32
*/
inline uint32_t php_pack_reverse_int32(uint32_t arg)
{
uint32_t result;
result = ((arg & 0xFF) << 24) | ((arg & 0xFF00) << 8) | ((arg >> 8) & 0xFF00) | ((arg >> 24) & 0xFF);

return result;
}
/* }}} */

/* {{{ php_pack
*/
inline uint64_t php_pack_reverse_int64(uint64_t arg)
{
union Swap64 {
uint64_t i;
uint32_t ul[2];
} tmp, result;
tmp.i = arg;
result.ul[0] = php_pack_reverse_int32(tmp.ul[1]);
result.ul[1] = php_pack_reverse_int32(tmp.ul[0]);

return result.i;
}
/* }}} */

/* {{{ php_pack_copy_float
*/
static void php_pack_copy_float(int is_little_endian, void * dst, float f)
{
union Copy32 {
float f;
uint32_t i;
} m;
m.f = f;

#ifdef WORDS_BIGENDIAN
if (is_little_endian) {
m.i = php_pack_reverse_int32(m.i);
}
#else /* WORDS_BIGENDIAN */
if (!is_little_endian) {
m.i = php_pack_reverse_int32(m.i);
}
#endif /* WORDS_BIGENDIAN */

memcpy(dst, &m.f, sizeof(float));
}
/* }}} */

/* {{{ php_pack_copy_double
*/
static void php_pack_copy_double(int is_little_endian, void * dst, double d)
{
union Copy64 {
double d;
uint64_t i;
} m;
m.d = d;

#ifdef WORDS_BIGENDIAN
if (is_little_endian) {
m.i = php_pack_reverse_int64(m.i);
}
#else /* WORDS_BIGENDIAN */
if (!is_little_endian) {
m.i = php_pack_reverse_int64(m.i);
}
#endif /* WORDS_BIGENDIAN */

memcpy(dst, &m.d, sizeof(double));
}
/* }}} */

/* {{{ php_pack_parse_float
*/
static float php_pack_parse_float(int is_little_endian, void * src)
{
union Copy32 {
float f;
uint32_t i;
} m;
memcpy(&m.i, src, sizeof(float));

#ifdef WORDS_BIGENDIAN
if (is_little_endian) {
m.i = php_pack_reverse_int32(m.i);
}
#else /* WORDS_BIGENDIAN */
if (!is_little_endian) {
m.i = php_pack_reverse_int32(m.i);
}
#endif /* WORDS_BIGENDIAN */

return m.f;
}
/* }}} */

/* {{{ php_pack_parse_double
*/
static double php_pack_parse_double(int is_little_endian, void * src)
{
union Copy64 {
double d;
uint64_t i;
} m;
memcpy(&m.i, src, sizeof(double));

#ifdef WORDS_BIGENDIAN
if (is_little_endian) {
m.i = php_pack_reverse_int64(m.i);
}
#else /* WORDS_BIGENDIAN */
if (!is_little_endian) {
m.i = php_pack_reverse_int64(m.i);
}
#endif /* WORDS_BIGENDIAN */

return m.d;
}
/* }}} */

/* pack() idea stolen from Perl (implemented formats behave the same as there except J and P)
* Implemented formats are Z, A, a, h, H, c, C, s, S, i, I, l, L, n, N, q, Q, J, P, f, d, x, X, @.
* Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double.
*/
/* {{{ proto string pack(string format, mixed arg1 [, mixed arg2 [, mixed ...]])
Takes one or more arguments and packs them into a binary string according to the format argument */
Expand Down Expand Up @@ -210,8 +334,12 @@ PHP_FUNCTION(pack)
case 'N':
case 'v':
case 'V':
case 'f':
case 'd':
case 'f': /* float */
case 'g': /* little endian float */
case 'G': /* big endian float */
case 'd': /* double */
case 'e': /* little endian double */
case 'E': /* big endian double */
if (arg < 0) {
arg = num_args - currentarg;
}
Expand Down Expand Up @@ -289,11 +417,15 @@ PHP_FUNCTION(pack)
break;
#endif

case 'f':
case 'f': /* float */
case 'g': /* little endian float */
case 'G': /* big endian float */
INC_OUTPUTPOS(arg,sizeof(float))
break;

case 'd':
case 'd': /* double */
case 'e': /* little endian double */
case 'E': /* big endian double */
INC_OUTPUTPOS(arg,sizeof(double))
break;

Expand Down Expand Up @@ -468,6 +600,26 @@ PHP_FUNCTION(pack)
}
break;
}

case 'g': {
/* pack little endian float */
while (arg-- > 0) {
float v = (float) zval_get_double(&argv[currentarg++]);
php_pack_copy_float(1, &ZSTR_VAL(output)[outputpos], v);
outputpos += sizeof(v);
}

break;
}
case 'G': {
/* pack big endian float */
while (arg-- > 0) {
float v = (float) zval_get_double(&argv[currentarg++]);
php_pack_copy_float(0, &ZSTR_VAL(output)[outputpos], v);
outputpos += sizeof(v);
}
break;
}

case 'd': {
while (arg-- > 0) {
Expand All @@ -477,6 +629,26 @@ PHP_FUNCTION(pack)
}
break;
}

case 'e': {
/* pack little endian double */
while (arg-- > 0) {
double v = (double) zval_get_double(&argv[currentarg++]);
php_pack_copy_double(1, &ZSTR_VAL(output)[outputpos], v);
outputpos += sizeof(v);
}
break;
}

case 'E': {
/* pack big endian double */
while (arg-- > 0) {
double v = (double) zval_get_double(&argv[currentarg++]);
php_pack_copy_double(0, &ZSTR_VAL(output)[outputpos], v);
outputpos += sizeof(v);
}
break;
}

case 'x':
memset(&ZSTR_VAL(output)[outputpos], '\0', arg);
Expand Down Expand Up @@ -537,6 +709,7 @@ static zend_long php_unpack(char *data, size_t size, int issigned, int *map)
* Numeric pack types will return numbers, a and A will return strings,
* f and d will return doubles.
* Implemented formats are Z, A, a, h, H, c, C, s, S, i, I, l, L, n, N, q, Q, J, P, f, d, x, X, @.
* Added g, G for little endian float and big endian float, added e, E for little endian double and big endian double.
*/
/* {{{ proto array unpack(string format, string input)
Unpack binary string into named array elements according to format argument */
Expand Down Expand Up @@ -679,11 +852,15 @@ PHP_FUNCTION(unpack)

/* Use sizeof(float) bytes of input */
case 'f':
case 'g':
case 'G':
size = sizeof(float);
break;

/* Use sizeof(double) bytes of input */
case 'd':
case 'e':
case 'E':
size = sizeof(double);
break;

Expand Down Expand Up @@ -939,18 +1116,37 @@ PHP_FUNCTION(unpack)
}
#endif

case 'f': {
case 'f': /* float */
case 'g': /* little endian float*/
case 'G': /* big endian float*/
{
float v;

memcpy(&v, &input[inputpos], sizeof(float));
if (type == 'g') {
v = php_pack_parse_float(1, &input[inputpos]);
} else if (type == 'G') {
v = php_pack_parse_float(0, &input[inputpos]);
} else {
memcpy(&v, &input[inputpos], sizeof(float));
}

add_assoc_double(return_value, n, (double)v);
break;
}


case 'd': {
case 'd': /* double */
case 'e': /* little endian float */
case 'E': /* big endian float */
{
double v;

memcpy(&v, &input[inputpos], sizeof(double));
if (type == 'e') {
v = php_pack_parse_double(1, &input[inputpos]);
} else if (type == 'E') {
v = php_pack_parse_double(0, &input[inputpos]);
} else {
memcpy(&v, &input[inputpos], sizeof(double));
}
add_assoc_double(return_value, n, v);
break;
}
Expand Down
Loading