Require 2 decimal places after the point in PHP? - php

I'm working with currencies so for example -
5 is OK as it is interpreted as 5.00. But 5.005 is not as it has too many digits after the point.
How can I restrict the amount of digits and show an error if there's too many?
Thanks

$x = '5.005'; // declare as string to avoid floating point errors
$parts = explode('.', $x);
if (strlen($parts[1]) > 2) {
die("Too many digits");
}

number_format will correct it for you, but if you want to error when too much precision is provided, you will need to test it.
$x = 12.345;
if ($x != number_format($x, 2)) {
// error!
}

You can format the number like this:
$num = 50695.3043;
$num = number_format( $num, 2, '.' );
echo $num;
This will result in:
50695.30
Note that this rounds. So 1.566 would round to 1.57.

I usually use sprintf
$formatted = sprintf("%01.2f", $price);
But there are many other functions / solutions you could use.

The following code will capture a number of things from a user-entered string:
too many decimal points, such as 1.2.3.
more than two digits in second section (if there), such as 1.234.
any non-numerics in first or second section, such as 123.4a or 1a3.45.
both first and second section empty, such as ..
$x = '12.34';
$parts = explode('.', $x);
$nm0a = preg_match ('/^[0-9]*$/', $parts[0]);
$nm0b = preg_match ('/^[0-9]+$/', $parts[0]);
$nm1a = preg_match ('/^[0-9]*$/', $parts[1]);
$nm1b = preg_match ('/^[0-9]+$/', $parts[1]);
if (count ($parts) > 2) { die ("Too many decimal points"); }
if ($nm0a == 0) { die ("Non-numeric first part"); }
if ($nm1a == 0) { die ("Non-numeric second part"); }
if (($nm0b == 0) && ($nm1b == 0)) { die ("Both parts empty"); }
if (strlen ($parts[1]) > 2) { die ("Too many digits after decimal point"); }
die ("Okay"); # Only here to provide output.

Related

Generate a list of IPv6 addresses within a given range

In Maria DB table I have two varbinary(16) fields that represent IPv6 addresses for the start and end of the IPv6 range.
I want to use PHP to loop between this range and generate each IPv6 address that's within the range. I tried to turn binary to decimal to do the loop and increase the decimal number, but the loop does not produce any iteration.
Any help?
//The $IpStart_v6_FromDb/$IpStart_v6_End Vars are produced with INET6_ATON MariaDB function
$IpStartBin = decbin($IpStart_v6_FromDb);
$IpEndBin = decbin($IpEnd_v6_FromDb);
$ArIpRange = array();
$ArIpRange[] = $IpStartBin;
$x=0;
for(;;)
{
if ($IpStartBin==$IpEndBin) break;
$tLastIpBin = $ArIpRange[$x];
$tNextIpBin = decbin( (bindec($tLastIpBin) + 1) );
if ($tNextIpBin==$IpEndBin) break;
$ArIpRange[] = $tNextIpBin;
$x++;
}
foreach ($ArIpRange as $v)
{
echo "<br>IP range item:<br>".base64_encode($v); //debug
}
[EDIT]
I'm embarrassed to say that I thought the length of an IPv6 address is 64 bits.
So, some simple troubleshooting or reading the manual would have told you that decbin expects an integer as input. So right off the bat you're getting a zero back for both variables.
Furthermore, even if you did correct that problem (by using bindec,) you're talking about 128 bit numbers which, unless you're from the future, are not something PHP can handle natively.
I'd suggest handling them as strings. First normalize them (fill in missing zeroes and replace :: with zeroes) using code from this answer, find and remove the matching prefix using code from this answer, and then deal with the rest by converting them to much smaller numbers.
And, as mentioned in the comments, make sure you don't try dealing with too big a range or you will make your server unhappy.
<?php
// https://stackoverflow.com/a/55521768/1255289
// Generate a list of IPv6 addresses within a given range
function expand_ipv6(string $ip): ?string
{
// https://stackoverflow.com/a/12095836/1255289
$hex = unpack("H*", inet_pton($ip))[1] ?? "";
return (strlen($hex) === 32) ? implode(":", str_split($hex, 4)) : null;
}
$IpStart_v6_FromDb = "2001:db8::1234";
$IpEnd_v6_FromDb = "2001:db8::1299";
$ip1 = expand_ipv6($IpStart_v6_FromDb);
$ip2 = expand_ipv6($IpEnd_v6_FromDb);
if ($ip1 === null || $ip2 === null) {
die;
}
// https://stackoverflow.com/a/35838357/1255289
// length is 39 to account for 7 colons
for ($i = 0; $i < 39 && $ip1[$i] === $ip2[$i]; $i++);
$ipv6_prefix = substr($ip1, 0, $i);
$ipv6_start = hexdec(substr($ip1, $i));
$ipv6_end = hexdec(substr($ip2, $i));
if (strlen($ipv6_prefix) < 26) {
// adjust this to requirements to prevent too large ranges
die;
}
for ($a = $ipv6_start; $a <= $ipv6_end; $a++) {
$hex = dechex($a);
printf("%s%s\n", $ipv6_prefix, implode(":", str_split($hex, 4)));
}

