php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #65485 Cast gives different values in one way or the other
Submitted: 2013-08-20 08:50 UTC Modified: 2013-08-20 17:35 UTC
From: stephane at it-asia dot com Assigned:
Status: Not a bug Package: *General Issues
PHP Version: 5.4.18 OS: Windows 8 & Mint Maya
Private report: No CVE-ID: None
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: stephane at it-asia dot com
New email:
PHP Version: OS:

 

 [2013-08-20 08:50 UTC] stephane at it-asia dot com
Description:
------------
I have different value when I cast a double to int and when I cast to string before casting to int.

I understand 39.48 is difficult to store in base 2.

The problem is the cast algorythm is not the same if you cast a float to int or if you cast a float to string, This involves huges mistakes in accountancy software. Whatever the way you choose (float -> int or float -> string -> int ) , you should have the same result at the end.

Please define the right way to process data in that case.

I have the same problem with almost every machines, Windows or Debian based.

Thanks !

Test script:
---------------
$d = "39.48" * "100";
print("39.48 * 100 : ");
var_dump ($d);

$i = (int) $d;
print("<br />int:  ");
var_dump ($i);

$s = (string) $d;
print("<br />string:  ");
var_dump ($s);

$i = (int) $s;
print("<br />int:  ");
var_dump ($i);

Expected result:
----------------
same value if you cast double => int and if you cast double => string => int

Actual result:
--------------
39.48 * 100 : double(3948)
int:  int(3947)
string:  string(4) "3948"
int:  int(3948)

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2013-08-20 09:02 UTC] johannes@php.net
-Status: Open +Status: Not a bug
 [2013-08-20 09:02 UTC] johannes@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://www.floating-point-gui.de/

Thank you for your interest in PHP.

Casting to string will be imprecise, then doing cast to int will be imprecise again. Two imprecisions increase the difference.
 [2013-08-20 09:46 UTC] stephane at it-asia dot com
Yes I know about the imprecision and the link, this is what you say all the time here.

But if you THINK about the matter, the casting algorythm is different, and this is WRONG.

Other languages found solutions for that. I can't believe in 2013 PHP still cannot provide a reliable solution to make simple operation like "multiplications" and int casting.

The cast algorythm for float -> string -> int is GOOD. It is ALL THE TIME.

The cast algorythm for float -> int is WRONG. It is ALL THE TIME.

Please implement the same solution in both sides, hence choose the good one.
 [2013-08-20 09:53 UTC] nikic@php.net
I think you misunderstood how the casts work:

An (int) cast rounds DOWN to the nearest integer (in all sane languages).

A (string) cast just converts the float to a string representation. This representation has the precision specified with the "precision" ini setting.

So what you are comparing here is a down-round ( floor($f) ) with a round-with-precision ( round($f, $prec) ). Of course the result will be different.
 [2013-08-20 10:14 UTC] stephane at it-asia dot com
I don't find sane to see a 3948 float value going to 3947 after an int casting, I really, really don't find it sane.

I don't find sane as well to see the same number ending with different value after several casts. The cast doesn't provide the same precision when it is one case, ot the other. One uses floor(), when the other uses (round). And to be honest, one is obviously right, when the other is obviously wrong. 

Please provide examples where the float -> string -> int value doesn't give satisfaction. 

Because for me, the float -> int cast NEVER gives satisfaction when there's a decimal number involved.

The right way is the way giving the right solution, It is not the one giving the wrong answer for the only pleasure to match impossibility theory.

In every case, please add to the documentation that the float -> int cast gives stupid results because ti tends to respect a theoric lack orp precision, when the float -> string cast tends to provide a more acceptable answer.
 [2013-08-20 11:10 UTC] nikic@php.net
@stephane: Float-to-integer casts are the same across all languages, at least as far as I am aware. They are always truncations (i.e. they just take the integer portion of the number and drop the rest. For positive numbers truncation and floor are the same).

