php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #77278 floatval(strval(value)) truncates if locale's decimal_point isn't a period
Submitted: 2018-12-10 16:55 UTC Modified: 2018-12-10 22:16 UTC
From: spam2 at rhsoft dot net Assigned:
Status: Verified Package: Scripting Engine problem
PHP Version: Irrelevant OS:
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2018-12-10 16:55 UTC] spam2 at rhsoft dot net
Description:
------------
i doubt that it is expected behavior when some buggy library seems to play around with LOCALE that unformatted output of float vars changes from dot to a comma

$pages_summary = (string)($count_summary / 3);
echo "PAGES-SUMMARY: $pages_summary\n";

https://github.com/htacg/tidy-html5/issues/780#issuecomment-445885150

libtidy-5.6.0-2.fc28.x86_64.rpm
[harry@srv-rhsoft:~]$ php /downloads/tidy-debug.php
PAGES-SUMMARY: 3,3333333333333
PAGES-SUMMARY: 3,3333333333333
CORRUPTION!

libtidy-5.4.0-4.fc28.20181003.rh.x86_64.rpm
[harry@srv-rhsoft:~]$ php /downloads/tidy-debug.php
PAGES-SUMMARY: 3.3333333333333
PAGES-SUMMARY: 4
OK

Test script:
---------------
libtidy 5.6 is touching LOCALE somewhere without a proper reset - period
look at the comma instad dot after typecasting a float value

libtidy-5.6.0-2.fc28.x86_64.rpm
[harry@srv-rhsoft:~]$ php /downloads/tidy-debug.php
PAGES-SUMMARY: 3,3333333333333
PAGES-SUMMARY: 3,3333333333333
CORRUPTION!

libtidy-5.4.0-4.fc28.20181003.rh.x86_64.rpm
[harry@srv-rhsoft:~]$ php /downloads/tidy-debug.php
PAGES-SUMMARY: 3.3333333333333
PAGES-SUMMARY: 4
OK

/** comment out this line and everything is fine with libtidy-5.6.0-2.fc28.x86_64 too */
$tidy = tidy_parse_string('bla', [], 'latin1');

/** that code is completly unrelated to tidy and must not change it's behavior */
$conn = mysqli_init();
mysqli_real_connect($conn, $host, $user, $pwd, $db);
$result = mysqli_query($conn, "select SQL_CALC_FOUND_ROWS * from cl_autotest_youtube_items where yi_cid='1' and yi_aktiv='1' order by yi_sort asc limit 0, 3");
$count_summary = mysqli_fetch_row(mysqli_query($conn, 'select SQL_NO_CACHE found_rows()'))[0];
$pages_summary = (string)($count_summary / 3);
echo "PAGES-SUMMARY: $pages_summary\n";
if($pages_summary > (int)$pages_summary)
{
$pages_summary = (int)$pages_summary + 1;
}
echo "PAGES-SUMMARY: $pages_summary\n";
if((int)$pages_summary !== 4)
{
echo "CORRUPTION!\n";
}
else
{
echo "OK\n";
}

Expected result:
----------------
PAGES-SUMMARY: 3.3333333333333
PAGES-SUMMARY: 4

Actual result:
--------------
PAGES-SUMMARY: 3,3333333333333
PAGES-SUMMARY: 3,3333333333333
CORRUPTION!

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-12-10 17:36 UTC] spam2 at rhsoft dot net
standalone sample

<?php declare(strict_types=1);
 $host = 'localhost';
 $user = 'autotest';
 $pwd  = '****';
 $db   = 'autotest';

 /** comment out this line and everything is fine with libtidy-5.6.0-2.fc28.x86_64 too */
 $tidy = tidy_parse_string('bla', [], 'latin1');

 /** that code is completly unrelated to tidy and must not change it's behavior */
 $conn = mysqli_init();
 mysqli_real_connect($conn, $host, $user, $pwd, $db);
 mysqli_query($conn, "DROP TABLE IF EXISTS `tidy_test`;");
 mysqli_query($conn, "CREATE TABLE IF NOT EXISTS `tidy_test` (`tidy_id` mediumint(7) UNSIGNED NOT NULL AUTO_INCREMENT, `tidy_test` char(2), PRIMARY KEY (`tidy_id`));");
 for($i=0; $i<=10; $i++)
 {
  mysqli_query($conn, "insert into `tidy_test` (`tidy_test`) values ('$i');");
 }
 $result = mysqli_query($conn, "select SQL_CALC_FOUND_ROWS * from `tidy_test`");
 $count_summary = mysqli_fetch_row(mysqli_query($conn, 'select SQL_NO_CACHE found_rows()'))[0];
 $pages_summary = (string)($count_summary / 3);
 echo "PAGES-SUMMARY: $pages_summary\n";
 if($pages_summary > (int)$pages_summary)
 {
  $pages_summary = (int)$pages_summary + 1;
 }
 echo "PAGES-SUMMARY: $pages_summary\n";
 if((int)$pages_summary !== 4)
 {
  echo "CORRUPTION!\n";
 }
 else
 {
  echo "OK\n";
 }
 mysqli_query($conn, "DROP TABLE IF EXISTS `tidy_test`;");
