Why does "hexdec" produce different results? - php

Have played with hex values in PHP, but haven't understand this behavior yet
echo hexdec(0x80); //296
echo hexdec((string)0x80); //296
echo hexdec("0x80"); //128

hexdec() assumes its’s getting a string. In the first you’re taking a number 0x80, which is 128 in decimal, and giving it to it. This means it needs to be made into a string “128” because the default way to convert is the decimal form. When you then want that to be treated as hex it will become 296.
In the last one you actually say “here’s a string of a hex number, convert it to decimal” and the conversion is done.
Numeric literals aren’t handled as hex or dec or anything. They’re just numbers and hexdec() isn’t meant to be used with them. Only strings. This is where implicit conversions between types may cause issues.

here are some differents between string in php internal and at php layer.
0x80 is a numberic literal, it is same at the internal and language layer. it is 128 hexadecimal, convert to decimal is 296.
(string)0x80 looks like it will change to "0x80", but if you echo (string)0x80 it will output 128!!! and we know 0x80 is in Decimal is 128, so (string) action just change it to decimal string expression.
"0x80" is real string expression. it will calculate every charater to hex, so it will be (0 * 16 + x * 16 (igonre) + 8 *16 + 0 * 16) = 128.
all above is the numberic in php literal final store as decimal.
(string)number is also store decimal string expression.

Related

Convert a string containing a number in scientific notation to a normal string in PHP

I need help converting a string that contains a number in scientific notation to a normal string.
Example strings: "9.1892E+11" to "918919850755"
PHP should understand the scientific notation.
By substracting or adding 0 or something, it will change to a normal notation.
By doing this you might lose precision.
You can cast the string to a float to prevent this.
$number = (float)$number;

Why do base_convert, dechex and decbin return unpadded strings in PHP?

Okay, so I understand that in the world of PHP—and this level of programming in general—ordinal values rule. So a number like 18 would simply be returned as 18 when generated in a PHP function. That makes sense to me. Base 10 numbers typically do not get left 0 padding by default.
But then comes along base_convert, dechex & decbin. Which each have the following format where a string is returned:
string base_convert ( string $number , int $frombase , int $tobase )
string dechex ( int $number )
string decbin ( int $number )
Which makes sense because values like these are not ordinal numbers in PHP:
ord 13 becomes hex d
ord 32 becomes bin 100000
Now these are technically correct values. But what doesn’t make sense to me is why each of the returned values doesn’t have left padding like this which seems to be more human readable to me:
ord 13 becomes hex 0d
ord 32 becomes bin 00100000
Is there some logical reason I am missing as to why there isn’t a left 0 padding option for binary & hex numbers generated via base_convert, dechex & decbin?
It seems to me a simple padding option could be added to the interface of each function? Possibly as simple as a true/false with perhaps options similar to str_pad. Or was such an option purposely left off due to the potential formatting rabbit hole of complexity adding such options would create? Meaning, “Just use str_pad since these are strings & not ordinal numbers so do with it as you wish.”

How do I get the value encoded in a SHORT (signed 16 bit number) that is explicitly MSBF in PHP

