Some help regarding rounding

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
GreyWing
Forum Newbie
Posts: 2
Joined: Sat Nov 20, 2010 4:44 am

Some help regarding rounding

Post by GreyWing »

Hi Guys

New to the forum and probably going to ask more questions than I could answer unfortunately as only have basic knowledge of php.

I have a problem with a bit of code that keeps rounding the 2'nd decimal place up. I'm trying to prevent it doing this but as yet still no luck. I am running PHP : 5.2.12. Would be grateful if anyone could point me in the right direction to stop it doing this. Basically we are trying to get it to invoice $1.134 but it is rounding up to $1.13 which when we mutliply it per product is a way off the correct total. I assume (almost certain) it is in the code below. Does anyone know what I could do to stop it doing this... Would be more than happy to make a donation to the forum or individual.

Cheers
Graeme

//Replacement for PHP's crud number_format/round functions which round down for even numbers on Linux
//This one rounds up whether odd or even, as it should!!
function format_number($float, $decimal_places = null, $format_negative = false, $suppress_commas = true, $negative_in_brackets = true)
{
if ($decimal_places === null)
{
$decimal_places = 4; //TODO: Load from config
}
else
{
$decimal_places = intval($decimal_places);
}

if (nbf_globals::$system_locale)
{
@setlocale(LC_ALL, nbf_globals::$system_locale); //You could force this to a different locale here if your system default one is not right
}

$is_negative = $float < 0;
if ($is_negative)
{
$float = abs($float);
}

$str_float = str_replace(",", ".", $float . ""); //Convert float to string, and if locale indicates a comma decimal separator, replace with dot
if (strpos($str_float, ".") !== false && substr($str_float, strlen($str_float) - 1) == '5')
{
$float += 0.00001;
}

$ret_val = $float;

if ($is_negative)
{
$ret_val = 0 - $ret_val;
}

if ($suppress_commas)
{
$thousands = "";
}
else
{
$thousands = ",";
}

$ret_val = number_format($ret_val, $decimal_places, ".", $thousands);

if ($format_negative)
{
if ($ret_val < 0)
{
if ($negative_in_brackets)
{
$ret_val = "<span style=\"color:#ff0000;\">(" . str_replace("-", "", $ret_val) . ")</span>";
}
else
{
$ret_val = "<span style=\"color:#ff0000;\">$ret_val</span>";
}
}
}

if (!$suppress_commas && $format_negative && function_exists("localeconv")) //Only applied to HTML output (invoices/reports)
{
$locale_info = localeconv();
if (strlen(@$locale_info['mon_thousands_sep']) > 0 && strlen(@$locale_info['mon_thousands_sep']) < 4)
{
$ret_val = str_replace(",", "!!#!!", $ret_val); //In case decimal point becomes comma
}
if (strlen(@$locale_info['mon_decimal_point']) > 0 && strlen(@$locale_info['mon_decimal_point']) < 4)
{
$ret_val = str_replace(".", @$locale_info['mon_decimal_point'], $ret_val);
}
if (strlen(@$locale_info['mon_thousands_sep']) > 0 && strlen(@$locale_info['mon_thousands_sep']) < 4)
{
$ret_val = str_replace("!!#!!", @$locale_info['mon_thousands_sep'], $ret_val);
}
}

//Revert back to default US locale so that database inserts are not messed up by unexpected commas
@setlocale(LC_ALL, "en_US", "en-US", "en", "English_United States", "en_US.UTF-8");

return $ret_val;
}

//The following functions are based on code in the public domain (PHP user comments)
function float_cmp($f1, $f2, $precision = null) // are 2 floats equal
{
if ($precision == null)
{
$precision = 3; //TODO: Load from config
}
$f1 = $f1 ? $f1 : 0;
$f2 = $f2 ? $f2 : 0;
$e = pow(10, $precision);
$first = $f1 * $e;
$second = $f2 * $e;
//Handle exponential notation
if (strpos($first, "E") !== false)
{
$first = sprintf("%d", $first);
}
if (strpos($second, "E") !== false)
{
$second = sprintf("%d", $second);
}
$i1 = intval((string)$first);
$i2 = intval((string)$second);
return ($i1 == $i2);
}

function float_gtr($big, $small, $precision = null) // is one float bigger than another
{
if ($precision == null)
{
$precision = 3; //TODO: Load from config
}
$big = $big ? $big : 0;
$small = $small ? $small : 0;
$e = pow(10, $precision);
$first = $big * $e;
$second = $small * $e;
//Handle exponential notation
if (strpos($first, "E") !== false)
{
$first = sprintf("%d", $first);
}
if (strpos($second, "E") !== false)
{
$second = sprintf("%d", $second);
}
$ibig = intval((string)$first);
$ismall = intval((string)$second);
return ($ibig > $ismall);
}

function float_gtr_e($big, $small, $precision = null) // is on float bigger or equal to another
{
if ($precision == null)
{
$precision = 3; //TODO: Load from config
}
$big = $big ? $big : 0;
$small = $small ? $small : 0;
$e = pow(10, $precision);
$first = $big * $e;
$second = $small * $e;
//Handle exponential notation
if (strpos($first, "E") !== false)
{
$first = sprintf("%d", $first);
}
if (strpos($second, "E") !== false)
{
$second = sprintf("%d", $second);
}
$ibig = intval((string)$first);
$ismall = intval((string)$second);
return ($ibig >= $ismall);
}