?>
 [2018-12-10 18:40 UTC] requinix@php.net
I assume you're not trying to use this bug report for Tidy changing locale but that there's an inconsistency between casting floats to strings and strings to floats when it comes to the decimal symbol?
 [2018-12-10 18:47 UTC] spam2 at rhsoft dot net
> but that there's an inconsistency between casting floats 
> to strings and strings to floats when it comes to the decimal symbol?

that's exactly the point - oustide of format functions my locale de_DE.UTF-8 normally don't matter and shouldn't do so but afer a simple tidy call anywhere every following php code doing type casts on float values becomes a lottery
 [2018-12-10 19:37 UTC] requinix@php.net
-Summary: libtidy 5.6 changes behavior of casting results +Summary: floatval(strval(value)) truncates if locale's decimal_point isn't a period -Status: Open +Status: Verified
 [2018-12-10 19:37 UTC] requinix@php.net
var_dump(setlocale(LC_NUMERIC, "de_DE", "German_Germany"));
var_dump(1 / 2); float 0,5
var_dump(strval(1 / 2)); // string "0,5"
var_dump(floatval(strval(1 / 2))); // float 0
 [2018-12-10 21:29 UTC] nikic@php.net
I think we should get rid of the locale-dependent float to string cast in PHP 8, but for now this is working as "intended". We should definitely *not* make string to float cast locale-sensitive as well, that would only make a bad situation worse.
 [2018-12-10 21:33 UTC] spam2 at rhsoft dot net
working as intended?

a random linked library calls locale and the whole behavior of internal php typecast changes for every follow-up code in a PHP application is intended? really?
 [2018-12-10 21:41 UTC] requinix@php.net
You can't blame PHP for Tidy's problem. If they change locale (already a dangerous operation since it's not thread-safe) and then don't revert it to the previous setting when they're done, that's their fault.
 [2018-12-10 21:45 UTC] spam2 at rhsoft dot net
nonsense! show me the PHP code where type casts after setlocale() change tehir meaning!

you can't even distinct what happens by use setlocale('whatever', NULL)  before and after the tidy call because i get always get de_DE.UTF-8 as result
 [2018-12-10 21:52 UTC] spam2 at rhsoft dot net
ok, you can partially repdoduce that behavior

echo (string)4/3, "\n";
setlocale(LC_ALL, 'en_US.UTF-8');
echo (string)4/3, "\n";
setlocale(LC_ALL, 'de_DE.UTF-8');
echo (string)4/3, "\n";

-------------------------------

BUT YOU CANN NOT GET THE CURRECT LOCALE WITH PHP

echo (string)4/3, "\n";
setlocale(LC_ALL, 'en_US.UTF-8');
echo setlocale(LC_ALL, NULL), "\n";
echo (string)4/3, "\n";
setlocale(LC_ALL, 'de_DE.UTF-8');
echo setlocale(LC_ALL, NULL), "\n";
echo (string)4/3, "\n";

[harry@srv-rhsoft:/downloads]$ php test.php
1.3333333333333
de_DE.UTF-8
1,3333333333333
de_DE.UTF-8
1,3333333333333

-------------------------------

why don't that shit mention 'en_US.UTF-8' which i would have liked to put at the libtidy bugreport instead wasting all my time with assumptions?

there is anyways no sibgle pojnt of have TYPECASTS behave different - that's what format functions are!
 [2018-12-10 21:59 UTC] nikic@php.net
-Block user comment: No +Block user comment: Yes
 [2018-12-10 21:59 UTC] nikic@php.net
Please calm down.
 [2018-12-10 22:16 UTC] requinix@php.net
spam2, there's two bugs here:

1. PHP converts by locale in one direction (float->string) but not in the other direction (string->float). It should be consistent. But like @nikic said, this inconsistency is currently the intended behavior - not the correct behavior, merely the *intended* behavior. It does need to get fixed but it's not a simple thing to just do.

2. Tidy changes locales for whatever reason. Okay. But it does not change the locale back to the original value when it is done, and that is not okay. It needs to change it back in case the calling code had set a locale and still needs it. That is just basic etiquette for any library code. This is a bug Tidy needs to fix, not PHP.

Getting the locale is possible by passing null to setlocale. You probably aren't seeing en_US because you don't have the locale installed, and calling setlocale with it would have returned false. Do `locale -a` to see what you have available.
 
PHP Copyright © 2001-2019 The PHP Group
All rights reserved.
Last updated: Sun Oct 20 03:01:26 2019 UTC