I need to unpack binary data that is encoded rather exotically: a 32 bit 2's complement bit pattern, representing a SHORT.USHORT decimal fraction, with a signed SHORT integer component and an unsigned SHORT "this many 1/65536 parts" decimal fraction component. To make things even more fun, the sign of the SHORT is determined by the first bit in the 2's complement 32 bit pattern. Not by its sign after decoding to 'real' bit pattern.
An example of this would be the following:
2's complement bit pattern: 11111111110101101010101010101100
converted 'normal' pattern: 00000000001010010101010101010100
SHORT bits (upper 16): 0000000000101001 (decimal: 41)
USHORT bits (lower 16: 0101010101010100 (decimal: 21844)
actual number encoded: -41.333 (41, negative from high MSB + 21844/65536)
(if you think this scheme is insane: it certainly seems that way, doesn't it? It's the byte format used in Type2 fonts that are encoded in a CFF block, or "compact font format" block. Crazy as it is, this format is set in stone, and we're about 20 years too late to have it changed. This is the byte layout in a CFF font, and the only thing we get to worry about now is how to correctly decode it)
Problems occur when we're dealing with patterns like these:
2's complement bit pattern: 00000000000000000000000000000001
converted pattern: 11111111111111111111111111111111
upper 16 bits: 1111111111111111 (decimal 65535 *OR* -1)
lower 16 bits: 1111111111111111 (decimal 65535)
SHORT.USHORT number: -65536 *OR* 1
Depending on who you ask, the pattern 1111111111111111 can be decoded either as 65535, such as when interpreted as a bit pattern in a larger (32 or 64 bit) number, or as -1, when interpreted as a 16 bit signed integer. The only correct interpretation here, however, is as the latter, so this leads us to the question's subject line:
what PHP code do I use to turn this 16 bit pattern into the correct number, given that PHP has no pack/unpack parameter for unpacking as 16 bit int with the most significant bit first? There is a parameter for unpacking a 16 bit int using machine-indicated byte order, but this is going to give problems because font data storage is non-negotiable: all fonts, allwhere, everywhen, must be encoded using Motorola/Big Endian byte ordering, irrespective of the machine's preferred byte ordering.
My code to going from 32-bit 2's complement to final value at the moment is this:
// read in 32 bit pattern, represenging a 2's complement pattern
$p2c = 0x01000000 * $b[x] + 0x010000 * $b[x+1] + 0x0100 * $b[x+2] + $b[x+3];
// convert 2's complement to plain form
$p = (~$p2c + 1) & 0xFFFFFFFF;
// get lower 16 bits, representing an unsigned short.
// due to unsigned-ness, this values is always correct.
$ushort = 0xFFFF & $p;
// get higher 16 bits, representing a signed short.
// due to its sign, this value can be spectacularly wrong!
$short = ($p >> 16);
// "reconstitute" the FIXED format number
$num = - ($short + round($ushort/65536,3));
This had a pretty simple answer that I completely ignored for no good reason, and of course didn't think of until I wrote this question.
$short = $pattern >> 16;
if($short >= 32768) { $short -= 65536; }
and voila.

Casting zero padded numeric strings to integers

I am padding integers for barcodes with leading zeros so they have same number of characters, for example:
1 -> 00000001
12 -> 00000012
1044 -> 00001044
00000001 is numeric and when casting to an integer it is 1.
Will this work as expected for all integers?
Careful, numbers starting with 0 are treated as base 8 in PHP
>> var_dump(011);
int(9)
however explicit casting string to int seems to be safe
>> var_dump((int)'011');
int(11)
You did not say anything where you're going to be storing these numbers, but in case it will be a database, here's some advice regarding datatype:
Barcode numbers are not (despite the name) numbers. Same goes for fax numbers, social security numbers, etc. You should not store these as numeric data (for example using MySQL's INT or DECIMAL) datatype. Instead use textual datypes (like CHAR or VARCHAR)
Unless you treat it as a string PHP will interpret 00000001 as simply the integer 1. If you want to treat your barcodes as a string, take care to cast them correctly because PHP might interpret them as integers due to type juggling.
Will this work as expected for all ints?
That depends on what you expect. Left-padding with zeros will never change the value. Regardless of the number, you can add any number of zeros to the left of the string and it will always be numeric, and it will always cast to the same integer.
(int)"01" == 1
(int)"0001" == 1
(int)"000000001" == 1
etc.

How to generate a 128-bit long string?

Basically, I'm looking for a function to perform the following
generateToken(128)
which will return a 128-bit string consisting of integers or alphabet characters.
Clarification: From the comments, I had to change the question. Apparently, I am looking for a string that is 16 characters long if it needs to be 128 bits.
Is there a reason you must restrict the string to integers? That actually makes the problem a lot harder because each digit gives you 3.3 bits (because 2^3.3 ~= 10). It's tricky to generate exactly 128 bits of token in this manner.
Much easier is to allow hexadecimal encoding (4 bits per character). You can then generate 128 genuine random bits, then encode them in hex for use in your application. Base64 encoding (6 bits per character) is also useful for this kind of thing.
openssl_random_pseudo_bytes will give you a string of random bytes that you can use bin2hex to encode, otherwise you can use mt_rand in your own token-generation routine.
EDIT: After reading the updates to the question it seems that you want to generate a token that represents 128 bits of data and the actual string length (in characters) is not so important. If I guess your intention correctly (that this is a unique ID, possibly for identification/authentication purposes) then I'd suggest you use openssl_random_pseudo_bytes to generate the right number of bits for your problem, in this case 128 (16 bytes). You can then encode those bits in any way you see fit: hex and base64 are two possibilities.
Note that hex encoding will use 32 characters to encode 128 bits of data since each character only encodes 4 bits (128 / 4 = 32). Base64 will use 22 characters (128 / 6 = 21.3). Each character takes up 8 bits of storage but only encodes 4 or 6 bits of information.
Be very careful not to confuse encoded string length with raw data length. If you choose a 16-character string using alphanumeric characters (a-z, A-Z, 0-9) then you only get 6 bits of information per character (log base 2 of 62 is nearly 6), so your 16-character string will only encode 96 bits of information. You should think of your token as an opaque byte array and only worry about turning it into / from a character string when you actually try to send it over the wire or put it in a cookie or whatever.
As of PHP 5.3:
$rand128 = bin2hex(openssl_random_pseudo_bytes(16));
What is your purpose?
If you just want a unique id, then use uniqid:
http://www.php.net/manual/en/function.uniqid.php
Its not random, its essentially a hex string based on microtime. If you do uniqid('', true), then it will return a hex string based on microtime as well as tack on a bunch of random numbers on the end of the id (so even if two calls come in on the same microsecond, it is unlikely that they'll share a unique id).
If you need a 16-character string exactly, then what purpose? Are you salting passwords? How random should the string be? All in all, you can always just do:
$toShow = array();
for($i = 0; $i<16; $i++){
$toShow[] = chr(mt_rand(ord('a'), ord('z')));
}
return $toShow
Now this creates a string of characters that are between 'a' and 'z'. You can change "ord('a')" to 0, and "ord('z')" to 255 to get a fully random binary string... or any other range you need.

Categories