php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #47418 number_format misbehaving with some values
Submitted: 2009-02-17 09:45 UTC Modified: 2013-02-18 00:33 UTC
Votes:17
Avg. Score:4.7 ± 0.6
Reproduced:15 of 17 (88.2%)
Same Version:4 (26.7%)
Same OS:8 (53.3%)
From: cu19 at gmx dot de Assigned:
Status: No Feedback Package: *Math Functions
PHP Version: 5.3.1 OS: Win7
Private report: No CVE-ID: None
 [2009-02-17 09:45 UTC] cu19 at gmx dot de
Description:
------------
In my script im using number_format to format values read from a MSSQL-DB via ODBC. Works fine, as long as the value is not equal to 1.9, 2.9, 3.9, 1.7 and probably some others.

I've made the following test script:
<?php
echo number_format(3.9, 2);
?>

Output is expected to be 3.90, but in fact is 9.8:
I don't know where this colon does come from, but for me it seems to be an error in number_format. Using sprintf('%4.2f', 3.9) leads to the same problem.

I'm using PHP 5.2.8 on Apache 2.2.9 on Windows XP MCE SP3. When Apache just got restarted, the test script works fine, but when executing my (quite large) script with several ODBC queries, it produces this error and after execution also the test script fails.

I've tried using 5.3 snapshot and 5.2.8 snap, the error occurs also. With PHP 5.2.6, the error didn't occur as far as I can remember. The error doesn't occur, too, with CLI.

Any idea? Or is it about the Apache webserver?

Reproduce code:
---------------
echo number_format(3.9, 2);

Expected result:
----------------
3.90

Actual result:
--------------
3.8:

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2009-02-17 09:48 UTC] cu19 at gmx dot de
In fact the result is 3.8:
9.8: was a typo
 [2009-02-17 10:13 UTC] cu19 at gmx dot de
After some additional recherche, it seems to me that the value is first internally represented as '3.89'. Then, the function checks the truncated digit which is larger than 5. So the last digit in the result is increased by one. In ASCII, after the '9' comes the ':', so when the last char of '3.89' is increased by one, it becomes '3.8:'. So when increasing the last digit there must be checked if it's smaller than 9.

What I don't understand is why it only occurs sometimes...

Don't know the PHP sources and where to look for this, so I kindly ask someone to look after this function.
 [2009-02-17 10:18 UTC] scottmac@php.net
Floating point values have a limited precision. Hence a value might 
not have the same string representation after any processing. That also
includes writing a floating point value in your script and directly 
printing it without any mathematical operations.

If you would like to know more about "floats" and what IEEE
754 is, read this:
http://docs.sun.com/source/806-3568/ncg_goldberg.html
 
Thank you for your interest in PHP.

It's the way floating point numbers work in computers, the canned response gives you a link to read about this some more.
 [2009-02-17 10:35 UTC] cu19 at gmx dot de
I don't think it's a problem with limited precision but rather about the function itself. I think a float should be able to represent 3 digits without a problem. The hex representation of the float that is to be formatted is 0x40799998, which equals to about 3.8999996.

I think number_format should be able to round this to '3.90' instead of displaying it as '3.8:'
 [2009-02-17 11:20 UTC] scottmac@php.net
scott@skinny [~] $ php -r 'var_dump(number_format(3.9, 2));'
string(4) "3.90"

Can't reproduce this on Linux.
 [2009-02-25 01:00 UTC] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
 [2010-02-12 11:31 UTC] tdietsche at comcast dot net
this sure IS a bug, scott_mac you need to wake up, if this is the best that php handles floating point then it is worthless. In reading a value of "19" from an excel spreadsheet via odbc, it formats it as "18.:0" which is pure garbage and a bug to me. How can you defend that?
I can send my code if you want.
 [2010-02-12 11:37 UTC] pajoye@php.net
For the garbage problem, that's something else. 

Can you provide a reproduce script (with data) please?
 [2010-02-12 11:55 UTC] cu19 at gmx dot de
That's nothing different, but exactly the same problem as I described from the beginning.

This is the function that I currently use as a workaround. It works fine, but I would prefer php doing this itself...

function my_number_format ($number, $decimals=0, $dec_point=',', $thousands_sep='.') {
	$string = number_format($number, $decimals, $dec_point, $thousands_sep);
	$pos = strlen($string) - 1;
	$increase = false;
	while ($pos >= 0) {
		if (($increase) and (is_numeric($string{$pos}))) {
			$string{$pos} = chr(ord($string{$pos}) + 1);
			$increase = false;
		}
		if ($string{$pos} == ':') {
			$string{$pos} = '0';
			$increase = true;
		}
		$pos--;
	}
	if ($increase)
		$string = '1' . $string;
	return $string;
}
 [2010-02-12 13:19 UTC] tdietsche at comcast dot net