How to round down to the nearest significant figure in php

Is there any slick way to round down to the nearest significant figure in php?
So:
0->0
9->9
10->10
17->10
77->70
114->100
745->700
1200->1000
?
$numbers = array(1, 9, 14, 53, 112, 725, 1001, 1200);
foreach($numbers as $number) {
printf('%d => %d'
, $number
, $number - $number % pow(10, floor(log10($number)))
);
echo "\n";
}
Unfortunately this fails horribly when $number is 0, but it does produce the expected result for positive integers. And it is a math-only solution.
Here's a pure math solution. This is also a more flexible solution if you ever wanted to round up or down, and not just down. And it works on 0 :)
if($num === 0) return 0;
$digits = (int)(log10($num));
$num = (pow(10, $digits)) * floor($num/(pow(10, $digits)));
You could replace floor with round or ceil. Actually, if you wanted to round to the nearest, you could simplify the third line even more.
$num = round($num, -$digits);
If you do want to have a mathy solution, try this:
function floorToFirst($int) {
if (0 === $int) return 0;
$nearest = pow(10, floor(log($int, 10)));
return floor($int / $nearest) * $nearest;
}
Something like this:
$str = (string)$value;
echo (int)($str[0] . str_repeat('0', strlen($str) - 1));
It's totally non-mathy, but I would just do this utilizing sting length... there's probably a smoother way to handle it but you could acomplish it with
function significant($number){
$digits = count($number);
if($digits >= 2){
$newNumber = substr($number,0,1);
$digits--;
for($i = 0; $i < $digits; $i++){
$newNumber = $newNumber . "0";
}
}
return $newNumber;
}
A math based alternative:
$mod = pow(10, intval(round(log10($value) - 0.5)));
$answer = ((int)($value / $mod)) * $mod;
I know this is an old thread but I read it when looking for inspiration on how to solve this problem. Here's what I came up with:
class Math
{
public static function round($number, $numberOfSigFigs = 1)
{
// If the number is 0 return 0
if ($number == 0) {
return 0;
}
// Deal with negative numbers
if ($number < 0) {
$number = -$number;
return -Math::sigFigRound($number, $numberOfSigFigs);
}
return Math::sigFigRound($number, $numberOfSigFigs);
}
private static function sigFigRound($number, $numberOfSigFigs)
{
// Log the number passed
$log = log10($number);
// Round $log down to determine the integer part of the log
$logIntegerPart = floor($log);
// Subtract the integer part from the log itself to determine the fractional part of the log
$logFractionalPart = $log - $logIntegerPart;
// Calculate the value of 10 raised to the power of $logFractionalPart
$value = pow(10, $logFractionalPart);
// Round $value to specified number of significant figures
$value = round($value, $numberOfSigFigs - 1);
// Return the correct value
return $value * pow(10, $logIntegerPart);
}
}
While the functions here worked, I needed significant digits for very small numbers (comparing low-value cryptocurrency to bitcoin).
The answer at Format number to N significant digits in PHP worked, somewhat, though very small numbers are displayed by PHP in scientific notation, which makes them hard for some people to read.
I tried using number_format, though that needs a specific number of digits after the decimal, which broke the 'significant' part of the number (if a set number is entered) and sometimes returned 0 (for numbers smaller than the set number).
The solution was to modify the function to identify really small numbers and then use number_format on them - taking the number of scientific notation digits as the number of digits for number_format:
function roundRate($rate, $digits)
{
$mod = pow(10, intval(round(log10($rate))));
$mod = $mod / pow(10, $digits);
$answer = ((int)($rate / $mod)) * $mod;
$small = strstr($answer,"-");
if($small)
{
$answer = number_format($answer,str_replace("-","",$small));
}
return $answer;
}
This function retains the significant digits as well as presents the numbers in easy-to-read format for everyone. (I know, it is not the best for scientific people nor even the most consistently length 'pretty' looking numbers, but it is overall the best solution for what we needed.)

