I need to check which hexadecimal flags are included in a certain decimal variable. At the moment the script just works for small decimals but I've some longer ones.
In the following example it shows me the right flags when I check the flags inside $decimal2, but it doesn't work with $decimal.
I don't know the reason for this behaviour and what has to be changed.
This is the example demo.
$decimal = 613090029426844e18; // doesn't work
$decimal2 = 64; //works
const FLAG_1 = 0x1;
const FLAG_2 = 0x2;
const FLAG_3 = 0x4;
const FLAG_4 = 0x4;
const FLAG_5 = 0x32;
const FLAG_6 = 0x40;
const FLAG_7 = 0x400;
function show_flags ($decimal) {
if ($decimal & FLAG_1) {
echo "Flag 1 included.<br>\n";
}
if ($decimal & FLAG_2) {
echo "Flag 2 included.<br>\n";
}
if ($decimal & FLAG_3) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_4) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_5) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_6) {
echo "Flag 3 included.<br>\n";
}
if ($decimal & FLAG_7) {
echo "Flag 3 included.<br>\n";
}
}
show_flags($decimal);
First of all, none of the flags are set in your $decimal so it is expected to print nothing. Even if you did set those flags, let say by adding 0xfff to $decimal, it still won't print anything because the number is too large to be stored as an int, so it is stored as a float, which has limited precision. You can see this with var_dump()
$decimal = 613090029426844e18; // doesn't work
$decimal2 = 64; //works
var_dump($decimal);
var_dump($decimal2);
Output:
float(6.13090029426844E+32)
int(64)
A floating point number only stores the most significant digits (roughly 14 digits), any digit in the lower significant places are inevitably discarded.
$decimal3 = 613090029426844e18 + 0xfff;
Output:
float(6.13090029426844E+32)
So you won't be able to use the lower significant digits in a large number. You may want to take a look at BigInterger class Is there a BigInteger class in PHP?
Related
Why is this not keeping the rounding on the $artWidthCM and $artHeightCM when I am adding it to the object?
If I echo out the values I get what is expected: '0.305'.
But when adding it to $artObj it prints as '0.304999999999999993338661852249060757458209991455078125'.
if ($result->num_rows > 0) {
$artObj = new stdClass();
while($row = $result->fetch_assoc()) {
//Dimensions
$dimenionsStrip = str_replace(' ', '', $row["Dimensions"]);
$dimensionsArr = explode("x", $dimenionsStrip);
$artWidthInch = 12; // $dimensionsArr[0];
$artHeightInch = 12; // $dimensionsArr[1];
$artWidthCM = (round(($artWidthInch * 2.54) * 2) / 2) / 100;
$artHeightCM = (round(($artHeightInch * 2.54) * 2) / 2) / 100;
// Build Object
$artObj->id = $row["ArtID"];
$artObj->artwidth = $artWidthCM;
$artObj->artheight = $artHeightCM;
}
$artJSON = json_encode($artObj, JSON_PRETTY_PRINT);
echo '['.$artJSON.']';
} else {
echo "No Artwork Found";
}
Prints JSON as follows:
[{ "id": "35628", "artwidth": 0.304999999999999993338661852249060757458209991455078125, "artheight": 0.304999999999999993338661852249060757458209991455078125 }]
On my local machine with exact same code it prints as:
[{ "id": "35628", "artwidth": 0.305, "artheight": 0.305 }]
This is a problem of floating point precision. See the Warning at https://www.php.net/manual/en/language.types.float.php
Highlight from the link:
So never trust floating number results to the last digit, and do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.
The floating point handling may be different on different machines, that's why it behaves "correctly" on one and "incorrectly" on other.
I'm trying to write an algorithm to convert a 64Bit integer to 32Bit via truncation, and return a value accurately representing a 32Bit value. The obvious problem is that integers in PHP are only 64Bit (barring 32Bit systems).
I first tried the following:
$x = $v & 0xFFFFFFFF
So if $v = PHP_INT_MAX, $x is 4294967295, when really I want -1. I know the reason for this is that when represented as a 64Bit integer, PHP prepends the zeros to the last 32 bits, and thats why I get a positive number.
My solution so far is this:
function convert64BitTo32Bit(int $v): int
{
$v &= 0xFFFFFFFF;
if ($v & 0x80000000) {
return $v | 0xFFFFFFFF00000000;
}
return $v;
}
And I'm pretty sure its right. The problem I have with this is that it requires that if statement to inspect whether the truncated number is negative. I was really hoping for a bitwise only solution. It may not be possible, but I thought I'd ask.
EDIT:
The problem can be simplified to just part of the solution. i.e. if the first bit is 1, make all bits 1, if first bit is 0 make all bits 0.
# example assumes input is the LSBs of an 8Bit integer.
# scale for 64Bit and the same solution should work.
op(1010) = 1111
op(0101) = 0000
op(0000) = 0000
op(1000) = 1111
op(0001) = 0000
I would be able to use the LSBs to derive this value, then mask it onto the MSBs of the 64Bit integer. This is what I'm trying to figure out now, though I'm trying to avoid creating a monster equation.
There's probably a more elegant/efficient way using bitwise operations, but you can also force the conversion with pack() and unpack(). Eg: Pack as unsigned, unpack as signed.
function overflow32($in) {
return unpack('l', pack('i', $in & 0xFFFFFFFF))[1];
}
var_dump( overflow32(pow(2,33)-1) ); // int(-1)
I'm curious what you're applying this to, because I had to break a hash function with this same thing some years ago when I moved an app from a 32 bit machine to a 64 bit machine, but I can't remember what it was.
Edit: Wow for some reason I remembered Two's Complement being hard. Literally just invert and add one.
function overflow32($in) {
if( $in & 0x80000000 ) {
return ( ( ~$in & 0xFFFFFFFF) + 1 ) * -1;
} else {
return $in & 0xFFFFFFFF;
}
}
var_dump( kludge32(pow(2,33)-1) );
Edit 2: I saw that you want to extend this to arbitrary bit lengths, in which case you just need to calculate the masks instead of explicitly setting them:
function overflow_bits($in, $bits=32) {
$sign_mask = 1 << $bits-1;
$clamp_mask = ($sign_mask << 1) - 1;
var_dump(
decbin($in),
decbin($sign_mask),
decbin($clamp_mask)
);
if( $in & $sign_mask ) {
return ( ( ~$in & $clamp_mask) + 1 ) * -1;
} else {
return $in & $clamp_mask;
}
}
var_dump(
overflow_bits(pow(2, 31), 32),
overflow_bits(pow(2, 15), 16),
overflow_bits(pow(2, 7), 8)
);
I left in the debug var_dump()s for output flavor:
string(32) "10000000000000000000000000000000"
string(32) "10000000000000000000000000000000"
string(32) "11111111111111111111111111111111"
string(16) "1000000000000000"
string(16) "1000000000000000"
string(16) "1111111111111111"
string(8) "10000000"
string(8) "10000000"
string(8) "11111111"
int(-2147483648)
int(-32768)
int(-128)
I think I've cracked it:
function overflow32Bit(int $x): int
{
return ($x & 0xFFFFFFFF) | ((($x & 0xFFFFFFFF) >> 31) * ((2 ** 32) - 1) << 32);
}
var_dump(overflow32Bit(PHP_INT_MAX)); // -1 correct
var_dump(overflow32Bit(PHP_INT_MAX - 1)); // -2 correct
var_dump(overflow32Bit(PHP_INT_MIN)); // 0 correct
var_dump(overflow32Bit((2 ** 31) - 1)); // 2147483647 correct
var_dump(overflow32Bit((2 ** 31))); // -2147483647 correct
var_dump(overflow32Bit(0xFFFFFFFF)); // -1 correct
var_dump(overflow32Bit(0x7FFFFFFF)); // 2147483647 correct
The solution was actually staring me in the face. Get the value of the first bit, then multiply it by the max value of unsigned 32bit integer.
If someone can come up with a better or shorter solution, I'll also accept that.
PS. This is only for 32Bit, i also intend to use this proof for 16Bit and 8Bit.
Expanded, $x is input, $z is output.
$x = PHP_INT_MAX;
$y = (2 ** 32) - 1;
$z = ($x & $y) | ((($x & $y) >> 31) * ($y << 32));
var_dump($z);
I am having a variety of long numbers and I am trying to write a function to format them correctly. Can someone help me out?
I already tried "number_format()" and "round()" but that doesn't solve my problems..
I would like to round it like following:
1024.43 --> 1,024.43
0.000000931540 --> 0.000000932
0.003991 --> 0.00399
0.3241 --> 0.324
1045.3491 --> 1,045.35
So that means, If number is bigger than "0" it should round to 2 decimal places and add thousands seperator (like 6,554.24) AND if number less than "1" it should round to 3 digits whenever numbers appear after the zeros (for example 0.0003219 to 0.000322 OR 0.2319 to 0.232)
EDIT:
The same should apply to "-" values. For example:
-1024.43 --> -1,024.43
-0.000000931540 --> -0.000000932
-0.003991 --> -0.00399
-0.3241 --> -0.324
-1045.3491 --> -1,045.35
Adapting from https://stackoverflow.com/a/48283297/2469308
handle this in two separate cases.
for numbers between -1 and 1; we need to calculate the number of digits to round. And then, using number_format() function we can get the result.
for else, simply use number_format() function with decimal digits set to 2.
Try the following:
function customRound($value)
{
if ($value > -1 && $value < 1) {
// define the number of significant digits needed
$digits = 3;
if ($value >= 0) {
// calculate the number of decimal places to round to
$decimalPlaces = $digits - floor(log10($value)) - 1;
} else {
$decimalPlaces = $digits - floor(log10($value * -1)) - 1;
}
// return the rounded value
return number_format($value, $decimalPlaces);
} else {
// simply use number_format function to show upto 2 decimal places
return number_format($value, 2);
}
// for the rest of the cases - return the number simply
return $value;
}
Rextester DEMO
$x = 123.456;
echo number_format($x, max(2, 3 - ceil(log10(abs($x))))) . "\n";
$x = 1.23456;
echo number_format($x, max(2, 3 - ceil(log10(abs($x))))) . "\n";
$x = 0.0123456;
echo number_format($x, max(2, 3 - ceil(log10(abs($x))))) . "\n";
$x = 0.0000123456;
echo number_format($x, max(2, 3 - ceil(log10(abs($x))))) . "\n";
$x = 0.000000123456;
echo number_format($x, max(2, 3 - ceil(log10(abs($x))))) . "\n";
$x = 0.00000000123456;
echo number_format($x, max(2, 3 - ceil(log10(abs($x))))) . "\n";
Output:
123.45
1.23
0.0123
0.0000123
0.000000123
0.00000000123
Basically this always keeps a minimal of 2 decimal digits up to 3 significant digits.
However, because of the way floating point is handled internally (as power of 2 and not 10), there are some catches. Numbers like 0.1 and 0.001 and so forth can't be stored precisely, so they are actually stored as 0.09999999... or things like that. In cases like this it may seem like it is computing things wrong and give you answers with more significant digits than it should.
You can try to counteract this phenomena by allowing an error margin to the formula:
number_format($x, max(2, 3 - ceil(log10(abs($x))) - 1e-8))
But this may cause other undesirable effects. You will have to make tests.
I'm trying to convert a 64-bit float to a 64-bit integer (and back) in php. I need to preserve the bytes, so I'm using the pack and unpack functions. The functionality I'm looking for is basically Java's Double.doubleToLongBits() method. http://docs.oracle.com/javase/7/docs/api/java/lang/Double.html#doubleToLongBits(double)
I managed to get this far with some help from the comments on the php docs for pack():
function encode($int) {
$int = round($int);
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($int & $left) >>32;
$r = $int & $right;
return unpack('d', pack('NN', $l, $r))[1];
}
function decode($float) {
$set = unpack('N2', pack('d', $float));
return $set[1] << 32 | $set[2];
}
And this works well, for the most part...
echo decode(encode(10000000000000));
100000000
echo encode(10000000000000);
1.1710299640683E-305
But here's where it gets tricky...
echo decode(1.1710299640683E-305);
-6629571225977708544
I have no idea what's wrong here. Try it for yourself: http://pastebin.com/zWKC97Z7
You'll need 64-bit PHP on linux. This site seems to emulate that setup: http://www.compileonline.com/execute_php_online.php
$x = encode(10000000000000);
var_dump($x); //float(1.1710299640683E-305)
echo decode($x); //10000000000000
$y = (float) "1.1710299640683E-305";
var_dump($y); //float(1.1710299640683E-305)
echo decode($y); //-6629571225977708544
$z = ($x == $y);
var_dump($z); //false
http://www.php.net/manual/en/language.types.float.php
... never trust
floating number results to the last digit, and do not compare floating
point numbers directly for equality. If higher precision is necessary,
the arbitrary precision math functions and gmp functions are
available. For a "simple" explanation, see the » floating point guide
that's also titled "Why don’t my numbers add up?"
It is working properly, the only problem in this case is in logic of:
echo decode(1.1710299640683E-305);
You can't use "rounded" and "human readable" output of echo function to decode the original value (because you are loosing precision of this double then).
If you will save the return of encode(10000000000000) to the variable and then try to decode it again it will works properly (you can use echo on 10000000000000 without loosing precision).
Please see the example below which you can execute on PHP compiler as well:
<?php
function encode($int) {
$int = round($int);
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($int & $left) >>32;
$r = $int & $right;
return unpack('d', pack('NN', $l, $r))[1];
}
function decode($float) {
$set = unpack('N2', pack('d', $float));
return $set[1] << 32 | $set[2];
}
echo decode(encode(10000000000000)); // untouched
echo '<br /><br />';
$encoded = encode(10000000000000);
echo $encoded; // LOOSING PRECISION!
echo ' - "human readable" version of encoded int<br /><br />';
echo decode($encoded); // STILL WORKS - HAPPY DAYS!
?>
If you have a reliable fixed decimal point, like in my case and the case of currency, you can multiply your float by some power of 10 (ex. 100 for dollars).
function encode($float) {
return (int) $float * pow(10, 2);
}
function decode($str) {
return bcdiv($str, pow(10, 2), 2);
}
However, this doesn't work for huge numbers and doesn't officially solve the problem.
Seems like it's impossible to convert from an integer to a float string and back without losing the original integer value in php 5.4
my question is: is there a good (common) algorithm to create numbers, which match well looking user understood numbers out of incomming (kind of random looking for a user) numbers.
i.e. you have an interval from
130'777.12 - 542'441.17.
But for the user you want to display something more ...say userfriendly, like:
130'000 - 550'000.
how can you do this for several dimensions?
an other example would be:
23.07 - 103.50 to 20 - 150
do you understand what i mean?
i should give some criteria as well:
the interval min and max should
include the given limits.
the "rounding" should be in a
granularity which reflects the
distance between min and max (meaning
in our second example 20 - 200
would be too coarse)
very much honor you'll earn if you know a native php function which can do this :-)
*update - 2011-02-21 *
I like the answer from #Ivan and so accepted it. Here is my solution so far:
maybe you can do it better. i am open for any proposals ;-).
/**
* formats a given float number to a well readable number for human beings
* #author helle + ivan + greg
* #param float $number
* #param boolean $min regulates wheter its the min or max of an interval
* #return integer
*/
function pretty_number($number, $min){
$orig = $number;
$digit_count = floor(log($number,10))+1; //capture count of digits in number (ignoring decimals)
switch($digit_count){
case 0: $number = 0; break;
case 1:
case 2: $number = round($number/10) * 10; break;
default: $number = round($number, (-1*($digit_count -2 )) ); break;
}
//be sure to include the interval borders
if($min == true && $number > $orig){
return pretty_number($orig - pow(10, $digit_count-2)/2, true);
}
if($min == false && $number < $orig){
return pretty_number($orig + pow(10, $digit_count-2)/2, false);
}
return $number;
}
I would use Log10 to find how "long" the number is and then round it up or down. Here's a quick and dirty example.
echo prettyFloor(23.07);//20
echo " - ";
echo prettyCeil(103.50);//110
echo prettyFloor(130777.12);//130000
echo " - ";
echo prettyCeil(542441.17);//550000
function prettyFloor($n)
{
$l = floor(log(abs($n),10))-1; // $l = how many digits we will have to nullify :)
if ($l<=0)
$l++;
if ($l>0)
$n=$n/(pow(10,$l)); //moving decimal point $l positions to the left eg(if $l=2 1234 => 12.34 )
$n=floor($n);
if ($l>0)
$n=$n*(pow(10,$l)); //moving decimal point $l positions to the right eg(if $l=2 12.3 => 1230 )
return $n;
}
function prettyCeil($n)
{
$l = floor(log(abs($n),10))-1;
if ($l<=0)
$l++;
if ($l>0)
$n=$n/(pow(10,$l));
$n=ceil($n);
if ($l>0)
$n=$n*(pow(10,$l));
return $n;
}
This example unfortunately will not convert 130 to 150. As both 130 and 150 have the same precision. Even thou for us, humans 150 looks a bit "rounder". In order to achieve such result I would recommend to use quinary system instead of decimal.
You can use php's round function which takes a parameter to specify the precision.
<?php
echo round(3.4); // 3
echo round(3.5); // 4
echo round(3.6); // 4
echo round(3.6, 0); // 4
echo round(1.95583, 2); // 1.96
echo round(1241757, -3); // 1242000
echo round(5.045, 2); // 5.05
echo round(5.055, 2); // 5.06
?>
The number_format() function handles "prettifying" numbers with arbitrary thousands/decimal characters and decimal places, but you'd have to split your ranges/strings into individual numbers, as number_formation only works on one number at a time.
The rounding portion would have to handled seperately as well.
I haven't seen ready algorithm or function for that. But it should be simple, based on string replacement (str_replace, preg_replace), number_format and round functions.
This actually is kind of a special case, that can be addressed with the following function:
function roundto($val, $toceil=false) {
$precision=2; // try 1, 2, 5, 10
$pow = floor(log($val, 10));
$mult = pow(10, $pow);
$a = $val/$mult*$precision;
if (!$toceil) $a-=0.5; else $a+=0.5;
return round($a)/$precision*$mult;
}
$v0=130777.12; $v1=542441.17;
echo number_format(roundto($v0, false), 0, '.', "'").' - '
.number_format(roundto($v1, true), 0, '.', "'").'<br/>';
$v0=23.07; $v1=103.50;
echo number_format(roundto($v0, false), 0, '.', "'").' - '
.number_format(roundto($v1, true), 0, '.', "'").'<br/>';
Outputs exactly this:
100'000 - 550'000
20 - 150
For any other case of number formatting it might be interesting to have a look at my newly published PHP class "php-beautiful-numbers", which I use in almost ever project to display run times ("98.4 µs" [= 9.8437291615846E-5]) or numbers in running text (e.g. "you booked two flights." [= 2]).
https://github.com/SirDagen/php-beautiful-numbers