PHP, Function breaks when numbers are passed in as arguements - php

I have the following function which works well, if I call it with only the first parameter:
function max_months($vehicle_age,$max_peroid,$no_older) {
$tot_age_in = $vehicle_age + 315360000;
while ($tot_age_in > 536467742) {
$tot_age_in = $tot_age_in - 31536000;
if ($tot_age_in < 536467742) {
$max_payback = floatval($tot_age_in - $vehicle_age);
$max_payback = seconds_to_month($max_payback);
break;
}
}
return $max_payback;
}
However, when I alter this function and pass in the numbers seen above as
parameters, the function breaks.
function max_months($vehicle_age,$max_peroid,$no_older) {
$tot_age_in = $vehicle_age + $max_peroid;
while ($tot_age_in > $no_older) {
$tot_age_in = $tot_age_in - $max_peroid;
if ($tot_age_in < $no_older) {
$max_payback = floatval($tot_age_in - $vehicle_age);
$max_payback = seconds_to_month($max_payback);
break;
}
}
return $max_payback;
}
I'm calling the function like so:
$max_payback = max_months($vehicle_age,315360000,536467742);
$vehicle_age is set to 288897248
So in the first instance I return a valid number, however in the second instance I return false, even though the numbers are the same. Could anyone suggest why this might be? Cheers

$max_payback is not always initialized. It's a good habit to always initialize the return value..

It is highly likely that you run out of the PHP_INT_MAX value, you can check the maximum integer value by doing
echo PHP_INT_MAX;
If the variable is bigger than the INT_MAX, it is treated like a float value. This means, that you have to deal with floating point imprecision problems. And instead of checking <, == or >, you should check for a certain range epsilon around the value to be checked.
By changing your code like below, the problem is likely solved:
function max_months($vehicle_age,$max_peroid,$no_older) {
$e = 0.0001;
$tot_age_in = $vehicle_age + $max_peroid;
while ($tot_age_in > $no_older-$e) {
$tot_age_in = $tot_age_in - $max_peroid;
if ($tot_age_in < $no_older+$e) {
$max_payback = floatval($tot_age_in - $vehicle_age);
$max_payback = seconds_to_month($max_payback);
break;
}
}
return $max_payback;
}
See also: http://php.net/manual/en/language.types.integer.php
You did not have that problem when you used the hard coded numbers because they are treated like constants and therefore you did not have the float problem.

Related

Unable to decrypt my encrypted string in a substitution cipher