Convert Fraction to Percent

In our Learning Management System someone in their infinite wisdom decided to keep non-standardized grades. As a result we have a table similar to this:
Assignment 1 - 100
Assignment 2 - 80
Assignment 3 - 10/20
Assignment 4 - 68
Assignment 5 - 8/10
As you can see we have a mixture of percentages and fractions. What i'd like to do is check if the grade is a fraction i.e. 10/20 and if so convert it out to a percentage. Are there any built in php functions for either action? I was thinking of doing a strpos('/'/, $grade); to check if it was a fraction but is there a cleaner way? Additionally to break up the fraction and convert it to a decimal my initial thought was to explode the fraction grade on a / and do (array[1] * 100) / array[2].
Is there any better solution than the one i am thinking?
if(is_nan($grade)) {
if(strpos('/',$grade) !== false) {
$numbers = explode($grade,'/');
$percent = (((int)$numbers[0] / (int)$numbers[1])*100).'%';
} else {
echo "Not a valid grade!";
}
} else {
$percent = $grade.'%';
}
i believe that should work
dont have to deal with pesky regex either
A quick function which you can just pass the values to:
function normal($number)
{
$parts = explode("/", $number);
return count($parts) > 1 ? ($parts[0] * 100) / $parts[1] : $parts[0];
}
Array index starts at zero, not one.
$array = explode('/', $str, 2);
if (count($array) === 2) {
$grade = sprintf('%.2f', 100 * $array[0] / $array[1]);
} else {
$grade = $str;
}
I'd do it like such:
public function convertFractions($givenValue){
if(strpos($givenValue, "/") !== false){
$strings = explode("/", $givenValue);
return 100 * ($strings[0] / $strings[1]);
} else {
return $givenValue;
}
}
My one caveat would be: I'm not sure if the backslash requires escaping, as I've done here, as I didn't have time to test completely. If not, remove the backslash, and you should get the required values from the function every time.
Oh, thats a nice one, you can do it "easy" via regex:
$lines = array(
"10/20",
"20/10",
"90",
);
foreach($lines as $line){
if(preg_match('#([\d]+)/([\d]+)#',$line,$matches)){
print (($matches[1]*100)/$matches[2])."% ";
}else{
print $line."% ";
}
}
returns 50% 200% 90%

Serial Number Checker

