Problem with Variable Type

I have a function which splits up the whole and fractional part of a real number.

When I feed the function 4.9 it returns 0.9 for the fractional portion - which appears all good and well.

But when I try to compare another number against the 0.9 I am getting unexpected results.

When I run this code…


	$ratingAvg = 4.9;

	$splitDecimal = getSplitDecimal($ratingAvg);

	echo "<p>{$splitDecimal['fraction']}</p>";

	$type = gettype($splitDecimal['fraction']);

	echo "<p>$type</p>";

	if ($splitDecimal['fraction'] == 0.9){
		echo "<p>TRUE</p>";
	}else{
		echo "<p>FALSE</p>";
	}
	exit();

I get this test output on my screen…


0.9

double

FALSE

What is going wrong in this comparison…


	if ($splitDecimal['fraction'] == 0.9){

:-/

Sincerely,

Debbie

Let’s see the code for getSplitDecimal()

Here you go…


	function getSplitDecimal($input=NULL){
		/**
		 * Returns components of Decimal number.
		 *
		 * Written on:	2014-08-14
		 * Updated on:	
		 *
		 * @param Double		$input
		 *
		 * @return	array		$splitDecimal {Whole part, Fractional part}
		 */
		
		if (is_numeric($input)){
			// Valid Number.
			$wholePart = (int)$input;
			$fractionalPart = $input - $wholePart;
			
		}else{
			// Not Number.
			$wholePart = NULL;
			$fractionalPart = NULL;			
		}
		
		// Set Array.
		$splitDecimal['whole'] = $wholePart;
		$splitDecimal['fraction'] = $fractionalPart;
		
		return $splitDecimal;
		
	}//End of getSplitDecimal

Debbie

Most likely this is a floating point precision problem, which is a problem inherent to computers. It happens when you use base2 to represent base10 numbers. Something that can be easily represented in base10 is maybe not so easily represented in base2.

For example:

8 - 6.4 == 1.6 // false

Broadly speaking, if you need to test for exact equivalents, try avoiding fractions. Do you math using whole numbers as much as possible.

80 - 64 == 16 // true

Ok, good…
now, I’m really not sure what the question is.

Does this produce the results you are expecting?



function getSplitDecimal($input=NULL){
        /**
         * Returns components of Decimal number.
         *
         * Written on:    2014-08-14
         * Updated on:    
         *
         * @param Double        $input
         *
         * @return    array        $splitDecimal {Whole part, Fractional part}
         */
        
        if (is_numeric($input)){
            // Valid Number.
            $wholePart = (int)$input;
            $fractionalPart = '.'.substr($input, 2);
            
        }else{
            // Not Number.
            $wholePart = NULL;
            $fractionalPart = NULL;            
        }
        
        // Set Array.
        $splitDecimal['whole'] = $wholePart;
        $splitDecimal['fraction'] = (float)$fractionalPart;
        
        return $splitDecimal;
        
    }//End of getSplitDecimal 


If you want to remove the leading 0 from 0.9, then you can just remove (float) from $splitDecimal[‘fraction’] = (float)$fractionalPart;

Please re-read my OP.

The 0.9 returned by my function is not equaling the 0.9 in my conditional.

And the data-type appears to be okay, so why is my conditional failing?

Debbie

Well, that’s kind of what I’m getting at…Jeff answered that pretty accurately already. The code that I gave above is a workaround for the inherent problem, I am just trying to verify that the code is what you want

Jeff, don’t make me cry… I spent ALL DAY writing this function and taking the approach I did, thinking it was a “good” idea… :frowning:

Let me explain the method to Debbie’s madness for the day…

People can give a Comment a rating of 1 to 5 stars. I then average these votes, and display it next to the Comment along with the appropriate number of Star icons.

The Avg Rating can be any value between 1 to 5, so I needed some help from PHP to figure out how many Stars to display.

So I came up with the following approach…

1.) Split the Avg Rating value into Whole and Fractional parts
2.) Use the Whole portion to run my FOR loop and display all of the Whole Stars
3.) If there is a Fractional portion, then display a Half-Star.

Well, all of that worked great, so I got greedy and decided to take Paul O’s advice and fine-tune my Partial-Stars down to the 1/10.

So then I created a series of IF-THEN-ELSEIF’s to determine what type of Partial Star (e.g. 70%) to display, but I discovered that my Conditionals were NOT properly recognizing the Fractional part from my function!!

I could be wrong, but what I hear you saying is that everything I just spent the day programming will not work because of the impreciseness of Floating-Point Values?! :frowning:

Help!!!

Debbie

I didn’t see Jeff’s response, and I thought you were quoting my function - thus why I got frustrated.

Let me start at the top of this thread and make sure I am following everyone.

In the mean time, please see my latest post and my “thought-process” on all of this.

Thanks,

Debbie

Rounding is your friend. :slight_smile:

if ([COLOR="#FF0000"]round([/COLOR]$splitDecimal['fraction'][COLOR="#FF0000"], 1)[/COLOR] == 0.9){ 

PHP docs tells you in the big red box a little about floats’ lack of precision and not to compare them directly for equality.
http://php.net/manual/en/language.types.float.php

Like I said, the code provided above should work around that, and even gives you the option of dropping the leading 0 by converting it to a string. As long as you use == and not ===, you will be fine

Dear lord…how did we not see that one from the get go?? Use your original code and round() the way Jeff showed, guarantee that will fix it.

Funny you suggested that, because right after might last message - and before I went to supper - I tried the same thing and it worked! :stuck_out_tongue:

So that leads me to some questions…

1.) If there is a floating-point math error, then why is it when I echo this it produces 0.9…