I am developing my own encryption and decryption algorithms. My encryption function is working fine, but my decryption function is not working properly. The following are my functions:
<?php
function encrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i])-$mode)%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
?>
My expected output is: abcde
But the output returned is: ejUPa
The encryption works in this way:
$new_char_index = ($char_index + $shift) % $alphabet_length;
where the modulo % handles the wrap around so that the new index is still in the alphabet range. This works well for positive numbers, but doesn't work like you would expect it to for negative numbers. Here are some examples:
echo -3 % 7; // -3
echo -11 % 7; // -4
That is why simply changing + for - doesn't work:
$new_char_index = ($char_index - $shift) % $alphabet_length;
This can lead to negative numbers. You can't access arrays with negative numbers in PHP, but you could do that in Python.
The easy fix is to make sure that the resulting index is always a positive numbers. You've tried that with abs(), but the problem is that this doesn't correctly wrap around from the other side of the alphabet, but only removes the sign which is wrong.
An easy fix is adding the alphabet length before the modulo operation in order to get a positive number:
$new_char_index = ($char_index - $shift + $alphabet_length) % $alphabet_length;
This works, because $alphabet_length % $alphabet_length == 0. It wraps to the correct position of the alphabet.
Now you only have to make sure that $shift is already in the alphabet range:
$shift = $shift % $alphabet_length;
One final improvement: you can use the same function for encryption and decryption, because the $enc_shift == -$dec_shift and the last formula should give you work for both.
This is not encryption. This is a variation on a Caeser cipher. Simply put, you should never implement your own encryption (unless you know exactly what you're doing...). This would do for obfuscation and nothing more.
As for the code itself, I suspect its an order of operations issue. Simply replacing a + with a - won't reverse the operator precedence in the encrypt() function. A handy generic string rotation function you could adapt is in the comments of this php documentation page.
If you want encryption there are many good articles about solid encryption; this is my personal opinion of a good starting point.
Here is a solution that works with every $mode and $pattern size.
You have to notice that you can only "encrypt" chars that are contained in $pattern.
<?php
function encrypt($os,$mode=64,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$patternLength = strlen($pattern);
if($mode < 0) {
$mode = ($patternLength - $mode) % $patternLength;
}
if($mode >= $patternLength) {
$mode %= $patternLength;
}
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$patternLength = strlen($pattern);
if($mode < 0) {
$mode = ($patternLength - $mode) % $patternLength;
}
if($mode >= $patternLength) {
$mode %= $patternLength;
}
$ns="";
for($i=0;$i<strlen($os);$i++){
$pos = strpos($pattern,$os[$i]);
if($pos >= $mode ) {
$ns .= $pattern[$pos - $mode];
} else {
// $pos - $mode is negative so we need + sign
$ns .= $pattern[$patternLength + ($pos - $mode)];
}
}
return $ns;
}
To test this, you could do something like that:
$failed = false;
for($mode = -128; $mode <= 128; $mode++) {
// include all possible chars in the test to see if encryption and
// decryption works for all.
$allChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
if(decrypt(encrypt($allChars, $mode), $mode) != $allChars) {
echo "mode ".$mode." failed<br>";
$failed = true;
};
}
if(!$failed) {
echo "success";
}
Fixed it:
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
In your decrypt ^^.
Just a matter of bracket placing.
Full fixed code:
function encrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
Hopefully you can understand the difference yourself.
And now to fix the issue with spaces:
$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"
Take note of the space at the beginning of the string.
An additional note due to comments...
Should you wish to change $mode to let's say 62, you would need to shorten $pattern appropriately.
e.g.
function encrypt($os,$mode=62,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=62,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
Works just fine, notice $pattern is now missing the last 2 characters.

For a given integer Z, check if Z can be written as P^Q where Q and P are positive integers

Here is what I have tried but it is giving me wrong output. Can anyone point out what is the mistake?
function superPower($n) {
$response = false;
$n = abs($n);
if ($n < 2) {
$response = true;
}
for ($i=2;$i<$n;$i++) {
for ($j=2;$j<$n;$j++) {
if (pow($i,$j) == $n) {
$response = true;
}
}
}
return $response;
}
For example if I give it number 25, it gives 1 as output. //Correct
But if I give it 26 it still gives me 1 which is wrong.
By using superPower, you are essentially trying to put a certain defence to the power of an attack to see if it holds up. This can be done much more effectively than through the brute-force method you have now.
function superPower( $hp) { // Niet used Superpower!
if( $hp <= 1) return true;
for( $def = floor(sqrt($hp)); $def > 1; $def--) { // Niet's Defence fell
for( $atk = ceil(log($hp)/log($def)); $atk > 1; $atk--) { // Niet's Attack fell
if( pow($def,$atk) == $hp) return true;
break;
// you don't need the $atk loop, but I wanted to make a Pokémon joke. Sorry.
}
// in fact, all you really need here is:
// $atk = log($hp)/log($def);
// if( $atk-floor($atk) == 0) return true;
}
return false;
}
The maths on the accepted answer is absolutely brilliant, however there are a couple of issues with the solution:
the function erroneously returns true for all of the following inputs: monkey, -3 and 0. (Technically 0 is unsigned, so there is no way of getting it by taking a positive integer to the power of another positive integer. The same goes for any negative input.)
the function compares floating numbers with integers (floor() and ceil() return float), which should be avoided like the plague. To see why, try running php -r '$n = (-(4.42-5))/0.29; echo "n == {$n}\n".($n == 2 ? "OK" : "Surprise")."\n";'
The following solution improves on the idea by fixing all of the above issues:
function superPower($value)
{
// Fail if supplied value is not numeric
if (!is_numeric($value)) {
// throw new InvalidArgumentException("Value is not numeric: $value");
return false;
}
// Normalise numeric input
$number = abs($value);
// Fail if supplied number is not an integer
if (!is_int($number)) {
// throw new InvalidArgumentException("Number is not an integer: $number");
return false;
}
// Exit early if possible
if ($number == 1) {
// 1 to the power of any positive integer is one
return true;
} elseif ($number < 1) {
// X to the power of Y is never less then 1, if X & Y are greater then 0
return false;
}
// Determine the highest logarithm base and work backwards from it
for ($base = (int) sqrt($number); $base > 1; $base--) {
$coefficient = log($number)/log($base);
// Check that the result of division is a whole number
if (ctype_digit((string) $coefficient)) {
return true;
}
}
return false;
}

Cannot get functions to execute

So if the last function ended up working. And I followed all of your instructions to the T, why is this function not working properly? I've looked over it for an hour, tried rewrite it over and over again and all I get is a 0 or no return.
function marketing () {
$newsold = $_POST['newsold'];
$usedsold = $_POST['usedsold'];
$carsSold = $newsold + $usedsold;
$AdSpend = $carsSold * 275;
echo "You shoud spend roughly $AdSpend per year on advertising";
}
marketing();
You echo the values inside the function:
not:
echo "$autoProfit";
but,
<?php
function autoProfits () {
$usedprofit = 1527;
$newprofit = 800;
$newsold = $_POST['newsold'];
$usedsold = $_POST['usedsold'];
$uprofit = $usedsold * $profitused;
$nprofit = $newsold * $profitnew;
$autoProfit = $uprofit + $nprofit;
}
autoProfits();
?>
Take close att with the curly brace where to be placed.
Several things with your code:
1) By default, a form will post with GET and not POST. So either change your PHP variables to $_GET OR change your form's method to $_POST. I prefer to change the method.
<form action="calc.php" method="POST">
2) You're missing a curly brace on your function:
function makeProfit () {
if ($profit >=0) {
echo "Your company is making a profit.";
} else {
echo "Your company is not making a profit.";
}
}
3) In your function adSpend(), you should invert the line for $carsSold.
4) You have used upper and lower-case characters interchangeably in your variable names ($usedSold vs $usedsold). PHP variables are case-sensitive.
5) The "+" operator when used to combine a string and integer may work, but it would be better not to put integers in quotes.
5b) Using a comma will cause PHP to not recognize your variable as a number, so use $profitUsed = 1527; instead of $profitUsed = "1,527";
6) Your variables at the top of a PHP file are not GLOBAL. You'll either need to convert them to global variables, or (I prefer) send them as parameters to your function. An example of the corrected adSpend():
function adSpend ($newSold = 0, $usedSold = 0) {
$adSpendPerCar = 275;
$carsSold = $newSold + $usedSold;
$adSpend = $carsSold * $adSpendPerCar;
echo $AdSpend;
}
adSpend($newSold, $usedSold);
7) Finally, when you expect an integer from user input, you'd be best to verify that you have an integer. There are a lot of ways to do this, one simple method is to do something like this:
$newSold = intval($_POST['newsold']);
$usedSold = intval($_POST['usedsold']);
Edit Change your variables $profitused for $usedprofit and $profitnew for $newprofit
function autoProfits () {
$usedprofit = 1527;
$newprofit = 800;
$newsold = $_POST['newsold'];
$usedsold = $_POST['usedsold'];
$uprofit = $usedsold * $usedprofit;
$nprofit = $newsold * $newprofit;
$autoProfit = $uprofit + $nprofit;
echo $autoProfit;
}
function makeProfit () {
if ($profit >=0) {
echo "Your company is making a profit.";
} else {
echo "Your company is not making a profit.";
}
}
This function is missing the last '}' (curly bracket)
But you should also be sure when calling adSpend(), that the variables you're trying to access is set, else you won't get anything out of it.
Since you've made that setup, then you should be calling the functions right after you've set all the variables, for anything to work.
You're using undefined variables on the products. You defined $usedprofit and $newprofit but you're multiplying $profitused and $profitnew. Since they're not defined, PHP assumes they're 0.

