PHP saving precision float into MYSQL ends up with rounded resulting field - php

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.

Related

How to convert WEI to ETHER php

I need to convert string like 0x2fe84e3113d7b to the float type. This string comes from infura.io API as balance of the account. I've tried to use https://github.com/mbezhanov/ethereum-converter, but it makes no sense in this case (it always returns 0.00000 in any way). How to convert this string to 0.000842796652117371 with php?
use Bezhanov\Ethereum\Converter;
...
$this->converter = new Converter();
$weiValue = '0x1e1e83d93bb6ebb88bbaf';
dump($this->converter->fromWei($weiValue)); // returns 0.00000000000000000000000000000000000
$hexValue = hexdec($weiValue); // returns 2.2757423599815E+24
dump($this->converter->fromWei($hexValue)); // returns the same
I guess it caused by too long value on $hexValue (I mean converter can't convert long integers as it). But how to get the ether value from this hex?
https://www.investopedia.com/terms/w/wei.asp
1 Ether = 1,000,000,000,000,000,000 Wei (10^18)
and since this is currency, storing it as a floating point would be asinine, so it's got to be a 64-bit integer there.
Deleted my overwrought answer for a simple:
var_dump(
$wei = hexdec("0x2fe84e3113d7b"),
$wei / pow(10, 18)
);
Output:
int(842796652117371)
float(0.000842796652117370993)
Which, coincidentally, also illustrates why you don't want to use floats for currency. Also, WFM.
Still doesn't explain why you have:
$hexValue = hexdec($weiValue); // returns 2.2757423599815E+24
Quoted in your example as that's several orders of magnitude wrong for the supposed input.

Passing integer to php $_GET via url

So I'm doing some CTF and one of the challenge is about php type juggling. The code looks something like this
if($_GET['var1'] == hash('md4', $_GET['var1']))
{
//print flag
}
So I was 80% sure that I need to pass in an integer so it'll be true but all I can manipulate is the url. I tried using python request to do it but still failed.
import requests
url = 'http://example.com'
params = dict(var1=0)
r = requests.get(url=url, params=params)
Is there any special case where php will treat the variable pass into GET array as int? Or there is other way to do so?
The answer to this CTF can be found here https://medium.com/#Asm0d3us/part-1-php-tricks-in-web-ctf-challenges-e1981475b3e4
The general idea is to find a value which starts with 0e and its hash also starts with 0e. The page gives an example of 0e001233333333333334557778889 which produces a hash of 0e434041524824285414215559233446
This notation is called scientific notation. To understand why this kind of type juggling works you need to understand what both numbers are. If we convert from scientific notation to decimal it becomes obvious.
0e001233333333333334557778889 = 0×101233333333333334557778889
0e434041524824285414215559233446 = 0×10434041524824285414215559233446
From primary school maths we know that anything multiplied by 0 is 0, which means both numbers are 0.
All input provided in the $_GET or $_POST superglobals is of type string.
Thanks to PHP's type juggling system both strings are treated as floating point numbers written in scientific notation and when cast to float they both equal 0.

Need to cast string value representing a 0.XXXX floats to actual number value in PHP

So I am trying to cast a string value of ie: '0.0000143' to actual FLOAT or DECIMAL number value (so it must not be a string after conversion), but the actual number of 0.0000143 as it needs to be sent through some API call and the API requires it to be structured like that and not have an actual string value.
Can anyone help me achieve this (or is it actually even possible to do)?. Since I am out of ideas
To make things more clear, I have tried all the type casting possibilities there are in PHP (it is not working as I do not need something like 1.4E-5 returned to me, but the actual value like 0.0000143).
You can use Type casting in order to achieve that:
$string = '0.0000143';
$intValue = (int)$string;
$floatValue = (float)$string;

php serialize huge float causes rounding and formatting issues

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";}

PHP equivalent for Excel's COMBIN(N,K) function?

I am aware that the function may be computed manually using factorials, the problem is that larger numbers will not calculate properly.
For example, if I input COMBIN(1500,5) in MS Excel it will return 62,860,358,437,800 as it should. But if I try to calculate it manually, even in Excel I will get a #NUM! error when I try to first find the factorial of 1500. The manual formula would be
1500!/5!(1500-5)!
I find it curious that Excel's COMBIN function calculates properly yet the manual way returns an error. In short, I am wondering if there is an equivalent of this function in PHP? I have already tried manually computing using gmp_fact and as with Excel, it returns error (NaN).
Thanks
Your calculation is failing because you're quickly overflowing the Integer data type, which is probably just 32-bits on your system. You could use the arbitrary precision math functions to get around that problem:
http://www.php.net/manual/en/ref.gmp.php
If gmp_fact(...) seems to be returning errors or bad results, you're probably passing it a bad value or assuming it's result is a basic numeric type. You'll want to use something like gmp_strval to convert the returned GMP resource into something readable when you're done performing calculations.
Example:
$a = gmp_fact(1500);
$b = gmp_fact(5);
$c = gmp_fact(1500-5);
$result = gmp_div($a,(gmp_mul($b,$c)));
echo gmp_strval($result);

Categories