echo "<p>{$splitDecimal['fraction']}</p>";

2.) How can I see exactly what is being stored in $splitDecimal[‘fraction’] ??

3.) Rounding worked with a simple number like 4.3, but what happens if the Avg Rating = 4 2/3 (i.e. 4.6666666) ??

4.) Isn’t there a way to “truncate” in PHP? Maybe that would be better?

Sincerely,

Debbie

round($splitDecimal[‘fraction’], 1)

The 1 specifies how many digits after the decimal point to return; i.e., it truncates everything after that.

Rounding works as expected…
if you used round(4.6666666, 2) it would return 4.67

But if $avgRating = 4.99 and we use round, then you get 5 which would be wrong in my mind.

I would rather be conservative and round down, but apparently PHP doesn’t offer that.

Would this maybe do what I want…


$avgRating = 4.999999;

$temp = (floor($avgRating * 100))/100

What do you think?

That way I am not accidentally rounding up to the next whole number on edge cases, but I would also make it so my comparison works, right?

Off Topic:

I decided to dig a little deeper to figure out why on my screen 0.9 wasn’t equal to 0.9 and added this code…


echo "<p>" . number_format($splitDecimal['fraction'], 100) . "</p>";

Turns out this is what my function was actually returning…


[b]0.9000000000000003552713678800500929355621337890625000000000000000000000000000000000000000000000000000[/b]

:wink:

Not sure why when I echoed things normally, PHP didn’t reveal that to me?! :rolleyes:

Sincerely,

Debbie

This is interesting…

Test Code:


	$ratingAvg = .999;

	$splitDecimal = getSplitDecimal($ratingAvg);

	echo "<p>" . '$ratingAvg = ' . $ratingAvg . "</p>";
		
	echo "<p>" . 'number_format($splitDecimal[\\'fraction\\'], 100) = ' . number_format($splitDecimal['fraction'], 100) . "</p>";

	echo "<p>" . 'round($splitDecimal[\\'fraction\\'], 2) = ' . round($splitDecimal['fraction'], 2) . "</p>";

	echo "<p>" . '(floor($splitDecimal[\\'fraction\\'] * 10)/10) = ' . (floor($splitDecimal['fraction'] * 10)/10) . "</p>";

	if ((floor($splitDecimal['fraction'] * 10)/10) == 0.9){
		echo "<p>TRUE</p>";
	}else{
		echo "<p>FALSE</p>";
	}

Test Output:


$ratingAvg = 0.999

number_format($splitDecimal['fraction'], 100) = 0.9989999999999999991118215802998747676610946655273437500000000000000000000000000000000000000000000000

round($splitDecimal['fraction'], 2) = 1

(floor($splitDecimal['fraction'] * 10)/10) = 0.9

TRUE

Looks like using floor() along with the multiplication/division is the way to go.

Sincerely,

Debbie

round( 4.99999999999999, 1, PHP_ROUND_HALF_DOWN) would return 4.9 … you can skip floor()

When needed, cast your data types and do === comparisons

I had problems with Tandy Basic a long time ago when calculating VAT. The solution was to multiply the amount by 100 and apply 12.5%

Your problem could be solved by multiplying by 10 then return the modulus of 10. Result is a single digit integer.

I missed that there were extra features with ROUND().

Sadly, it doesn’t work as you would expect…


	//****************************************************************************
	function getSplitDecimal($input=NULL){
		
		if (is_numeric($input)){
			// Valid Number.
			$wholePart = (int)$input;
			$fractionalPart = $input - $wholePart;
			
			// Compensate for Floating-Point rounding errors.
			$fractionalPart2 = (floor($fractionalPart * 10))/10;

			$fractionalPart3 = round($fractionalPart, 1, PHP_ROUND_HALF_DOWN);

echo "<p>" . $fractionalPart . "</p>";
echo "<p>" . $fractionalPart2 . "</p>";
echo "<p>" . $fractionalPart3 . "</p>";
echo "<p>" . number_format($fractionalPart2, 100) . "</p>";
echo "<p>" . number_format($fractionalPart3, 100) . "</p>";
exit();
		}else{
			// Not Number.
			$wholePart = NULL;
			$fractionalPart = NULL;			
		}
		
		// Set Array.
		$splitDecimal['whole'] = $wholePart;
		$splitDecimal['fraction'] = $fractionalPart;
		
		return $splitDecimal;
		
	}//End of getSplitDecimal
	//****************************************************************************


	$ratingAvg = 1.77;

	$getSplitDecimal = getSplitDecimal($ratingAvg);


Here is what the output says…


0.77

0.7

0.8

0.6999999999999999555910790149937383830547332763671875000000000000000000000000000000000000000000000000

0.8000000000000000444089209850062616169452667236328125000000000000000000000000000000000000000000000000

Sad to say, but using my FLOOR() approach is more accurate.

I think I have lost all respect for PHP after this little “experiment” over the last 2 days!!

Regardless of the approach used, these deceptive results are PATHETIC!!! :mad:

What in the hell would a person do if they where working in Banking/Finance or the Scientific Community??? (Is this how Wall Street gets rich?!)

I find it hard to believe that you would get the same lame results in languages like C, C++, or Java… :rolleyes:

Maybe this is another sign that it is time to ditch PHP for a more “mature” programming language…

Debbie