Detecting negative numbers

I was wondering if there is any way to detect if a number is negative in PHP?
I have the following code:
$profitloss = $result->date_sold_price - $result->date_bought_price;
I need to find out if $profitloss is negative and if it is, I need to echo out that it is.
if ($profitloss < 0)
{
echo "The profitloss is negative";
}
Edit: I feel like this was too simple an answer for the rep so here's something that you may also find helpful.
In PHP we can find the absolute value of an integer by using the abs() function. For example if I were trying to work out the difference between two figures I could do this:
$turnover = 10000;
$overheads = 12500;
$difference = abs($turnover-$overheads);
echo "The Difference is ".$difference;
This would produce The Difference is 2500.
I believe this is what you were looking for:
class Expression {
protected $expression;
protected $result;
public function __construct($expression) {
$this->expression = $expression;
}
public function evaluate() {
$this->result = eval("return ".$this->expression.";");
return $this;
}
public function getResult() {
return $this->result;
}
}
class NegativeFinder {
protected $expressionObj;
public function __construct(Expression $expressionObj) {
$this->expressionObj = $expressionObj;
}
public function isItNegative() {
$result = $this->expressionObj->evaluate()->getResult();
if($this->hasMinusSign($result)) {
return true;
} else {
return false;
}
}
protected function hasMinusSign($value) {
return (substr(strval($value), 0, 1) == "-");
}
}
Usage:
$soldPrice = 1;
$boughtPrice = 2;
$negativeFinderObj = new NegativeFinder(new Expression("$soldPrice - $boughtPrice"));
echo ($negativeFinderObj->isItNegative()) ? "It is negative!" : "It is not negative :(";
Do however note that eval is a dangerous function, therefore use it only if you really, really need to find out if a number is negative.
:-)
if(x < 0)
if(abs(x) != x)
if(substr(strval(x), 0, 1) == "-")
You could check if $profitloss < 0
if ($profitloss < 0):
echo "Less than 0\n";
endif;
if ( $profitloss < 0 ) {
echo "negative";
};
Don't get me wrong, but you can do this way ;)
function nagitive_check($value){
if (isset($value)){
if (substr(strval($value), 0, 1) == "-"){
return 'It is negative<br>';
} else {
return 'It is not negative!<br>';
}
}
}
Output:
echo nagitive_check(-100); // It is negative
echo nagitive_check(200); // It is not negative!
echo nagitive_check(200-300); // It is negative
echo nagitive_check(200-300+1000); // It is not negative!
Just multiply the number by -1 and check if the result is positive.
You could use a ternary operator like this one, to make it a one liner.
echo ($profitloss < 0) ? 'false' : 'true';
I assume that the main idea is to find if number is negative and display it in correct format.
For those who use PHP5.3 might be interested in using Number Formatter Class - http://php.net/manual/en/class.numberformatter.php. This function, as well as range of other useful things, can format your number.
$profitLoss = 25000 - 55000;
$a= new \NumberFormatter("en-UK", \NumberFormatter::CURRENCY);
$a->formatCurrency($profitLoss, 'EUR');
// would display (€30,000.00)
Here also a reference to why brackets are used for negative numbers:
http://www.open.edu/openlearn/money-management/introduction-bookkeeping-and-accounting/content-section-1.7
Can be easily achieved with a ternary operator.
$is_negative = $profitloss < 0 ? true : false;
I wrote a Helper function for my Laravel project but can be used anywhere.
function isNegative($value){
if(isset($value)) {
if ((int)$value > 0) {
return false;
}
return (int)$value < 0 && substr(strval($value), 0, 1) === "-";
}
}