/* 
set up an Excel 97-2003 spreadsheet with a worksheet 'Nov'
and the row headers below on row 3
then add a row 14 with "John Smith" in Tenant, "11" in Lot, and "19" in Late Fee.
Late fee should be number format with 2 decimals and comma thousands separator format.
Create an odbc system DSN called "rent_2009" linked to it.
Then run this script against it, I have stripped it down but I think it will show the problem.
note this will NOT show the problem if you just supply variable values directly, it has to come
from excel and has something to do with the (string) casting of values that excel thinks are numbers.
somehow the float and string are getting goofed up. Even when I fixed the value, the total it was
being added to still had the problem until I hardwired a second test on "18.:0" to fix that. Jeez.

(row headers):
Tenant
Lot
Prev Bal
Rent
LP Gas
Taxes
Garbage
Elec CR
Mowing
Late Fee
Misc
Deposit
New Bal
Check
Cash
Mail
Bal Fwd
Last Mon
This Mon
Change
Factor
Gallons
Rate/Gal
LP Amt
NOTES
Misc Desc

*/
$result    = '';
$month_total = 0;
$totals  = array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

function FmtFieldAmt($tot_num, $field_name) {
  global $result, $month_total, $totals;
  $res = odbc_result($result, $field_name);
  $fld_amt = GetDec($res);
  $fmt_amt = FmtAmt($fld_amt);
  $month_total      += $fmt_amt;
  $totals[$tot_num] += $fmt_amt;
  return $fmt_amt;
}
function GetDec($value) {
  if ($value == null || (string)$value == '')
    return 0;
  else
    return $value;
}
function FmtAmt($amt) {
  if ((string)$amt == '18.:') $amt = '19';  // another workaround for the total
  $cred = '&nbsp;&nbsp;&nbsp;&nbsp;';
  if ($amt < 0) {
    $amt  = 0 - $amt;
    $cred = 'CR';
  }
  // $amt = number_format($amt, 2) . $cred;  // uncomment this comment out next line to see bug
  $ret = stupid_php($amt) . $cred;
  return $ret;
}
// this is my workaround which works if the value doesn't already have the stupid colon in it
function stupid_php($val) {
  $len = strlen($val);
  if ($len == 0)
    return $val;
  if (strpos($val, '.') == false)
    return $val . '.00';
  // for 123.4  len=5 pos=3 return 123.45
  if (strpos($val, '.') == $len - 2)
    return $val . '0';
  if (strpos($val, '.') < $len - 2)
    return round($val, 2);
  return $val;
}
function jsi_db_error($query, $errno, $error) {
  exit ('**Database Error (' .$errno. ') ' .$error. ' Query=[' .$query. '] **');
}

$db_name = 'rent_2009';
$link  = odbc_connect($db_name, 'user', 'pw') 
          or jsi_db_error('Connect', odbc_errormsg(), odbc_error() );
$query = 'SELECT * FROM [Nov$A3:Z43] WHERE Lot = 11';
$result = odbc_exec($link, $query) or jsi_db_error($query, odbc_errormsg(), odbc_error());
echo FmtFieldAmt(6, 'Late Fee');
echo FmtAmt($totals[6]);
 [2010-02-12 13:32 UTC] tdietsche at comcast dot net
In case it might help, here is my php config (on WinXP SP3):


  PHP Version 5.3.1


System 	Windows NT DIY2004 5.1 build 2600 (Windows XP Professional
Service Pack 3) i586
Build Date 	Nov 19 2009 10:16:45
Compiler 	MSVC6 (Visual C++ 6.0)
Architecture 	x86
Configure Command 	cscript /nologo configure.js
"--enable-snapshot-build" "--disable-isapi" "--enable-debug-pack"
"--disable-isapi"
"--with-pdo-oci=D:\php-sdk\oracle\instantclient10\sdk,shared"
"--with-oci8=D:\php-sdk\oracle\instantclient10\sdk,shared"
"--with-oci8-11g=D:\php-sdk\oracle\instantclient11\sdk,shared"
"--enable-object-out-dir=../obj/"
Server API 	Apache 2.0 Handler
Virtual Directory Support 	enabled
Configuration File (php.ini) Path 	C:\WINDOWS
Loaded Configuration File 	C:\PHP\php.ini
Scan this dir for additional .ini files 	(none)
Additional .ini files parsed 	(none)
PHP API 	20090626
PHP Extension 	20090626
Zend Extension 	220090626
Zend Extension Build 	API220090626,TS,VC6
PHP Extension Build 	API20090626,TS,VC6
Debug Build 	no
Thread Safety 	enabled
Zend Memory Manager 	enabled
Zend Multibyte Support 	disabled
IPv6 Support 	enabled
Registered PHP Streams 	php, file, glob, data, http, ftp, zip,
compress.zlib, compress.bzip2, https, ftps, phar
Registered Stream Socket Transports 	tcp, udp, ssl, sslv3, sslv2, tls
Registered Stream Filters 	convert.iconv.*, string.rot13,
string.toupper, string.tolower, string.strip_tags, convert.*, consumed,
dechunk, zlib.*, bzip2.*
 [2010-02-12 14:07 UTC] pajoye@php.net