I am sorry if this does not match your expectations, but this is standard behavior that everyone uses and we will not deviate from it.

How integer to float casts work is documented here: http://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting

 > When converting from float to integer, the number will be rounded towards zero.

See also the warning on that page, which deals with exactly your situation.

In your case, what you are probably looking for is just the round() function, which will do a round towards the closest integer, rather than a round towards zero (truncation).
 [2013-08-20 12:08 UTC] rasmus@php.net
You mean like this in Python:

>>> int(39.48*100)
3947
>>> "%s" % (39.48 * 100)
'3948.0'

This is not PHP-specific in any way.
 [2013-08-20 13:44 UTC] stephane at it-asia dot com
Hi Rasmus

Good to find you here.

I understand you follow the C engine. But in C , you have 39.48*100 =  3947.9999999999995  and in Python, you have 39.48*100 = 3947 when PHP shows 3948 . 

PHP makes a few more things than that. It is not exactly the same. 

More, when I make tests in the php.ini to increase the precision to 20, the float → string gives  3947.9999999999995453  (and 3947.99999999999954525264911353588104248046875 for a precision of 100)

And the vardump doesn't really give the real full value : it is confusing. var_dump (39.48 * 100) => float(3948)   => this is not the real value. 

Better example: 
$d1 = 39.48 * 100;
$d2 = 3948.0;
$i1 = (int) $d1;
$i2 = (int) $d2;

var_dump($d1);
var_dump($d2);
var_dump($i1);
var_dump($i2);

it returns float(3948) float(3948) int(3947) int(3948) 
it should return float(3947.99999999999954525264911353588104248046875) float(3948) int(3947) int(3948) 

Can we have a more precise vardump, so we know exactly what happens? I lost time because of that. If the vardump gave me  3947.99999999999954525264911353588104248046875 this morning, I would have understood directly what happened. But it gave me 3948, and it is not the real value.

I was accustomed to see PHP to provide easy and direct solutions. I don't know why, but I was totally convinced we found a way to go over this kind of problems. Obviously, talk to any developer around you, nobody never expects to see 3948 become 3947 after a cast when a decimal number was involved 100 lines before.

In VB6, the result is always 3948 (even for double → int casting ), and they have a currency type. SQL server created a "money" type to avoid this kind of problems ( http://technet.microsoft.com/en-us/library/ms179882.aspx ). I hate saying that, but these guys found  solutions when we still didn't.

Rasmus, can I suggest to consider a new type for “money” treatments? So we will still have the normal int and float that match the C standard, and we will have something to make time and go directly to the point . For example, a “64-bit (8-byte) numbers in an integer format, scaled by 10,000 to give a fixed-point number with 15 digits to the left of the decimal point and 4 digits to the right”. So (int)39.48*100 will never return 3947 anymore, and brutal and lemmings coders like me will make better accountancy software.

Thanks :)
 [2013-08-20 15:19 UTC] rasmus@php.net
var_dump() is giving you the real value based on your precision setting. If you 
add ini_set('precision',32); to the top of your test script, you will see:

float(3947.9999999999995452526491135359)
float(3948)
int(3947)
int(3948)

And this is exactly the same as C and Python.

And no, there won't be a money type in PHP. This is trivial to do in user space.
 [2013-08-20 16:40 UTC] stephane at it-asia dot com
OK, you the boss.

>This is trivial to do in user space.

So what's your recommendation there?
 [2013-08-20 17:35 UTC] rasmus@php.net
Always always always write all apps that deal with money using only integers. You 
never work with 17.99 you work with 1799 and only at display time do you add a 
decimal place. All internal manipulation is done using integers without any 
precision loss.
 [2013-08-21 14:12 UTC] stephane at it-asia dot com
OK

Thanks for you answer, your time, and everything you do for us.

S.L.
 
PHP Copyright © 2001-2025 The PHP Group
All rights reserved.
Last updated: Fri May 02 11:01:28 2025 UTC