I have the following php snippet
$newData = serialize(array('ep' => 50733372961735.4));
echo "New data: " . print_r($newData, 1);
Output:
New data: a:1:{s:2:"ep";d:5.07333729617E+13;}
But I would like the float value as it is and not E+13.
What could I do without having to make drastic changes as this is just an example. In my actual code the 'ep' value could be inside a complex array hierarchy
Firstly, a general note: serialize should never be used on data that could be manipulated in any way. It's useful for things like session data and caches, but should not be relied on for transporting data between applications or data storage. In many cases, you're better off using a standard serialization format like JSON.
You also certainly shouldn't care what the serialized string looks like - the only thing you should do with that string is pass it back to unserialize(). So the fact that there is E+13 is not a problem if the actual value it gives back when you unserialize is the one you wanted.
However, it's clear in your example that you have lost precision - the last digits are ...29617 rather than ...29617354 - so back to the point: there is a PHP setting serialize_precision, described in the manual here. It's default value has varied over the years, but setting it to an explicit value other than -1 will serialize floats with that number of significant figures:
ini_set('serialize_precision', 2);
echo serialize(50733372961735.4), PHP_EOL;
// d:5.1E+13;
ini_set('serialize_precision', 20);
echo serialize(50733372961735.4), PHP_EOL;
// d:50733372961735.398438;
Note that the first example has clearly thrown away information, whereas the second has actually stored more precision than you realised you had - because of the inaccuracy of storing decimals in binary floating point format.
The problem is not the serialize at all!
it's numbers in general.
$num = 50733372961735.4;
print($num);
=> 50733372961735
to "solve" this problem you can use:
ini_set('serialize_precision', 15);
I just executed your code on www.writephponline.com
I got the below value if I did not put your value in string :- $newData = serialize(array('ep' => 50733372961735.4));
New data: a:1:{s:2:"ep";d:50733372961735.398;}
and this after adding it in string :- $newData = serialize(array('ep' => '50733372961735.4'));
New data: a:1:{s:2:"ep";s:16:"50733372961735.4";}
Related
I've read all over about arithmetic regarding floating point numbers, but I'm just trying to accurately store the darn things.
I have a mysql field with the type of DECIMAL (40,20).
I am saving a value in php of 46457.67469999996. After updating the record with this value, the end result is 46457.67470000000000000000. Not sure why it's being rounded at all just being saved to the database.
The value is not being converted to a string or anything beforehand. The field value that is passed into PDO is the value I expected to be saved and it is returned as a float... Perhaps it's because I'm saving a PHP float to a mysql decimal type where the rounding is occurring?
What am I missing here?
EDIT: Added example code that has the issue
// Query placeholder variables. Hard-coded for the example test
$query_vars = array(
":vendor_id" => 33154,
":year" => 2018,
":coop_committed_dollar" => 46457.67469999996,
":coop_committed_dollar_update" => 46457.67469999996
);
$statement = " INSERT INTO vendor_data_yearly
(vendor_id, year, coop_committed_dollar) VALUES
(:vendor_id, :year, :coop_committed_dollar)
ON DUPLICATE KEY UPDATE
coop_committed_dollar = :coop_committed_dollar_update;";
$query = $connection->conn->prepare($statement);
$query->execute($query_vars);
When I run this, the resulting value of coop_committed_dollar is 46457.67470000000000000000. This code is legit all I am doing.
Possible solution
// Note that I am casting the string using the BC Math library.
// I dunno how to just initialize the number (lame documentation), so I'm adding 0 to it.
$number = "46457.674699999967";
$number = bcadd("46457.674699999967", 0, 20);
$query_vars = array(
":vendor_id" => 33154,
":year" => 2018,
":coop_committed_dollar" => $number,
":coop_committed_dollar_update" => $number
);
$statement = " INSERT INTO vendor_data_yearly
(vendor_id, year, coop_committed_dollar) VALUES
(:vendor_id, :year, :coop_committed_dollar)
ON DUPLICATE KEY UPDATE
coop_committed_dollar = :coop_committed_dollar_update;";
$query = $conn->prepare($statement);
$query->execute($query_vars);
This results in the number as expected in the DB.
ONLY SOLUTION I FOUND TO WORK CORRECTLY
The data I am working with is passed in via ajax. I had to take a few steps to get this to work correctly.
Use ini_set('precision', 20);
Manually set the data in question to be a string BEFORE sending it via ajax so PHP would not round it, extended with extra floating point madness, padd it, etc.
I found that PHP would just not let me reliably work with large numbers coming from a variable set outside the script's scope (ajax). Once PHP got it's hands on the number, it would do what it had to do in order to make it make sense as a float.
If anyone has a better solution for this particular scenario I'm all ears and eyes :)
The problem is that PHP's precision is not allowing you to store the exact number you think you are storing.
When you set ":coop_committed_dollar" => 46457.67469999996
PHP is actually storing it as a different value, depending on the precision.
The solution is to store the value in PHP as a string instead of a float.
Since your question is: "what am I missing", I will try to provide an answer.
Basically it comes down to storing floats internally using binary representation. Since 46457.67469999996 cannot be exactly in binary (it ends up with an infinite number, similar to 33% (.3333...) in base-10), the closest rounding is used based on PHP's precision (set in php ini).
I was given a great explanation in this question that I asked a while back.
In your particular case, it also seems that the value that you are sending via AJAX is being stored as a float when parsed by PHP on the server-side. You want it to be stored as a string instead. If you're using json_decode, add this option: JSON_BIGINT_AS_STRING.
As noted in the PHP documentation, when json_decodeing a data structure containing long integers, they'll be converted to floats. The workaround is to use JSON_BIGINT_AS_STRING, which preserves them as strings instead. When json_encodeing such values, JSON_NUMERIC_CHECK will encode those numbers back into large integers:
$json = '{"foo":283675428357628352}';
$obj = json_decode($json, false, JSON_BIGINT_AS_STRING);
$json2 = json_encode($obj, JSON_NUMERIC_CHECK);
var_dump($json === $json2); // true
Using this method for a correct roundtrip of the data is prone to errors. If a property contains '123', a numeric string which should stay a string, it will be encoded to an integer.
I want to get an object from the server, modify one property and than put the entire data structure back. I need to preserve the original types. I don't want to maintain properties other than the one I'm manipulating.
Is there any real workaround for this? PHP does not have any issues with big ints anymore, but the json_decode routine seems to be outdated.
As long as your PHP version can actually handle large integers, meaning if you're running a 64-bit version of PHP (on something other than Windows), json_decode has no problem with it:
$json = '{"foo":9223372036854775807}';
$obj = json_decode($json);
$json2 = json_encode($obj);
var_dump(PHP_INT_MAX, $obj, $json2);
int(9223372036854775807)
object(stdClass)#1 (1) {
["foo"]=>
int(9223372036854775807)
}
string(27) "{"foo":9223372036854775807}"
If the integer values you need to handle do exceed PHP's PHP_INT_MAX, you simply cannot represent them in PHP native types. So there's no way around the conundrum you have; you cannot use native types to track the correct type, and you cannot substitute other types (e.g. strings instead of integers), because that's ambiguous when encoding back to JSON.
In this case you will have to invent your own mechanism of tracking the correct types for each property and handle such serialisation with a custom encoder/decoder. For example, you'd need to write a custom JSON decoder which can decode to a custom class like new JsonInteger('9223372036854775808'), and your custom encoder would recognise this type and encode it to a JSON 9223372036854775808 value.
There's no such thing built into PHP.
For what it's worth, PHP can support values > PHP_INT_MAX using the bcmath package http://php.net/manual/en/book.bc.php but JSON is a slightly more difficult issue.
To answer the OP's question of why they can't encode the value from a string back to an int type in the JSON, the answer lies in the conversion step. When reading the original JSON string in, it's a string, and read byte by byte. When reading values, they're initially read as a string (as the JSON itself if a string), and later cast to the correct type to an int or a float depending upon the presence of a period (.). If the value is greater than PHP_INT_MAX then PHP converts it to a double, and you lose precision. Thus using JSON_BIGINT_AS_STRING will tell the parser to keep the value as a string and NOT try to cast it, everything should be good, the value is kept in tact, albeit a string.
The problem comes when doing the inverse, and doing json_encode($value, JSON_NUMERIC_CHECK) tells PHP to cast string numeric values into either int/float, but this appears to happen BEFORE writing to the JSON string, causing values > PHP_INT_MAX to be converted into a double representation like 9.2233720368548e+19
See https://3v4l.org/lHL62 or below:
$bigger_than_max = '{"max": ' . PHP_INT_MAX . '1}'; // appending 1 makes > PHP_INT_MAX
var_dump($bigger_than_max);
var_dump(json_decode($bigger_than_max));
var_dump(json_decode($bigger_than_max, false, 512, JSON_BIGINT_AS_STRING));
var_dump(json_encode(json_decode($bigger_than_max, false, 512, JSON_BIGINT_AS_STRING)));
var_dump(json_encode(json_decode($bigger_than_max, false, 512, JSON_BIGINT_AS_STRING), JSON_NUMERIC_CHECK));
Result:
string(29) "{"max": 92233720368547758071}"
object(stdClass)#1 (1) {
["max"]=>
float(9.2233720368548E+19)
}
object(stdClass)#1 (1) {
["max"]=>
string(20) "92233720368547758071"
}
string(30) "{"max":"92233720368547758071"}"
string(29) "{"max":9.223372036854776e+19}"
Unfortunately, it doesn't appear that there is a way to solve this, looking at the JSON constants http://php.net/manual/en/json.constants.php I don't see anything that allows you to write integer values > PHP_INT_MAX into ints within the JSON.
Sorry this doesn't find a solution but hopefully clears up some confusion.
I'm executing the following code in a PHP script:
$obj = new \stdClass();
$obj->lat = 0.000011399388312455;
echo json_encode($obj) . "\n";
The output of the script is
{"lat":1.1399388312454999446e-5}
As you can see, the number is represented in exponential notation.
Is there some way to represent that particular number in extended notation, in the JSON-serialized object?
The desired output is
{"lat":0.000011399388312455}
The version of PHP is 5.6.30.
P.S.: This JSON-serialized object will be sent to an external web service that doesn't accept numbers in exponential form. Moreover, it needs to be a number and not a string ({"lat":"0.000011399388312455"} won't work).
The number should not be rounded (it's a geographical coordinate).
Here are other similar questions, but they don't fit my case unfortunately.
php- floating point number shown in exponential form
Remove the "E" in a number format for very small numbers
Thank you in advance.
It is implemented in PHP 7.1 https://wiki.php.net/rfc/precise_float_value
As a work around, use other JSON serializers not a default json_encode.
E notation is valid form of float representation. In php >7.1 with precision set to -1 results of your code would look like: {"lat":1.1399388312455e-5} which is absolutely the same as 0.000011399388312455 because you have 4 leading zeros.
If you still want to have leading zeroes in json i would go towards strings. They are perfectly fine for coordinates representations.
I have to send the JSON:
{"val": 5000.00}
Neither {"val": "5000.00"} nor {"val": 5000} is correct format.
json_encode() converts 5000.00 to 5000
Is it possible to send correct json's format (two zeros) with json_encode from
array("val" => (float) 5000.00) ?
No, it's not possible, because what you think are "correct" and "incorrect" values are actually the same value. It's purely a rendering decision to show/hide trailing zeros.
It's up to you at display time to render the value with the correct number of decimal places. You can't force a floating point number to be stored or transfered with a certain number of decimals.
If you really really need to do that you may consider two options:
Option nr. 1:
You build up the json econded string by your own. If the data you have to encode have a simple structure and that structure is not subject to change in the future the task is easy. You'll have a slower script so another requirement is that the data is not too much.
For a single object like the example you posted...
$json = sprintf('{"val": %.2f}', $floatValue);
Of course for structured data, like an array of objects, arrays of arrays... you'll have to write the necessary loops, place accurately , : [ ] { } ". Hope I gave you the idea...
Option nr. 2:
You build up the data you have to encode just as you would if you didn't have the weird requirement you asked for. But store float values into strings that will also contains some pattern characters.
For example you'll store 5000.00 as "##5000.00##"
Encode the data with json_ecode.
Permorm string replacements to eliminate "## and ##"
$floatValue = 5000.00;
$data = array("val" => sprintf("##%.2f##", $floatValue));
$json = json_encode($data);
$json = str_replace('"##', '', $json);
$json = str_replace('##"', '', $json);
Choose your pattern characters (## in the example) carefully to avoid conflicts with other strings that may contain the same pattern elsewere in you data to be encoded.
just preg_replace('/:"(\d+\.\d+)",/', ':$1,', json_encode($a, JSON_FORCE_OBJECT))
I'm using FQL to retrieve a list of users from Facebook. For consistency I get the result as JSON. This causes a problem - since the returned JSON encodes the user IDs as numbers, json_decode() converts these numbers to floating point values, because some are too big to fit in an int; of course, I need these IDs as strings.
Since json_decode() does its own thing without accepting any behavior flags, I'm at a loss. Any suggestions on how to resolve this?
json_decode() can convert large integers to strings, if you specify a flag in the function call:
$array = json_decode($json, true, 512, JSON_BIGINT_AS_STRING)
I've resolved the issue by adding &format=json-strings to my the FQL api call, like so:
$myQuery = "SELECT uid2 FROM friend WHERE uid1=me()";
$facebook->api("/fql?q=" . urlencode($myQuery) . "&format=json-strings")
This tells facebook to wrap all the numbers in quotes, which leads json_decode to use neither
int-s not floats.
Because I was afraid this issue is not restricted to FQL but to all graph API calls that choose to represent some of the IDs as BIG-INTs I've went as far as patching facebook's PHP SDK a bit to force Facebook to return all of its numbers as strings.
I've added this one line to the _graph function.
This would be line 738 in facebook_base.php, version 3.1.1
$params['format'] = 'json-strings';
Sure fix
I had a similar problem where json_decode was converting recent twitter/tweet IDs into exponential numbers.
Björn's answer is great if you want your BIGINT to become a string - and have PHP 5.3+. If neither of those things are true, another option is to up PHP's float precision.
This can be done a different few ways...
find the precision value in your php.ini and change it to precision = 20
add ini_set('precision', 20); to your PHP app
add php_value precision 20 to your app's .htaccess or virtual host file
Quick and dirty, seems to work for now :
$sJSON = preg_replace('/:(\d+)/', ':"${1}"', $sJSON);
I use this and it works almost great.
json_decode(preg_replace('/("\w+"):(\d+)/', '\\1:"\\2"', $jsonString), true)
The json breaks when there is geo data included, eg. {"lat":54.2341} results in "lat":"54".2341
Solution:
$json = preg_replace('/("\w+"):(\d+)(.\d+)?/', '\\1:"\\2\\3"', $json);
This (preg_replace('/("\w+"):(\d+)(.\d+)?/', '\\1:"\\2\\3"', $json);) worked for me (for parsing result from facebook api)
A major oversight on PHP's part is that browsers don't choke on integers starting with BigInt (64 bits), but before that (53 bits). The introduction of BigInt to modern browsers doesn't help much, when JSON does not support it. So I am trying to remedy that.
My approach is to handle the decoded array (before potentially re-encoding it to a string):
function fix_large_int(&$value)
{
if (is_int($value) && $value > 9007199254740991)
$value = strval($value);
}
$json_arr = json_decode($json_str, flags: JSON_BIGINT_AS_STRING | JSON_OBJECT_AS_ARRAY);
echo('before: ' . json_encode($json_arr) . '<br />' . PHP_EOL);
array_walk_recursive($json_arr, 'fix_large_int');
echo('after: ' . json_encode($json_arr) . '<br />' . PHP_EOL);
The number 9007199254740991 is what JavaScript has as its Number.MAX_SAFE_INTEGER value. Read about that here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
(By the way, I got here, because Twitter API's stringify_ids parameter for blocks/ids and friends/ids does not work for me. There seems to be no mention of that anywhere I could find, though.)