I am trying to create a serial number checker.
Serial Numbers are in ranges
A87594 - A92778
AB34534 - AC23405
B23933 - C344444
I was able to get the numbers to work using range() for the first serial number example, I'm guessing I need to use explode() but I wasn't sure how to explode the letters into a variable and the numbers into a seperate variable.
if($_POST['submit']) {
$snum = $_POST['serial_number'];
// 1952
$jan01_jan07 = range(87594, 92478);
if (in_array($snum, $jan01_jan07)) {
echo 'You have a 1952 Widget';
}
else {
echo 'Your serial number is unknown';
}
}
You can try using strcmp, as it checks two strings, so you can check whether the incoming data is equal to or more than the lower bound and less than or equal to the upper bound, like this:
$data = $_POST['data']; // change this accordingly
if(strcmp($data, $lowerBound) >= 0 && strcmp($data, $upperBound) <= 0) {
// successful match
}
As strcmp returns -1, 0, 1 if $data is before, the same as and after $lowerBound (dictionary ordered), so this works for strings as well.
Try something along these lines:
preg_match('/([A-C]+)(\d+)/', $serial, $matches);
list(, $characters, $numbers) = $matches;
From there is kind of depends on the exact rules that govern your serials, something along these lines should do:
if ($characters == 'A' && 87594 <= $numbers && $numbers <= 92778) {
return true;
} else if ($characters == 'AB' …) ...

PHP convert decimal into fraction and back?