How do I save the original argument in a recursive function in PHP?

I'm in PHP working on an Euler problem. I have this function so far:
<?php
$biggest = 0;
$counter = 1;
function test($i){
global $biggest;
global $counter;
if ($i == 1) {
echo "I'm done! Took me $biggest steps";
}
else {
if ($i%2 == 0) {
$counter = $counter + 1;
if ($counter>$biggest) {
$biggest = $counter;
}
test($i/2);
}
else {
$counter = $counter + 1;
if ($counter>$biggest) {
$biggest = $counter;
}
test(3*$i+1);
}
}
}
test(13);
?>
I have the problem mostly licked, but I can't seem to get back at the original input. The question is "When you have a number, if odd get 3n+1, when even, get n/2, do until returns 1. What starting value yields the most "steps" before you get to one?" I currently am returning the number of steps, but I keep resetting $i as I recurse, so I can't record what starting # yielded my $biggest number of steps.
How can I keep track of that number, but also not have it destroyed at the next instance of the loop? (I'll eventually wrap this in a for ($i=1, $i<1000000, $i++) loop)
Thanks!
A common approach is to pass the original argument through each time, so that when eventually you get to your base case, you still have it available. A primitive (and almost entirely unrelated example):
<?php
function fact($n) {
if($n == 1) return 1;
else return $n * fact($n - 1);
}
?>
This is an extremely basic implementation of the factorial function in PHP. Now say you wanted for whatever reason to have the initial value available in the final step: you'd build a wrapper function fact($n) that would call something like memory_fact($n, $initial):
<?php
function fact($n) {
return memory_fact($n, $n);
}
function memory_fact($n, $initial) {
if($n == 1) return 1;
else return $n * memory_fact($n - 1, $initial);
}
?>
This way, memory_fact always knows where it started.
It's easy, just pass it around as a parameter! Here's some python-ish pseudocode:
def func(start, arg):
if foo(arg):
return func(start, arg+1)
else:
return [start, arg]
You don't need the globals; globals are evil. Try returning something useful from test(). Also, you'll find the test() above wastes many cycles. Try using memoization.
Here's a memoization example for calculating Fibonacci numbers:
function fib($n) {
static $data = array(1, 1);
if (!isset($data[$n])) {
$data[$n] = fib($n-1) + fib($n-2);
}
return $data[$n];
}
Note that there are other time-efficent constant-space approaches to handle Fibonacci numbers (including one in O(log n) time), but the Collatz conjecture is a little trickier.

Categories