function float_add($f1, $f2, $precision = null) //Add 2 floats together (return as string)
{
if ($precision == null)
{
$precision = 3; //TODO: Load from config
}
$f1 = $f1 ? $f1 : 0;
$f2 = $f2 ? $f2 : 0;
$e = pow(10, $precision);
$first = $f1 * $e;
$second = $f2 * $e;
//Handle exponential notation
if (strpos($first, "E") !== false)
{
$first = sprintf("%d", $first);
}
if (strpos($second, "E") !== false)
{
$second = sprintf("%d", $second);
}
$result = intval((string)$first) + intval((string)$second);
$is_negative = $result < 0;
$result = str_pad(abs($result), 3, "0", STR_PAD_LEFT);
$result = $is_negative ? "-" . $result : $result;
$result = substr($result, 0, strlen($result) - $precision) . "." . substr($result, strlen($result) - $precision);
return $result;
}

function float_subtract($minuend, $subtrahend, $precision = null) //Subtract subtrahend from minuend (return string)
{
if ($precision == null)
{
$precision = 3; //TODO: Load from config
}
$minuend = $minuend ? $minuend : 0;
$subtrahend = $subtrahend ? $subtrahend : 0;
$e = pow(10, $precision);
$first = $minuend * $e;
$second = $subtrahend * $e;
//Handle exponential notation
if (strpos($first, "E") !== false)
{
$first = sprintf("%d", $first);
}
if (strpos($second, "E") !== false)
{
$second = sprintf("%d", $second);
}
$result = intval((string)$first) - intval((string)$second);
$is_negative = $result < 0;
$result = str_pad(abs($result), 3, "0", STR_PAD_LEFT);
$result = $is_negative ? "-" . $result : $result;
$result = substr($result, 0, strlen($result) - $precision) . "." . substr($result, strlen($result) - $precision);
return $result;
User avatar
greyhoundcode
Forum Regular
Posts: 613
Joined: Mon Feb 11, 2008 4:22 am

Re: Some help regarding rounding

Post by greyhoundcode »

What we can't see from the code you posted is how those functions are being called.

Anyway, the function responsible for rounding numbers appears to be the first one, format_number(). I'm guessing that somewhere, probably in a different bit of code, that is being called and it is being told to round up to 2 places instead of 3.

Take a look at the last couple of lines in the code below:

Code: Select all

// Your number formatting function ...
function format_number($float, $decimal_places = null, $format_negative = false, $suppress_commas = true, $negative_in_brackets = true)
{
    if ($decimal_places === null)
    {
        $decimal_places = 4; //TODO: Load from config
    }
    else
    {
        $decimal_places = intval($decimal_places);
    }
    
    // Commented out temporarily
    /* if (nbf_globals::$system_locale)
    {
        @setlocale(LC_ALL, nbf_globals::$system_locale); //You could force this to a different locale here if your system default one is not right
    } */
    
    $is_negative = $float < 0;
    
    if ($is_negative)
    {
        $float = abs($float);
    }
    
    $str_float = str_replace(",", ".", $float . ""); //Convert float to string, and if locale indicates a comma decimal separator, replace with dot
    
    if (strpos($str_float, ".") !== false && substr($str_float, strlen($str_float) - 1) == '5')
    {
        $float += 0.00001;
    }
    
    $ret_val = $float;
    
    if ($is_negative)
    {
        $ret_val = 0 - $ret_val;
    }
    
    if ($suppress_commas)
    {
        $thousands = "";
    }
    else 
    {
        $thousands = ",";
    }
    
    $ret_val = number_format($ret_val, $decimal_places, ".", $thousands);
    
    if ($format_negative)
    {
        if ($ret_val < 0)
        {
            if ($negative_in_brackets)
            {
                $ret_val = "<span style=\"color:#ff0000;\">(" . str_replace("-", "", $ret_val) . ")</span>";
            }
            else
            {
            $ret_val = "<span style=\"color:#ff0000;\">$ret_val</span>";
            }
        }
    }
    
    if (!$suppress_commas && $format_negative && function_exists("localeconv")) //Only applied to HTML output (invoices/reports)
    {
        $locale_info = localeconv();
        
        if (strlen(@$locale_info['mon_thousands_sep']) > 0 && strlen(@$locale_info['mon_thousands_sep']) < 4)
        {
            $ret_val = str_replace(",", "!!#!!", $ret_val); //In case decimal point becomes comma
        }
        
        if (strlen(@$locale_info['mon_decimal_point']) > 0 && strlen(@$locale_info['mon_decimal_point']) < 4)
        {
            $ret_val = str_replace(".", @$locale_info['mon_decimal_point'], $ret_val);
        }
        
        if (strlen(@$locale_info['mon_thousands_sep']) > 0 && strlen(@$locale_info['mon_thousands_sep']) < 4)
        {
            $ret_val = str_replace("!!#!!", @$locale_info['mon_thousands_sep'], $ret_val);
        }
    }
    
    //Revert back to default US locale so that database inserts are not messed up by unexpected commas
    @setlocale(LC_ALL, "en_US", "en-US", "en", "English_United States", "en_US.UTF-8");
    
    return $ret_val;
}

// I'm using 6.21345 as an example number.
// We pass the number itself first, then the
// number of decimal places:
echo format_number(6.21345, 3);

// Whereas for 2 decimal places we would do:
echo format_number(6.21345, 2);
What I'd suggest is doing a search for "format_number(" in your IDE and look for code that is asking for 2 decimal places rather than 3.
GreyWing
Forum Newbie
Posts: 2
Joined: Sat Nov 20, 2010 4:44 am

Re: Some help regarding rounding

Post by GreyWing »

Thanks mate, looking now

Will reply with answer,

Cheers
Graeme
Post Reply