I want the user to be able to type in a fraction like:
1/2
2 1/4
3
And convert it into its corresponding decimal, to be saved in MySQL, that way I can order by it and do other comparisons to it.
But I need to be able to convert the decimal back to a fraction when showing to the user
so basically I need a function that will convert fraction string to decimal:
fraction_to_decimal("2 1/4");// return 2.25
and a function that can convert a decimal to a faction string:
decimal_to_fraction(.5); // return "1/2"
How can I do this?
Sometimes you need to find a way to do it and rounding is acceptable. So if you decide what range of rounding works out for you you can build a function like this. To convert a decimal into the fraction that it most closely matches. You can extend the accuracy by adding more denominators to be tested.
function decToFraction($float) {
// 1/2, 1/4, 1/8, 1/16, 1/3 ,2/3, 3/4, 3/8, 5/8, 7/8, 3/16, 5/16, 7/16,
// 9/16, 11/16, 13/16, 15/16
$whole = floor ( $float );
$decimal = $float - $whole;
$leastCommonDenom = 48; // 16 * 3;
$denominators = array (2, 3, 4, 8, 16, 24, 48 );
$roundedDecimal = round ( $decimal * $leastCommonDenom ) / $leastCommonDenom;
if ($roundedDecimal == 0)
return $whole;
if ($roundedDecimal == 1)
return $whole + 1;
foreach ( $denominators as $d ) {
if ($roundedDecimal * $d == floor ( $roundedDecimal * $d )) {
$denom = $d;
break;
}
}
return ($whole == 0 ? '' : $whole) . " " . ($roundedDecimal * $denom) . "/" . $denom;
}
I think I'd store the string representation too, as, once you run the math, you're not getting it back!
And, here's a quick-n-dirty compute function, no guarantees:
$input = '1 1/2';
$fraction = array('whole' => 0);
preg_match('/^((?P<whole>\d+)(?=\s))?(\s*)?(?P<numerator>\d+)\/(?P<denominator>\d+)$/', $input, $fraction);
$result = $fraction['whole'] + $fraction['numerator']/$fraction['denominator'];
print_r($result);die;
Oh, for completeness, add a check to make sure $fraction['denominator'] != 0.
To can use PEAR's Math_Fraction class for some of your needs
<?php
include "Math/Fraction.php";
$fr = new Math_Fraction(1,2);
// print as a string
// output: 1/2
echo $fr->toString();
// print as float
// output: 0.5
echo $fr->toFloat();
?>
Here is a solution that first determines a valid fraction (although not necessarily the simplest fraction). So 0.05 -> 5/100. It then determines the greatest common divisor of the numerator and denominator to reduce it down to the simplest fraction, 1/20.
function decimal_to_fraction($fraction) {
$base = floor($fraction);
$fraction -= $base;
if( $fraction == 0 ) return $base;
list($ignore, $numerator) = preg_split('/\./', $fraction, 2);
$denominator = pow(10, strlen($numerator));
$gcd = gcd($numerator, $denominator);
$fraction = ($numerator / $gcd) . '/' . ($denominator / $gcd);
if( $base > 0 ) {
return $base . ' ' . $fraction;
} else {
return $fraction;
}
}
# Borrowed from: http://www.php.net/manual/en/function.gmp-gcd.php#69189
function gcd($a,$b) {
return ($a % $b) ? gcd($b,$a % $b) : $b;
}
This includes a pure PHP implementation of the gcd although if you are sure the gmp module is installed you could use the one that comes with gcd.
As many others have noted you need to use rational numbers. So if you convert 1/7 to a decimal then try to convert it back to a decimal you will be out of luck because the precision lost will prevent it from getting back to 1/7. For my purposes this is acceptable since all the numbers I am dealing with (standard measurements) are rational numbers anyway.
Buddies, can this help?
[]s
function toFraction($number) {
if (!is_int($number)) {
$number = floatval($number);
$denominator = round(1 / $number);
return "1/{$denominator}";
}
else {
return $number;
}
}
Little improvement on above, but keepin it simple.
function dec2frac($f) {
$base = floor($f);
if ($base) {
$out = $base . ' ';
$f = $f - $base;
}
if ($f != 0) {
$d = 1;
while (fmod($f, 1) != 0.0) {
$f *= 2;
$d *= 2;
}
$n = sprintf('%.0f', $f);
$d = sprintf('%.0f', $d);
$out .= $n . '/' . $d;
}
return $out;
}
An approach would be to retrieve the decimal value and multiply it by 2, 3, 4 and so on until you get an integer number.
However, I'd stick with the answer given by Derek. Guess what happens when a user inserts n/(n+1) with n high. Such an algorithm would have to scan all the numbers up to n+1.
Not to mention it is likely you'll end up with approximation problems.
You'll have to face a serious problem, because floats are not precise enough.
When you'll have to deal with 1.3333, PHP will make an estimate of this value... So you will never be able to convert it to 1 1/3.
It seems to be simple to overcome, but if you want your program to differentiate 1/7901 (~ 1,2656625743576762435134793064169e-4) with 1/7907 (~
1,2647021626406981155937776653598e-4) precisely... this will be a real hell !!
IMHO, if you want to deal with maths, you should rely on an external library... or try to make PHP communicate with Matlab.
If you want to know more, i suggest you dig in floating point problems... Starting with wikipedia.
A variation of Jir's approach could actually work if only a limited amount of denominators are used : multiply everything by the least common denominators (and round the result to discard any remaining decimals due to approximation).
I.e.: if you only have to deal with halfs, thrids and quarters, just multiply everything by 12.
And also if you know the common denominator, this should greatly reduce the search speed by knowing exactly which numbers to search instead of searching all n+1 possible.
If you have to deal with lots of unusual fractions, like 1/7, 1/13, etc. well, stick to Derek's solution and store the original value too.
The fraction to decimal is quite straightforward and there are lots of solutions. I'd go with trimming the string, replacing spaces with '+', and anything other than space,/,. or digits with '' then running it through 'eval'.
The decimal to fraction is virtually impossible to do correctly - not least because your decimal fraction would probably have to be converted to binary first - at which point you loose a lot of precision. As an academic exercise.....If you can live with the difference between 20976/41953 and 1/2 then you could try a fuzzy match for a predefined number of fractions:
(there's probably a neater way of implementing the same algorithm - but I'll leave that as an exercise for the reader).
define('DECIMAL_DIGITS',5);
function decimal_2_frac($inp_decimal)
{
static $fracs;
if (!is_array($fracs)) {
init_fracs($fracs);
}
$int_part=(integer)$inp_decimal;
$inp_decimal=$inp_decimal-$int_part;
$candidate='';
$distance=10;
foreach ($fracs as $decimal=>$frac) {
if (abs($decimal-$inp_decimal)<$distance) {
$candidate=$frac;
$distance=abs($decimal-$inp_decimal);
}
if (abs($decimal-$inp_decimal)>$distance) {
break;
}
}
return $int_part . ' ' . $candidate;
}
function init_fracs(&$fracs)
{
$fracs=array();
for ($x=2;$x<(5*DECIMAL_DIGITS);$x++) {
// there's probably a beter way to calculate the loop limit
for ($y=1; $y<$x; $y++) {
$decimal=round($y/$x,DECIMAL_DIGITS);
$frac="$x/$y";
if (!array_key_exists($decimal,$fracs)) {
$fracs[$decimal]=$frac;
}
}
}
}
But personally, I'd just store the original representation in a seperate field in the database.
function dec2frac($f)
{
$d = 1
while (fmod($f, 1) != 0.0) {
$f *= 2;
$d *= 2;
}
$n = sprintf('%.0f', $f);
$d = sprintf('%.0f', $d);
return array($n, $d);
}
Then $f == $n / $d
For example:
print_r(dec2frac(3.1415926));
Outputs:
Array
(
[0] => 3537118815677477 // $n
[1] => 1125899906842624 // $d
)
I made a blog post with a couple solutions for this, the most recent approach I took is:
http://www.carlosabundis.com/2014/03/25/converting-decimals-to-fractions-with-php-v2/
function dec2fracso($dec){
//Negative number flag.
$num=$dec;
if($num<0){
$neg=true;
}else{
$neg=false;
}
//Extracts 2 strings from input number
$decarr=explode('.',(string)$dec);
//Checks for divided by zero input.
if($decarr[1]==0){
$decarr[1]=1;
$fraccion[0]=$decarr[0];
$fraccion[1]=$decarr[1];
return $fraccion;
}
//Calculates the divisor before simplification.
$long=strlen($decarr[1]);
$div="1";
for($x=0;$x<$long;$x++){
$div.="0";
}
//Gets the greatest common divisor.
$x=(int)$decarr[1];
$y=(int)$div;
$gcd=gmp_strval(gmp_gcd($x,$y));
//Calculates the result and fills the array with the correct sign.
if($neg){
$fraccion[0]=((abs($decarr[0])*($y/$gcd))+($x/$gcd))*(-1);
}else{
$fraccion[0]=(abs($decarr[0])*($y/$gcd))+($x/$gcd);
}
$fraccion[1]=($y/$gcd);
return $fraccion;
}
Just adding a bit more logic to Derek's accepted answer - check for "division by zero" and whole number input check.
function fractionToDec($input) {
if (strpos($input, '/') === FALSE) {
$result = $input;
} else {
$fraction = array('whole' => 0);
preg_match('/^((?P<whole>\d+)(?=\s))?(\s*)?(?P<numerator>\d+)\/(?P<denominator>\d+)$/', $input, $fraction);
$result = $fraction['whole'];
if ($fraction['denominator'] > 0)
$result += $fraction['numerator'] / $fraction['denominator'];
}
return $result;
}
function frac2dec($fraction) {
list($whole, $fractional) = explode(' ', $fraction);
$type = empty($fractional) ? 'improper' : 'mixed';
list($numerator, $denominator) = explode('/', $type == 'improper' ? $whole : $fractional);
$decimal = $numerator / ( 0 == $denominator ? 1 : $denominator );
return $type == 'improper' ? $decimal : $whole + $decimal;
}
Use a 3rd party library, for example:
https://packagist.org/packages/phospr/fraction
Usage:
$fraction = Fraction::fromFloat(1.5);
echo "Fraction is: " . $fraction->getNumerator() . '/' . $fraction->getDenominator();
echo "Float is: " . $fraction->toFloat();
I usually do a quick search on https://packagist.org to see if something already exists to solve what I'm trying to do, if so then I can take advantage of the many hours that the community have already put into solving the problem (this will be much much more time than I'll be able to dedicate to it) and it will also be more likely to be bug free, having been battle tested by others and maybe even have a test suite covering it.
Saves time and results in higher quality.

Categories