PHP - How to validate float number has maximum 2 digits - php

I have to validate if a float number has maximum two digits.
I've tried a lot of methods but all fails in more or lase cases.
Last of them were:
//fails for 2638655.99
private function hasMoreThanTwoDecimals(string $number): bool
{
$number = abs($number);
$intPart = floor($number);
$floatPart = $number - $intPart;
return (strlen($floatPart) > 4);
}
OR
//fails for 36.62
private function hasMoreThanTwoDecimals(string $number): bool
{
return $number * 100 - floor($number * 100) > 0.00001;
}
What other methods do you use?

You can't determine the exact number of decimals with the float datatype, because the internal representation is binary. In binary, fx. 0.1 can not be represented exactly. That's why loops always should have integer increments.
for ($i = -1; $i < 1; $i += 0.1) {
if ($i == 0) {
echo "Zero is here!";
}
}
will never say "Zero is here!" because of binary rounding issues.
Using an Epsilon
You already tried to use an epsilon (a very small value) for thesholding (here a refactored version of your function):
private function hasMoreThanTwoDecimals(string $number): bool
{
$epsilon = 0.00001;
return fmod($number * 100, 1.0) > $epsilon;
}
but fails for some values. In that case, you need to increase your epsilon value.
String Arithmetic
The more precise way is to avoid float and use string representations instead. This is your best option, since - according to your function signature - your numbers are represented as strings already.
private function hasMoreThanTwoDecimals(string $number): bool
{
return bcmod(bcmul($number, '100'), '1.0') != 0;
}
This needs the BCMath module to be included in your PHP. A package supporting BCMath and other solutions is brick/math.
The Cheap Solution
However, if you really just need to probe the number and not are doing calculations, you can get the desired result with pattern matching using preg_match.
private function hasMoreThanTwoDecimals(string $number): bool
{
// Trailing 0 does not add to number of decimals
$number = rtrim($number, '0');
return preg_match('~\.\d\d\d~', $number);
}

You can explode the number using the . delimeter, then you return the length of the second part :
$num = 2638655.99;
echo strlen(explode('.',$num)[1]); // Echo 2

Taking the question literally, if a binary floating point number has a maximum of two decimal digits after the decimal point, the fractional part must be one of .0, .25, .5, or .75.
All other binary floating point numbers really have more decimal digits, although printout formatting may hide them. For example, the closest IEEE 754 64-bit binary number to 2638655.99 is 2638655.99000000022351741790771484375, which has more than two digits after the decimal point.
You could subtract the integer part and then test for the remainder being one of the four possibilities.
Alternatively, the real question may be how to determine whether displaying the number will show no more than two digits after the decimal point. If so, convert to string using the appropriate method, then locate the decimal point and count the digits after it, for example as suggested in this answer.

$res = preg_match("^[+-]?([0]{1}|[1-9]{1}[0-9]*)(\.?[0-9]{1,2})?$", $num) == true;
would be the best solution in my opinion. You can use signs (optional) and enforce that a number starts with only one zero.
Possible:
+0.10
+123.01
-1
123
Not possible:
00.0
0001.0
1.
123.123
Be aware that preg_match returns 0 if no match is found and false if an error occurred (preg_match)

you can use the number_format
number_format($number, 2, '.', '');

Related

What's the most efficient way of randomly picking a floating number within a specific range? [duplicate]

