In PHP I have a 64 bit number which represents tasks that must be completed. A second 64 bit number represents the tasks which have been completed:
$pack_code = 1001111100100000000000000011111101001111100100000000000000011111
$veri_code = 0000000000000000000000000001110000000000000000000000000000111110
I need to compare the two and provide a percentage of tasks completed figure. I could loop through both and find how many bits are set, but I don't know if this is the fastest way?
Assuming that these are actually strings, perhaps something like:
$pack_code = '1001111100100000000000000011111101001111100100000000000000011111';
$veri_code = '0000000000000000000000000001110000000000000000000000000000111110';
$matches = array_intersect_assoc(str_split($pack_code),str_split($veri_code));
$finished_matches = array_intersect($matches,array(1));
$percentage = (count($finished_matches) / 64) * 100
Because you're getting the numbers as hex strings instead of ones and zeros, you'll need to do a bit of extra work.
PHP does not reliably support numbers over 32 bits as integers. 64-bit support requires being compiled and running on a 64-bit machine. This means that attempts to represent a 64-bit integer may fail depending on your environment. For this reason, it will be important to ensure that PHP only ever deals with these numbers as strings. This won't be hard, as hex strings coming out of the database will be, well, strings, not ints.
There are a few options here. The first would be using the GMP extension's gmp_xor function, which performs a bitwise-XOR operation on two numbers. The resulting number will have bits turned on when the two numbers have opposing bits in that location, and off when the two numbers have identical bits in that location. Then it's just a matter of counting the bits to get the remaining task count.
Another option would be transforming the number-as-a-string into a string of ones and zeros, as you've represented in your question. If you have GMP, you can use gmp_init to read it as a base-16 number, and use gmp_strval to return it as a base-2 number.
If you don't have GMP, this function provided in another answer (scroll to "Step 2") can accurately transform a string-as-number into anything between base-2 and 36. It will be slower than using GMP.
In both of these cases, you'd end up with a string of ones and zeros and can use code like that posted by #Mark Baker to get the difference.
Optimization in this case is not worth of considering. I'm 100% sure that you don't really care whether your scrip will be generated 0.00000014 sec. faster, am I right?
Just loop through each bit of that number, compare it with another and you're done.
Remember words of Donald Knuth:
We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.
This code utilizes the GNU Multi Precision library, which is supported by PHP, and since it is implemented in C, should be fast enough, and supports arbitrary precision.
$pack_code = gmp_init("1001111100100000000000000011111101001111100100000000000000011111", 2);
$veri_code = gmp_init("0000000000000000000000000001110000000000000000000000000000111110", 2);
$number_of_different_bits = gmp_popcount(gmp_xor($pack_code, $veri_code));
$a = 11111;
echo sprintf('%032b',$a)."\n";
$b = 12345;
echo sprintf('%032b',$b)."\n";
$c = $a & $b;
echo sprintf('%032b',$c)."\n";
$n=0;
while($c)
{
$n += $c & 1;
$c = $c >> 1;
}
echo $n."\n";
Output:
00000000000000000010101101100111
00000000000000000011000000111001
00000000000000000010000000100001
3
Given your PHP-setuo can handle 64bit, this can be easily extended.
If not you can sidestep this restriction using GNU Multiple Precision
You could also split up the HEx-Representation and then operate on those coresponding parts parts instead. As you need just the local fact of 1 or 0 and not which number actually is represented! I think that would solve your problem best.
For example:
0xF1A35C and 0xD546C1
you just compare the binary version of F and D, 1 and 5, A and 4, ...
Related
I understand Integer size, PHP_INT_MAX is platform dependent.
On 64-bit system I can get a:
$large_number = 9223372036854775807
From what I read from PHP Document Example 3 here, when integer overflows, PHP handles integer as float with a precision of roughly 14 decimals digits, i.e:
Unsigned Int64: 18446744073709551615
PHP will handles as: 1.844674407371E+19
So it seems all precision only kept up to the max of Signed Int64.
Is this really just a limitation of PHP and there is nothing I can do about it?
This is pretty hacky, and I assure you your life will be easier using GMP but it is possible using pack() and a little binary thinking:
<?php
$large_number = 9223372036854775807;
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($large_number & $left) >>32; //shift 32 bits left
$r = $large_number & $right;
$int64_binary_string = pack('NN', $l, $r);
I haven't tested this, and wouldn't use it in real life, but it's probably close to what you're looking for.
http://tw1.php.net/manual/en/function.pack.php
if you need precision or numbers outside of PHP_INT_MAX / PHP_INT_MIN, learn to use GMP, which will probably keep going until you run out of memory... to give you some idea of how far you can take it,
$large_number = gmp_init("9223372036854775807");
$i=0;
while(true){
echo 'iteration '.$i++.':'.gmp_strval($large_number).'*1337=';
$large_number = gmp_mul($large_number,"1337");
echo gmp_strval($large_number).PHP_EOL;
}
will eventually
iteration 1291:63173727399431265977452467325176816266767500735669001788445953704
99100022309199341754638029233101065727095873054512847649411469159231474984853877
29990385564525384931210393372816147935077346316357322846070505282141866990047417
69795035320095201175780336777052639896516690649784147989039497959127394363606555
18277493016490689369408018504673663458745651065704848764945821685549595325229654
78521759046816461228651950877052534907426557036526546565508337234675673499891633
29265311281809541141691613057522094435618917713573494707844587004419303204290366
80410319411622496279245084808349790649184389864091281089934776245296259423173088
37125998364710920001673776690982404095905353172292803413540568804924330209161221
68502039031454119485450113377479892509776976500478875924900269471621353800015849
16768619446056453580306524878177680668058129358555807454509042635315136209751306
28888008462882394248192470687562440408441365704520299664521180552207604024174563
43137016510143518117853956283018500610190006192251441907450531358798897519852285
89712288688300401723956266383086427686519451058576140512867416675873224992592868
63873656820691379608416040082157797343590944870890093034489537997624441231196880
03716995482080107872348544579795198224539076738023147659657987086535844760748293
51680757422559231216506109885022428024013042708325323490543266063675126192858475
51062320509059668456301511665702512575733290996693385011230330439062943182094596
72982868367513131107811463983324141985257032192551001490775273440704964956322520
07994322409745388858643755238449137799970426027598547825636867768578263720009398
06345603047100479826296034929890108340101488966701293667796312958087571285537092
98292308718879824518810679195685709967212889579496467906343837737351387116485926
96065604380646092415099392666166947273008556519323980631398826013377061548412499
37045775535278706790702274483174254245054418225127782273776637425449169726435461
00864900455001093729251741310689643202116980745998974559375404087598011323913589
37378336888032750432647317316783507777959680594838416103235878202519306235270714
07689264877781232001726371838121263807804664374737613209359850417045192588050394
70657700599839677955302104699239419723871651195923433991338871172082351085772713
85795544605616579911291449220540413076513145849439424174121530477862582225947332
71577283620910275735801384726436002832118799836996365693738642178527175171378406
17487708039971384270639342138853231882316669599224158264716457232654635304792216
85595639961619807370286817974879544351592128186367207420223323272766972084744559
66627509594039497325711708953969586289133165339724320993714241576206582692070672
87281148490183467979899908277488360099016745185705207507885725820344193088663031
74742220273886082811546338253396232117540977001317286430939919216858700761871025
04883648220800173612755757031087544792419044177699287860855715222255510614135023
30706372831160632288499262660942154311052431900104315094339186786507006013357031
08125929293885129152264099174616004246743029176792527824811466605161478045492703
27090040582587712800567514734685257659124144431850835544283278381310315791931357
63459289106249506321560095185194943714045276693796155870508190654607382917421646
15486864936613368395909757833185705018554557948762713677856056206953385065593915
49326215023945424937441813782143778143766220175505533500485707438756395764163323
47714668439588417016028256182365660677880890399143415948539281432076160651247693
72982310275084829961404187585100633245654443972233743905144338234758859827413170
88983027723373315156469586730671906768407758289550985343535503818941377020797426
18604249085624012174154963219214543971133251713032811649089587541472009254502083
21938426925234679535003649815691245256828585977342824147515002509080267191887689
93756807224396178461744274960675671607151017304225081498878127594506757910634207
82366257650930768410626608502254524385838019829644847875689142861722171975269086
27152913039908221672006718321592434013558052896236283594560899204934144043552210
8813052796446077685440711695302241857244755302464656726076289075058391*1337=8446
32735330396026118539488137614033486681484835894553911522401035729672982739951992
59510450846561248771271822738836773072631342658924820547496339499714549977043965
30282959394551897891984120249697406451962655622236761656933974621596222296728397
20183102709193795416428153987614058613458087713533262641419642793700816304805168
68985207407486880443429354748473827987325635935798089498320484478359184559360866
27076583226192391712293067578359927580846468827613754693551137122772118377935650
64416866579070402604224929830477624243882128249086083841362204170859705333927752
53506783887636700979595292482900428172427958399610988487824191523745981361850004
22378394358434742762254571913554781639037404921838294896485533928722618505415775
20468015856906162855718175811402571115916602835577500306211903371964419937747843
68698237621235590531937189523891145666785900034163371124374965082326731487376110
98333333092709828260861059469436406514648183983015665803213913077419107406188372
35707395503957353158240382790401778302613604267141259840425062444532997625763710
49295281541865538168765060653162998657037360956425018150966653699907916926437453
64522455898449750483810932923800543871125123028238779261102286096962295954110422
53300041031861800262087455987369484209627287346984244451204684319717267396169213
64686689162749862681054381010309575068563467271336437198517817577032252061277672
60751210970442593137554100625790557600149517970271550344604758277809500736505629
11439273457043778342886520414406889931665405902225381466032093468840906182958490
40067007538064972385604595988992584428764922065891385936525652108407127397334152
77577987012630748507156907484796296338436704249630828087630933181681675714232538
16498780846317942261636333677867775907817110548388045747416843463971305692382555
89878879946652085040124400663361621041802303798851312902275116583020189066763097
91689409840039779256377571669958449000393642378255399242442113685637190833646231
60095781323920529612304032574006289858849152651185411400724689927483641929978732
84494632525395498991320929552989623300263691567683124365569447208054714159350718
63081591475681297110348362690241888609141200075894224902233777226934570198564942
62389139828831041708163976489496312464200707570741034016781184280864313770936734
13966676078625322832980760007005101208004862489022724360915838409882820115703865
87664513792449357865428353820641409325285645926908332041329290558106564944174076
98448004396467710266573872541626995999259033200592474025071939364137062868568245
40734756324139507980786753851729563208385832156894416773034762738098032723080792
44765548714573368685710420592114171685959409873882010592984896309489553137529668
91261773670019374523853883132878624380432154218001861595424734463034850618569271
90374542447907623411522862507612119581666719929400829186215604902943767120983212
02544471505640473874642620655839478699640912521556176910985261615442047526176536
97235141776796603138771014504394692811314927335598670398583505556436746592441767
65771005964615976778954300093716097017729308511008961468237442731938425891977201
43587672002741894902489811053845671227067431958118922138122251574506953505558995
19258472626056397456785349396054603988694509052100709605927409090593842025207354
53313462229692876098074439774957481872935471486966758326990650144914948701503314
13597050267262313782154363746508982901493908456173011366863634889451170372971355
04297785158228883263267504636547471231970192746858267907181665167734883778841765
83973988012795466494399915908765156011779802198725955892514094797030806615012236
41998374589083393493611728331296674043069686059246210768061588107388102747930427
68451858240898452894051575403248691748327785429480763732692852643167679903876653
82998798035791949083798194517073558852275583546403172355538414465285125901769060
33520956224233729387609101357489339640000565938555353265179358602368647929443736
50077755675142991038654325122351616097963840061225439309347683450344473435729237
54729823959690842761271167222679111659279222369969505862293059483051588848405865
434231536619097363136237839395246042763998493353068767
*and by "eventually", i mean less than 5 seconds on my laptop..
Yes it is a limitation of PHP and there is nothing you can do about it short of recompiling your PHP interpreter. Even then you are limited to the types your native system supports which wouldn't be bigger than 64bit normally. You can, as you know, use GMP, or BCMath, but that is not what your asking.
Under the hood, depending on your system the PHP integer and PHP floating point types corresponds to a signed C integer type, and C float types (PHP always uses C doubles for 'floats' AFAIK). This is a static relationship and can't change after compile time. Since the C types have fixed precision of course the PHP ones do too.
The "overflow" into a float is just a convenient compromise so you can store really big numbers, rather than not at all. Your losing some precision yeah, but only in the significand. PHP is not going to automatically convert the number to some other bigger precision floating point format because it doesn't have one.
I have a float value that I need to store as a string in PHP and then compare later after casting back into a float.
Due to the conversion I know that relying on equality would be a mistake, as there's potential for a loss of precision, so I'm doing something like the following:
if (abs((float)$string_value - $float_value) < 0.001) { echo "Values are close enough\n"; }
Now, while a margin for error of 0.001 should be fine for my immediate purposes, it got me wondering; what is the smallest margin of error that I can reliably/safely use?
I realise that the safe margin of error will change with the size of the float (i.e- larger values have less or even no fractional precision), so an answer should probably account for this.
So to put it another way; given a float value that I want to store in base 10 and read back, how can I reliably decide what my margin of error should be such that I can reasonably confirm that the two values are the same?
Unfortunately the values I'm handling must be stored in plain decimal form, so my usual go-to of packing them as a network order 64-bit integer is not an option here ☹️
EDIT: To clarify; please assume that my question is about handling arbitrarily sized floats; the example code I've given is for a recent case where I'm handling floats within a limited range, so setting the margin of error manually is fine, but I'd like to be able to handle floats of any magnitude in future.
As mentioned in Mark Dickinson's comment, it is possible to convert a floating-point number to a string and back without losing precision. This only works if
you use enough significant decimal digits (17 for IEEE doubles)
the conversions are accurate (i.e. they're guaranteed to convert to the nearest number)
From a quick look, it seems that casting a double $f to a string in PHP, either implicitly or with (string) $f, only uses 14 significant digits, so this method isn't accurate enough. But you can use sprintf with a %.16e conversion specifier to get 17 significant digits. So after the following roundtrip
$s = sprintf("%.16e", $f);
$f2 = (double) $s;
$f2 should equal $f exactly unless PHP uses suboptimal algorithms internally.
Note that the %e conversion specifier uses scientific (exponential) notation. If you need plain decimal strings, you can use the %f specifier and calculate the required number of digits after the decimal point using log10:
if ($f != 0) {
$prec = 16 - floor(log10(abs($f)));
if ($prec < 0) $prec = 0;
}
else {
$prec = 0;
}
$s = sprintf("%.${prec}f", $f);
This can produce extremely long strings for very small or large numbers, though.
It would probably require a huge amount of research to tell the whether these methods are completely reliable, and if not what the maximum error is. It all depends on several implementation details like PHP version, underlying C library, etc.
Another idea is to compare the string representations instead of floating-point values:
# Assuming $string_value was also converted with float_to_string
if ($string_value == float_to_string($float_value)) {
echo "Values are close enough\n";
}
This should be reliable as long as you stick to the same PHP version.
If you must compare floating-point numbers, it often makes more sense to compare the relative error. See Bruce Dawson's excellent blog for more details.
In PHP, I am writing an application which requires precision to 2 digits right of the decimal point for currency (eg: I care about 1.23 === 1.23 but no more right-side digits).
I am aware that floats are generally considered bad practice because they are imprecise with values based on the nature of converting from base 2 to base 10 right of the decimal point. However, in my research for a best practice for working with currency values, I saw some arguments that float is not good if you need precision greater than whole cent values. I clearly do not need greater precision that whole cent values.
So my questions, then, are:
Is it worth going through the extra effort of storing the values as strings to be used with the bcmath library?
If using the bcmath lib, should I store the values in the MySQL db as strings or decimal that MySQL supports?
Thanks!
After further digging, I found the solution at Should I use BCMath for values with about 1,2 or 3 decimals?
According the the accepted answer on the given post, floats can not be guaranteed for any precision right of the decimal point.
As far as storage in the DB, it seems that storing it as a string would be the easiest option since the bcmath lib works with strings.
Use this to trim to two decimal places without rounding.
<?php
$a = 12.37675;
$a = floor($a * 100) / 100; // 12.37
echo $a;
or
<?php
function dollar($value) {
return floor($value * 100) / 100;
}
$a = 12.37675;
echo dollar($a);
I understand Integer size, PHP_INT_MAX is platform dependent.
On 64-bit system I can get a:
$large_number = 9223372036854775807
From what I read from PHP Document Example 3 here, when integer overflows, PHP handles integer as float with a precision of roughly 14 decimals digits, i.e:
Unsigned Int64: 18446744073709551615
PHP will handles as: 1.844674407371E+19
So it seems all precision only kept up to the max of Signed Int64.
Is this really just a limitation of PHP and there is nothing I can do about it?
This is pretty hacky, and I assure you your life will be easier using GMP but it is possible using pack() and a little binary thinking:
<?php
$large_number = 9223372036854775807;
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$l = ($large_number & $left) >>32; //shift 32 bits left
$r = $large_number & $right;
$int64_binary_string = pack('NN', $l, $r);
I haven't tested this, and wouldn't use it in real life, but it's probably close to what you're looking for.
http://tw1.php.net/manual/en/function.pack.php
if you need precision or numbers outside of PHP_INT_MAX / PHP_INT_MIN, learn to use GMP, which will probably keep going until you run out of memory... to give you some idea of how far you can take it,
$large_number = gmp_init("9223372036854775807");
$i=0;
while(true){
echo 'iteration '.$i++.':'.gmp_strval($large_number).'*1337=';
$large_number = gmp_mul($large_number,"1337");
echo gmp_strval($large_number).PHP_EOL;
}
will eventually
iteration 1291:63173727399431265977452467325176816266767500735669001788445953704
99100022309199341754638029233101065727095873054512847649411469159231474984853877
29990385564525384931210393372816147935077346316357322846070505282141866990047417
69795035320095201175780336777052639896516690649784147989039497959127394363606555
18277493016490689369408018504673663458745651065704848764945821685549595325229654
78521759046816461228651950877052534907426557036526546565508337234675673499891633
29265311281809541141691613057522094435618917713573494707844587004419303204290366
80410319411622496279245084808349790649184389864091281089934776245296259423173088
37125998364710920001673776690982404095905353172292803413540568804924330209161221
68502039031454119485450113377479892509776976500478875924900269471621353800015849
16768619446056453580306524878177680668058129358555807454509042635315136209751306
28888008462882394248192470687562440408441365704520299664521180552207604024174563
43137016510143518117853956283018500610190006192251441907450531358798897519852285
89712288688300401723956266383086427686519451058576140512867416675873224992592868
63873656820691379608416040082157797343590944870890093034489537997624441231196880
03716995482080107872348544579795198224539076738023147659657987086535844760748293
51680757422559231216506109885022428024013042708325323490543266063675126192858475
51062320509059668456301511665702512575733290996693385011230330439062943182094596
72982868367513131107811463983324141985257032192551001490775273440704964956322520
07994322409745388858643755238449137799970426027598547825636867768578263720009398
06345603047100479826296034929890108340101488966701293667796312958087571285537092
98292308718879824518810679195685709967212889579496467906343837737351387116485926
96065604380646092415099392666166947273008556519323980631398826013377061548412499
37045775535278706790702274483174254245054418225127782273776637425449169726435461
00864900455001093729251741310689643202116980745998974559375404087598011323913589
37378336888032750432647317316783507777959680594838416103235878202519306235270714
07689264877781232001726371838121263807804664374737613209359850417045192588050394
70657700599839677955302104699239419723871651195923433991338871172082351085772713
85795544605616579911291449220540413076513145849439424174121530477862582225947332
71577283620910275735801384726436002832118799836996365693738642178527175171378406
17487708039971384270639342138853231882316669599224158264716457232654635304792216
85595639961619807370286817974879544351592128186367207420223323272766972084744559
66627509594039497325711708953969586289133165339724320993714241576206582692070672
87281148490183467979899908277488360099016745185705207507885725820344193088663031
74742220273886082811546338253396232117540977001317286430939919216858700761871025
04883648220800173612755757031087544792419044177699287860855715222255510614135023
30706372831160632288499262660942154311052431900104315094339186786507006013357031
08125929293885129152264099174616004246743029176792527824811466605161478045492703
27090040582587712800567514734685257659124144431850835544283278381310315791931357
63459289106249506321560095185194943714045276693796155870508190654607382917421646
15486864936613368395909757833185705018554557948762713677856056206953385065593915
49326215023945424937441813782143778143766220175505533500485707438756395764163323
47714668439588417016028256182365660677880890399143415948539281432076160651247693
72982310275084829961404187585100633245654443972233743905144338234758859827413170
88983027723373315156469586730671906768407758289550985343535503818941377020797426
18604249085624012174154963219214543971133251713032811649089587541472009254502083
21938426925234679535003649815691245256828585977342824147515002509080267191887689
93756807224396178461744274960675671607151017304225081498878127594506757910634207
82366257650930768410626608502254524385838019829644847875689142861722171975269086
27152913039908221672006718321592434013558052896236283594560899204934144043552210
8813052796446077685440711695302241857244755302464656726076289075058391*1337=8446
32735330396026118539488137614033486681484835894553911522401035729672982739951992
59510450846561248771271822738836773072631342658924820547496339499714549977043965
30282959394551897891984120249697406451962655622236761656933974621596222296728397
20183102709193795416428153987614058613458087713533262641419642793700816304805168
68985207407486880443429354748473827987325635935798089498320484478359184559360866
27076583226192391712293067578359927580846468827613754693551137122772118377935650
64416866579070402604224929830477624243882128249086083841362204170859705333927752
53506783887636700979595292482900428172427958399610988487824191523745981361850004
22378394358434742762254571913554781639037404921838294896485533928722618505415775
20468015856906162855718175811402571115916602835577500306211903371964419937747843
68698237621235590531937189523891145666785900034163371124374965082326731487376110
98333333092709828260861059469436406514648183983015665803213913077419107406188372
35707395503957353158240382790401778302613604267141259840425062444532997625763710
49295281541865538168765060653162998657037360956425018150966653699907916926437453
64522455898449750483810932923800543871125123028238779261102286096962295954110422
53300041031861800262087455987369484209627287346984244451204684319717267396169213
64686689162749862681054381010309575068563467271336437198517817577032252061277672
60751210970442593137554100625790557600149517970271550344604758277809500736505629
11439273457043778342886520414406889931665405902225381466032093468840906182958490
40067007538064972385604595988992584428764922065891385936525652108407127397334152
77577987012630748507156907484796296338436704249630828087630933181681675714232538
16498780846317942261636333677867775907817110548388045747416843463971305692382555
89878879946652085040124400663361621041802303798851312902275116583020189066763097
91689409840039779256377571669958449000393642378255399242442113685637190833646231
60095781323920529612304032574006289858849152651185411400724689927483641929978732
84494632525395498991320929552989623300263691567683124365569447208054714159350718
63081591475681297110348362690241888609141200075894224902233777226934570198564942
62389139828831041708163976489496312464200707570741034016781184280864313770936734
13966676078625322832980760007005101208004862489022724360915838409882820115703865
87664513792449357865428353820641409325285645926908332041329290558106564944174076
98448004396467710266573872541626995999259033200592474025071939364137062868568245
40734756324139507980786753851729563208385832156894416773034762738098032723080792
44765548714573368685710420592114171685959409873882010592984896309489553137529668
91261773670019374523853883132878624380432154218001861595424734463034850618569271
90374542447907623411522862507612119581666719929400829186215604902943767120983212
02544471505640473874642620655839478699640912521556176910985261615442047526176536
97235141776796603138771014504394692811314927335598670398583505556436746592441767
65771005964615976778954300093716097017729308511008961468237442731938425891977201
43587672002741894902489811053845671227067431958118922138122251574506953505558995
19258472626056397456785349396054603988694509052100709605927409090593842025207354
53313462229692876098074439774957481872935471486966758326990650144914948701503314
13597050267262313782154363746508982901493908456173011366863634889451170372971355
04297785158228883263267504636547471231970192746858267907181665167734883778841765
83973988012795466494399915908765156011779802198725955892514094797030806615012236
41998374589083393493611728331296674043069686059246210768061588107388102747930427
68451858240898452894051575403248691748327785429480763732692852643167679903876653
82998798035791949083798194517073558852275583546403172355538414465285125901769060
33520956224233729387609101357489339640000565938555353265179358602368647929443736
50077755675142991038654325122351616097963840061225439309347683450344473435729237
54729823959690842761271167222679111659279222369969505862293059483051588848405865
434231536619097363136237839395246042763998493353068767
*and by "eventually", i mean less than 5 seconds on my laptop..
Yes it is a limitation of PHP and there is nothing you can do about it short of recompiling your PHP interpreter. Even then you are limited to the types your native system supports which wouldn't be bigger than 64bit normally. You can, as you know, use GMP, or BCMath, but that is not what your asking.
Under the hood, depending on your system the PHP integer and PHP floating point types corresponds to a signed C integer type, and C float types (PHP always uses C doubles for 'floats' AFAIK). This is a static relationship and can't change after compile time. Since the C types have fixed precision of course the PHP ones do too.
The "overflow" into a float is just a convenient compromise so you can store really big numbers, rather than not at all. Your losing some precision yeah, but only in the significand. PHP is not going to automatically convert the number to some other bigger precision floating point format because it doesn't have one.
I have a very strange issue. If I subtract 2 float vars where one is the result of a mathematical operation I get a wrong value.
Example:
var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];
This it the output:
float 5.4
float 1.4
5.3290705182008E-15
5.4-1.4 should be 4
If I add the two values the result is correct.
Where is my mistake?
It can not be a rounding issue.
If still somebody hits this page with similar problems where floating number subtraction causes error or strange values.
Below I will explain this problem with a bit more details.
It is not directly related to PHP and it is not a bug.
However, every programmer should be aware of this issue.
This problem even took many lives two decades ago.
On 25 February 1991 an incorrect floating-point arithmetic (called rounding error) in a MIM-104 Patriot missile battery prevented it from intercepting an incoming Scud missile in Dhahran, Saudi Arabia, killing 28 soldiers and injuring near 100 servicemen from the U.S. Army's 14th Quartermaster Detachment.
But why it happens?
The reason is that floating point values represent a limited precision. So, a value might
not have the same string representation after any processing (chopped off). It also
includes writing a floating point value in your script and directly
printing it without any mathematical operations.
Just a simple example:
$a = '36';
$b = '-35.99';
echo ($a + $b);
You would expect it to print 0.01, right?
But it will print a very strange answer like 0.009999999999998
Like other numbers, floating point numbers double or float is stored in memory as a string of 0's and 1's. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them. There are many standards how they are stored.
Floating-point numbers are typically packed into a computer datum as the sign bit, the exponent field, and the significand or mantissa, from left to right....
Decimal numbers are not well represented in binary due to lack of enough space. So, you can't express 1/3 exactly as it's 0.3333333..., right? Why we can't represent 0.01 as a binary float number is for the same reason. 1/100 is 0.00000010100011110101110000..... with a repeating 10100011110101110000.
If 0.01 is kept in simplified and system-truncated form of 01000111101011100001010 in binary, when it is translated back to decimal, it would be read like 0.0099999.... depending on system (64bit computers will give you much better precision than 32-bits). Operating system decides in this case whether to print it as it sees or how to make it in more human-readable way. So, it is machine-dependent how they want to represent it. But it can be protected in language level with different methods.
If you format the result using
echo number_format(0.009999999999998, 2);
it will print 0.01.
It is because in this case you instruct how it should be read and how precision you require.
Note number_format() is not the only function, a few other functions and ways can be used to tell the programming language about the precision expectation.
References:
https://sdqweb.ipd.kit.edu/publications/pdfs/saglam2016a.pdf
https://en.wikipedia.org/wiki/Round-off_error
This worked for me:
<?php
$a = 96.35;
$b = 96.01;
$c = ( ( floor($a * 100) - floor($b * 100) ) / 100 );
echo $c; // should see 0.34 exactly instead of 0.33999999999999
?>
Since the problem occurs with floating point subtraction operation I decided to eliminate that by transforming it into an integer operation, then backing up the result into a floating point again.
I much prefer that solution because basically it does prevent the error on calculation rather than rouding up the result with other functions.
In addition to using number_format(), there are three other ways to obtain the correct result. One involves doing a little math, as follows:
<?php
$a = '36';
$b = '-35.99';
$a *= 100;
$b *= 100;
echo (($a + $b)/100),"\n";
See demo
Or, you could simply use printf():
<?php
$a = '36';
$b = '-35.99';
printf("\n%.2f",($a+$b));
See demo
Note, without the precision specifier, the printf() result will contain trailing zero decimals, as follows: 0.010000
You also could also utilize the BC Math function bcadd(), as follows:
<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);
See demo
I wrote a simple function to deal with this.
It works similarly to the bcadd function from the bcmath extension of php.
You pass it 2 decimal numbers in string form, $a and $b, and specify how many decimals should be used which must match the number of decimals in both $a and $b.
As you can see it will use integers to do the math, then convert back to string without using floating point operations at any point.
function decimalAdd($a,$b,$numDecimals=2) {
$intSum=(int)str_replace(".","",$a)+(int)str_replace(".","",$b);
$paddedIntSum=str_pad(abs($intSum),$numDecimals,0,STR_PAD_LEFT);
$result=($intSum<0?"-":"").($intSum<100&&$intSum>-100?"0":"").substr_replace($paddedIntSum,".",-$numDecimals,0);
return $result;
}
Sample usage:
echo decimalAdd("36.00","-35.99");
0.01