Can you try to isolate the value which causes problems and pass it directly to number_format? That will be easier to debug and fix.

Can you aslo try using 5.3.2RC2 (VC9 builds if possible, via CLI or using apachelounge.com's apache).
 [2010-02-12 14:15 UTC] tdietsche at comcast dot net
I am not sure what you mean by "isolate the value which causes problems and pass it directly to number_format". If I just assign 19 to a variable (either as a string or as a number) and do this, it works fine. It is only when it comes from a result row value of a number-formatted excel spreadsheet cell that it shows the problem, for me.

You could try a much simpler spreadsheet but I gave all the details of the one I am using that shows the problem, in case for some reason the spreadsheet details are related somehow (probably not though).

I don't have time to install a new version of php right now, maybe later. I have already spent more time on this than I can afford.

One other note, I see in playing around I changed some variable names in one function, it should really read:

function FmtAmt($amt) {
  // another workaround for the total
  if ((string)$amt == '18.:') $amt = '19';  
  $cred = '&nbsp;&nbsp;&nbsp;&nbsp;';
  if ($amt < 0) {
    $amt  = 0 - $amt;
    $cred = 'CR';
  }
  // uncomment this and comment out next line to see bug
  // $ret = number_format($amt, 2) . $cred;  
  $ret = stupid_php($amt) . $cred;
  return $ret;
}
 [2010-02-12 14:18 UTC] pajoye@php.net
It could be an encoding issue then (before it gets formatted). Can you zip the script and the excel file you use and post a link to it here? Or drop me a mail.
 [2010-02-12 19:10 UTC] tdietsche at comcast dot net
Yes, I will do that and email you a link. The information is confidential so I must first remove people's personal information first (names etc). Please remove the file when you are done with it.
 [2010-02-12 23:01 UTC] tdietsche at comcast dot net
pajoye: I have just sent you an email with an attached zip, please reply here or to my email to confirm that you received it, thanks.
 [2010-02-20 01:00 UTC] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
 [2010-03-08 03:37 UTC] tdietsche at comcast dot net
did you ever get my email?
 [2010-07-17 05:53 UTC] adrianv at taly dot net
Code to reproduce this, where lib.php contains the my_number_format function as described by cu19 at gmx dot de. Numbers are internally correct, but display incorrectly.

<?php

	include_once("lib.php");

 	echo "== Demo 1 == <BR>";

	$deb = 10.50;
	$crd = 1;
	$tot = 0;

	for ($t=1;$t<=300;$t++) {
		$tot += $deb - $crd;
		echo "$t  $tot";
		echo " >>>> ";
		echo strval($tot);
		echo " >>>> ";
		echo sprintf('%F',$tot);
		echo " >>>> ";
		echo number_format($tot,2);
		echo " >>>> ";
		echo my_number_format($tot,2);
		echo " >>>> ";
		echo "<BR>";
	}
?>
 [2010-11-30 23:23 UTC] carrieraglan at gmail dot com
Apache/2.2.11 
PHP/5.2.10

$sql = "SELECT FIELD_1 FROM DB2_TABLE WHERE ID = ?";
$cur = odbc_prepare($db_handle, $sql);
odbc_execute($cur, array(20382));
$field_1 = odbc_result($cur, "FIELD_1");
$field_1 = trim($field_1);
print "<br>".$field_1;
print "<br>".number_format($field_1, 2, '.', '');
print "<br>".number_format($field_1, 2, '.', ',');

Expected Result:
1.90000000000000E+001
19.00
19.00

Actual Result:
1.90000000000000E+001
18.:0
18.:0
 [2011-01-24 20:47 UTC] pjs67123 at yahoo dot com
Having the same problem on PHP 5.2.13 and Apache 2.2.14 on Windows 2003 Server.

<?php
echo 1.7;
?>

produces =>

1.6:

This happens after running Apache for about a week and then consistently happens EVERY time.  Restarting Apache fixes the problem until about a week later.  I consider this to be a serious problem that renders PHP useless for anything beyond casual applications.
 [2011-01-24 21:03 UTC] pajoye@php.net
-Status: No Feedback +Status: Feedback
 [2011-01-24 21:03 UTC] pajoye@php.net
VC6 builds I suppose?
 [2012-03-28 08:20 UTC] miguelzubiaga at ipronet dot es
Not a number_format() problem, a simple echo also fails.
<?php echo 1.7 ?>
expected: 1.7
result: 1.6:

using Apache/2.2.11 (Win32) DAV/2 PHP/5.2.9-2

Restarting apache fixes the problem temporarily
 [2013-02-18 00:33 UTC] php-bugs at lists dot php dot net
No feedback was provided. The bug is being suspended because
we assume that you are no longer experiencing the problem.
If this is not the case and you are able to provide the
information that was requested earlier, please do so and
change the status of the bug back to "Open". Thank you.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 14:01:32 2024 UTC