How does one generate a random float between 0 and 1 in PHP?
I'm looking for the PHP's equivalent to Java's Math.random().
You may use the standard function: lcg_value().
Here's another function given on the rand() docs:
// auxiliary function
// returns random number with flat distribution from 0 to 1
function random_0_1()
{
return (float)rand() / (float)getrandmax();
}
Example from documentation :
function random_float ($min,$max) {
return ($min+lcg_value()*(abs($max-$min)));
}
rand(0,1000)/1000 returns:
0.348 0.716 0.251 0.459 0.893 0.867 0.058 0.955 0.644 0.246 0.292
or use a bigger number if you want more digits after decimal point
class SomeHelper
{
/**
* Generate random float number.
*
* #param float|int $min
* #param float|int $max
* #return float
*/
public static function rand($min = 0, $max = 1)
{
return ($min + ($max - $min) * (mt_rand() / mt_getrandmax()));
}
}
update:
forget this answer it doesnt work wit php -v > 5.3
What about
floatVal('0.'.rand(1, 9));
?
this works perfect for me, and it´s not only for 0 - 1 for example between 1.0 - 15.0
floatVal(rand(1, 15).'.'.rand(1, 9));
function mt_rand_float($min, $max, $countZero = '0') {
$countZero = +('1'.$countZero);
$min = floor($min*$countZero);
$max = floor($max*$countZero);
$rand = mt_rand($min, $max) / $countZero;
return $rand;
}
example:
echo mt_rand_float(0, 1);
result: 0.2
echo mt_rand_float(3.2, 3.23, '000');
result: 3.219
echo mt_rand_float(1, 5, '00');
result: 4.52
echo mt_rand_float(0.56789, 1, '00');
result: 0.69
$random_number = rand(1,10).".".rand(1,9);
function frand($min, $max, $decimals = 0) {
$scale = pow(10, $decimals);
return mt_rand($min * $scale, $max * $scale) / $scale;
}
echo "frand(0, 10, 2) = " . frand(0, 10, 2) . "\n";
This question asks for a value from 0 to 1. For most mathematical purposes this is usually invalid albeit to the smallest possible degree. The standard distribution by convention is 0 >= N < 1. You should consider if you really want something inclusive of 1.
Many things that do this absent minded have a one in a couple billion result of an anomalous result. This becomes obvious if you think about performing the operation backwards.
(int)(random_float() * 10) would return a value from 0 to 9 with an equal chance of each value. If in one in a billion times it can return 1 then very rarely it will return 10 instead.
Some people would fix this after the fact (to decide that 10 should be 9). Multiplying it by 2 should give around a ~50% chance of 0 or 1 but will also have a ~0.000000000465% chance of returning a 2 like in Bender's dream.
Saying 0 to 1 as a float might be a bit like mistakenly saying 0 to 10 instead of 0 to 9 as ints when you want ten values starting at zero. In this case because of the broad range of possible float values then it's more like accidentally saying 0 to 1000000000 instead of 0 to 999999999.
With 64bit it's exceedingly rare to overflow but in this case some random functions are 32bit internally so it's not no implausible for that one in two and a half billion chance to occur.
The standard solutions would instead want to be like this:
mt_rand() / (getrandmax() + 1)
There can also be small usually insignificant differences in distribution, for example between 0 to 9 then you might find 0 is slightly more likely than 9 due to precision but this will typically be in the billionth or so and is not as severe as the above issue because the above issue can produce an invalid unexpected out of bounds figure for a calculation that would otherwise be flawless.
Java's Math.random will also never produce a value of 1. Some of this comes from that it is a mouthful to explain specifically what it does. It returns a value from 0 to less than one. It's Zeno's arrow, it never reaches 1. This isn't something someone would conventionally say. Instead people tend to say between 0 and 1 or from 0 to 1 but those are false.
This is somewhat a source of amusement in bug reports. For example, any PHP code using lcg_value without consideration for this may glitch approximately one in a couple billion times if it holds true to its documentation but that makes it painfully difficult to faithfully reproduce.
This kind of off by one error is one of the common sources of "Just turn it off and on again." issues typically encountered in embedded devices.
Solution for PHP 7. Generates random number in [0,1). i.e. includes 0 and excludes 1.
function random_float() {
return random_int(0, 2**53-1) / (2**53);
}
Thanks to Nommyde in the comments for pointing out my bug.
>>> number_format((2**53-1)/2**53,100)
=> "0.9999999999999998889776975374843459576368331909179687500000000000000000000000000000000000000000000000"
>>> number_format((2**53)/(2**53+1),100)
=> "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
Most answers are using mt_rand. However, mt_getrandmax() usually returns only 2147483647. That means you only have 31 bits of information, while a double has a mantissa with 52 bits, which means there is a density of at least 2^53 for the numbers between 0 and 1.
This more complicated approach will get you a finer distribution:
function rand_754_01() {
// Generate 64 random bits (8 bytes)
$entropy = openssl_random_pseudo_bytes(8);
// Create a string of 12 '0' bits and 52 '1' bits.
$x = 0x000FFFFFFFFFFFFF;
$first12 = pack("Q", $x);
// Set the first 12 bits to 0 in the random string.
$y = $entropy & $first12;
// Now set the first 12 bits to be 0[exponent], where exponent is randomly chosen between 1 and 1022.
// Here $e has a probability of 0.5 to be 1022, 0.25 to be 1021, etc.
$e = 1022;
while($e > 1) {
if(mt_rand(0,1) == 0) {
break;
} else {
--$e;
}
}
// Pack the exponent properly (add four '0' bits behind it and 49 more in front)
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
// Now convert to a double.
return unpack("d", $y | $z)[1];
}
Please note that the above code only works on 64-bit machines with a Litte-Endian byte order and Intel-style IEEE754 representation. (x64-compatible computers will have this). Unfortunately PHP does not allow bit-shifting past int32-sized boundaries, so you have to write a separate function for Big-Endian.
You should replace this line:
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
with its big-endian counterpart:
$z = pack("S", $e << 4) . "\0\0\0\0\0\0";
The difference is only notable when the function is called a large amount of times: 10^9 or more.
Testing if this works
It should be obvious that the mantissa follows a nice uniform distribution approximation, but it's less obvious that a sum of a large amount of such distributions (each with cumulatively halved chance and amplitude) is uniform.
Running:
function randomNumbers() {
$f = 0.0;
for($i = 0; $i < 1000000; ++$i) {
$f += \math::rand_754_01();
}
echo $f / 1000000;
}
Produces an output of 0.49999928273099 (or a similar number close to 0.5).
I found the answer on PHP.net
<?php
function randomFloat($min = 0, $max = 1) {
return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
var_dump(randomFloat());
var_dump(randomFloat(2, 20));
?>
float(0.91601131712832)
float(16.511210331931)
So you could do
randomFloat(0,1);
or simple
mt_rand() / mt_getrandmax() * 1;
what about:
echo (float)('0.' . rand(0,99999));
would probably work fine... hope it helps you.

How to multiply small floats with unkown number of decimal places in PHP withouth getting zero because of scientific notation?

I'm trying to multiply some small numbers in PHP, but bcmul is returning zero because the float value is being turned into scientific notation.
I tried using sprintf('%.32f',$value) on the small float values, but since the number of decimal places is unknown, it gets the wrong rounding, and then it'll cause rounding errors when multiplying.
Also, I can't use strpos('e',$value) to find out if it's scientific notation number, because it doesn't finds it even if I cast it as a string with (string)$value
Here's some example code:
$value = (float)'7.4e-5'; // This number comes from an API like this
$value2 = (float)3.65; // Another number from API
echo bcmul($value,$value2); // 0
By default the bc-functions round to 0 decimals. You can change this behavior by either using bcscale or by by changing the bcmath.scale value in your php.ini.
Okay, I found a way to solve it, so, here's how to multiply very small floating point numbers without needing to set an explicit scale for the numbers:
function getDecimalPlaces($value) {
// first we get how many decimal places the small number has
// this code was gotten on another StackOverflow answer
$current = $value - floor($value);
for ($decimals = 0; ceil($current); $decimals++) {
$current = ($value * pow(10, $decimals + 1)) - floor($value * pow(10, $decimals + 1));
}
return $decimals;
}
function multiplySmallNumbers($value, $smallvalue) {
$decimals = getDecimalPlaces($smallvalue); // Then we get number of decimals on it
$smallvalue = sprintf('%.'.$decimals.'f',$smallvalue ); // Since bcmul uses the float values as strings, we get the number as a string with the correct number of zeroes
return (bcmul($value,$smallvalue));
}

How do I truncate a decimal in PHP?

I know of the PHP function floor() but that doesn't work how I want it to in negative numbers.
This is how floor works
floor( 1234.567); // 1234
floor(-1234.567); // -1235
This is what I WANT
truncate( 1234.567); // 1234
truncate(-1234.567); // -1234
Is there a PHP function that will return -1234?
I know I could do this but I'm hoping for a single built-in function
$num = -1234.567;
echo $num >= 0 ? floor($num) : ceil($num);
Yes intval
intval(1234.567);
intval(-1234.567);
Truncate floats with specific precision:
echo bcdiv(2.56789, 1, 1); // 2.5
echo bcdiv(2.56789, 1, 3); // 2.567
echo bcdiv(-2.56789, 1, 1); // -2.5
echo bcdiv(-2.56789, 1, 3); // -2.567
This method solve the problem with round() function.
Also you can use typecasting (no need to use functions),
(int) 1234.567; // 1234
(int) -1234.567; // -1234
http://php.net/manual/en/language.types.type-juggling.php
You can see the difference between intval and (int) typecasting from here.
another hack is using prefix ~~ :
echo ~~1234.567; // 1234
echo ~~-1234.567; // 1234
it's simpler and faster
Tilde ~ is bitwise NOT operator in PHP and Javascript
Double tilde(~) is a quick way to cast variable as integer, where it is called 'two tildes' to indicate a form of double negation.
It removes everything after the decimal point because the bitwise operators implicitly convert their operands to signed 32-bit integers. This works whether the operands are (floating-point) numbers or strings, and the result is a number
reference:
https://en.wikipedia.org/wiki/Double_tilde
What does ~~ ("double tilde") do in Javascript?
you can use intval(number); but if your number bigger than 2147483648 (and your machine/os is x64) all bigs will be truncated to 2147483648. So you can use
if($number < 0 )
$res = round($number);
else
$res = floor($number);
echo $res;
You can shift the decimal to the desired place, intval, and shift back:
function truncate($number, $precision = 0) {
// warning: precision is limited by the size of the int type
$shift = pow(10, $precision);
return intval($number * $shift)/$shift;
}
Note the warning about size of int -- this is because $number is potentially being multiplied by a large number ($shift) which could make the resulting number too large to be stored as an integer type. Possibly converting to floating point might be better.
You could get fancy with a $base parameter, and sending that to intval(...).
Could (should) also get fancy with error/bounds checking.
An alternative approach would be to treat number as a string, find the decimal point and do a substring at the appropriate place after the decimal based on the desired precision. Relatively speaking, that won't be fast.

Random Float between 0 and 1 in PHP

How does one generate a random float between 0 and 1 in PHP?
I'm looking for the PHP's equivalent to Java's Math.random().
You may use the standard function: lcg_value().
Here's another function given on the rand() docs:
// auxiliary function
// returns random number with flat distribution from 0 to 1
function random_0_1()
{
return (float)rand() / (float)getrandmax();
}
Example from documentation :
function random_float ($min,$max) {
return ($min+lcg_value()*(abs($max-$min)));
}
rand(0,1000)/1000 returns:
0.348 0.716 0.251 0.459 0.893 0.867 0.058 0.955 0.644 0.246 0.292
or use a bigger number if you want more digits after decimal point
class SomeHelper
{
/**
* Generate random float number.
*
* #param float|int $min
* #param float|int $max
* #return float
*/
public static function rand($min = 0, $max = 1)
{
return ($min + ($max - $min) * (mt_rand() / mt_getrandmax()));
}
}
update:
forget this answer it doesnt work wit php -v > 5.3
What about
floatVal('0.'.rand(1, 9));
?
this works perfect for me, and it´s not only for 0 - 1 for example between 1.0 - 15.0
floatVal(rand(1, 15).'.'.rand(1, 9));
function mt_rand_float($min, $max, $countZero = '0') {
$countZero = +('1'.$countZero);
$min = floor($min*$countZero);
$max = floor($max*$countZero);
$rand = mt_rand($min, $max) / $countZero;
return $rand;
}
example:
echo mt_rand_float(0, 1);
result: 0.2
echo mt_rand_float(3.2, 3.23, '000');
result: 3.219
echo mt_rand_float(1, 5, '00');
result: 4.52
echo mt_rand_float(0.56789, 1, '00');
result: 0.69
$random_number = rand(1,10).".".rand(1,9);
function frand($min, $max, $decimals = 0) {
$scale = pow(10, $decimals);
return mt_rand($min * $scale, $max * $scale) / $scale;
}
echo "frand(0, 10, 2) = " . frand(0, 10, 2) . "\n";
This question asks for a value from 0 to 1. For most mathematical purposes this is usually invalid albeit to the smallest possible degree. The standard distribution by convention is 0 >= N < 1. You should consider if you really want something inclusive of 1.
Many things that do this absent minded have a one in a couple billion result of an anomalous result. This becomes obvious if you think about performing the operation backwards.
(int)(random_float() * 10) would return a value from 0 to 9 with an equal chance of each value. If in one in a billion times it can return 1 then very rarely it will return 10 instead.
Some people would fix this after the fact (to decide that 10 should be 9). Multiplying it by 2 should give around a ~50% chance of 0 or 1 but will also have a ~0.000000000465% chance of returning a 2 like in Bender's dream.
Saying 0 to 1 as a float might be a bit like mistakenly saying 0 to 10 instead of 0 to 9 as ints when you want ten values starting at zero. In this case because of the broad range of possible float values then it's more like accidentally saying 0 to 1000000000 instead of 0 to 999999999.
With 64bit it's exceedingly rare to overflow but in this case some random functions are 32bit internally so it's not no implausible for that one in two and a half billion chance to occur.
The standard solutions would instead want to be like this:
mt_rand() / (getrandmax() + 1)
There can also be small usually insignificant differences in distribution, for example between 0 to 9 then you might find 0 is slightly more likely than 9 due to precision but this will typically be in the billionth or so and is not as severe as the above issue because the above issue can produce an invalid unexpected out of bounds figure for a calculation that would otherwise be flawless.
Java's Math.random will also never produce a value of 1. Some of this comes from that it is a mouthful to explain specifically what it does. It returns a value from 0 to less than one. It's Zeno's arrow, it never reaches 1. This isn't something someone would conventionally say. Instead people tend to say between 0 and 1 or from 0 to 1 but those are false.
This is somewhat a source of amusement in bug reports. For example, any PHP code using lcg_value without consideration for this may glitch approximately one in a couple billion times if it holds true to its documentation but that makes it painfully difficult to faithfully reproduce.
This kind of off by one error is one of the common sources of "Just turn it off and on again." issues typically encountered in embedded devices.
Solution for PHP 7. Generates random number in [0,1). i.e. includes 0 and excludes 1.
function random_float() {
return random_int(0, 2**53-1) / (2**53);
}
Thanks to Nommyde in the comments for pointing out my bug.
>>> number_format((2**53-1)/2**53,100)
=> "0.9999999999999998889776975374843459576368331909179687500000000000000000000000000000000000000000000000"
>>> number_format((2**53)/(2**53+1),100)
=> "1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
Most answers are using mt_rand. However, mt_getrandmax() usually returns only 2147483647. That means you only have 31 bits of information, while a double has a mantissa with 52 bits, which means there is a density of at least 2^53 for the numbers between 0 and 1.
This more complicated approach will get you a finer distribution:
function rand_754_01() {
// Generate 64 random bits (8 bytes)
$entropy = openssl_random_pseudo_bytes(8);
// Create a string of 12 '0' bits and 52 '1' bits.
$x = 0x000FFFFFFFFFFFFF;
$first12 = pack("Q", $x);
// Set the first 12 bits to 0 in the random string.
$y = $entropy & $first12;
// Now set the first 12 bits to be 0[exponent], where exponent is randomly chosen between 1 and 1022.
// Here $e has a probability of 0.5 to be 1022, 0.25 to be 1021, etc.
$e = 1022;
while($e > 1) {
if(mt_rand(0,1) == 0) {
break;
} else {
--$e;
}
}
// Pack the exponent properly (add four '0' bits behind it and 49 more in front)
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
// Now convert to a double.
return unpack("d", $y | $z)[1];
}
Please note that the above code only works on 64-bit machines with a Litte-Endian byte order and Intel-style IEEE754 representation. (x64-compatible computers will have this). Unfortunately PHP does not allow bit-shifting past int32-sized boundaries, so you have to write a separate function for Big-Endian.
You should replace this line:
$z = "\0\0\0\0\0\0" . pack("S", $e << 4);
with its big-endian counterpart:
$z = pack("S", $e << 4) . "\0\0\0\0\0\0";
The difference is only notable when the function is called a large amount of times: 10^9 or more.
Testing if this works
It should be obvious that the mantissa follows a nice uniform distribution approximation, but it's less obvious that a sum of a large amount of such distributions (each with cumulatively halved chance and amplitude) is uniform.
Running:
function randomNumbers() {
$f = 0.0;
for($i = 0; $i < 1000000; ++$i) {
$f += \math::rand_754_01();
}
echo $f / 1000000;
}
Produces an output of 0.49999928273099 (or a similar number close to 0.5).
I found the answer on PHP.net
<?php
function randomFloat($min = 0, $max = 1) {
return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
var_dump(randomFloat());
var_dump(randomFloat(2, 20));
?>
float(0.91601131712832)
float(16.511210331931)
So you could do
randomFloat(0,1);
or simple
mt_rand() / mt_getrandmax() * 1;
what about:
echo (float)('0.' . rand(0,99999));
would probably work fine... hope it helps you.

What would be the best way to detect if a float has a zero fraction value (e.g. 125.00) in PHP?

See, I want to write a function that takes a float number parameter and rounds the float to the nearest currency value (a float with two decimal places) but if the float parameter has a zero fraction (that is, all zeroes behind the decimal place) then it returns the float as an integer (or i.e. truncates the decimal part since they're all zeroes anyways.).
However, I'm finding that I can't figure out how to determine if if a fraction has a zero fraction. I don't know if there's a PHP function that already does this. I've looked. The best I can think of is to convert the float number into an integer by casting it first and then subtract the integer part from the float and then check if the difference equals to zero or not.
if($value == round($value))
{
//no decimal, go ahead and truncate.
}
This example compares the value to itself, rounded to 0 decimal places. If the value rounded is the same as the value, you've got no decimal fraction. Plain and simple.
A little trick with PHPs type juggling abilities
if ($a == (int) $a) {
// $a has a zero fraction value
}
I think the best way:
if ((string)$value == (int)$value){
...
}
Example:
$value = 2.22 * 100;
var_dump($value == (int)$value); // false - WRONG!
var_dump($value == round($value)); // false - WRONG!
var_dump((string)$value == (int)$value); // true - OK!
function whatyouneed($number) {
$decimals = 2;
printf("%.".($number == (int)($number) ? '0' : $decimals)."F", $number);
}
So basically it's either printf("%.2F") if you want 2 decimals and printf("%.2F") if you want none.
Well, the problem is that floats aren't exact. Read here if you're interested in finding out why. What I would do is decide on a level of accuracy, for example, 3 decimal places, and base exactness on that. To do that, you multiply it by 1000, cast it to an int, and then check if $your_number % 1000==0.
$mynumber = round($mynumber *1000);
if ($mynumber % 1000==0)
{ isInt() }
Just so you know, you don't have to write a function to do that, there's already one that exists:
$roundedFloat = (float)number_format("1234.1264", 2, ".", ""); // 1234.13
If you want to keep the trailing .00, just omit the float cast (although it will return a string):
$roundedFloatStr = number_format("1234.000", 2, ".", ""); // 1234.00

Categories