КИТА unofficial

Компьютерный => Веб-программирование => Тема начата: naxellar от Июль 02, 2007, 11:06:18



Название: Траблы в PHP
Отправлено: naxellar от Июль 02, 2007, 11:06:18
Код: (php)
echo floor(493/29).' '.$Member->MemberActiveMoney.' '.floor($Member->MemberActiveMoney/29);
Выдается ответ:
17 493 16

Почему???


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 02, 2007, 07:29:58
Код: (php)
<?php
class mem
{
var $MemberActiveMoney;
}

$Member = new mem;

$Member->MemberActiveMoney 492.9999999999;

echo 
floor(493/29).&#39; &#39;.$Member->MemberActiveMoney.&#39; &#39;.floor($Member->MemberActiveMoney/29);
?>

Результат
Цитировать
17 493 16

Полагаю, это связано наверное с потерей точности при делении:

Цитата: http://www.php.net
Точность числа с плавающей точкой

Довольно часто простые десятичные дроби вроде 0.1 или 0.7 не могут быть преобразованы в свои внутренние двоичные аналоги без небольшой потери точности. Это может привести к неожиданным результатам: например, floor((0.1+0.7)*10) скорее всего возвратит 7 вместо ожидаемой 8 как результат внутреннего представления числа, являющегося в действительности чем-то вроде 7.9999999999....

Это связано с невозможностью точно выразить некоторые дроби в десятичной системе счисления конечным числом цифр. Например, 1/3 в десятичной форме принимает вид 0.3333333. . ..

Так что никогда не доверяйте точности последних цифр в результатах с числами с плавающей точкой и никогда не проверяйте их на равенство. Если вам действительно необходима высокая точность, вам следует использовать математические функции произвольной точности или gmp-функции.


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 02, 2007, 07:38:06
В моем коде - десять раз обновляю страницу и результат всегда одинаковый. Или ты имеешь ввиду, что при обработке твоего кода все становится нормально?

Блин... тут же нет телепатов. Надо было это сразу писать.

И вообще объявление класса и способ инициализации члена класса - в студию!


Название: Re: Траблы в PHP
Отправлено: naxellar от Июль 02, 2007, 07:43:43
Код: (php)
<?php
  
class GameMember {
var $MemberID$MemberLogin$MemberSurname$MemberName$MemberRegistrationDateTime$MemberSex$MemberDepartment$MemberCourse$MemberProfession$MemberLastLocation$MemberHealth$MemberActiveMoney$MemberEnergy$MemberEuroMoney$MemberAuthorized;

function __construct() {
global $MySQL$ENW;
session_start();
if (isset($_POST[&#39;AuthorizationLogin&#39;]) && isset($_POST[&#39;AuthorizationPassword&#39;])) {
$mysql_result=$MySQL->SendQuery(&#39;SELECT `MemberID`,`MemberLogin`,`MemberSurname`,`MemberName`,`MemberSex`,UNIX_TIMESTAMP(`MemberRegistrationDateTime`) AS `MemberRegistrationDateTime`,`MemberDepartment`,`MemberCourse`,`MemberProfession`,`MemberLastLocation`,`MemberHealth`,`MemberEnergy`,`MemberActiveMoney`,`MemberEuroMoney` FROM `members` WHERE `MemberLogin`=? AND `MemberPassword`=?&#39;,$_POST[&#39;AuthorizationLogin&#39;],md5($_POST[&#39;AuthorizationPassword&#39;]));
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
$LogIn=true;
}elseif (isset($_SESSION[&#39;UID&#39;])) {
$mysql_result=$MySQL->SendQuery(&#39;SELECT `MemberID`,`MemberLogin`,`MemberSurname`,`MemberName`,`MemberSex`,UNIX_TIMESTAMP(`MemberRegistrationDateTime`) AS `MemberRegistrationDateTime`,`MemberDepartment`,`MemberCourse`,`MemberProfession`,`MemberLastLocation`,`MemberHealth`,`MemberEnergy`,`MemberActiveMoney`,`MemberEuroMoney` FROM `members` WHERE `MemberID`=?&#39;,$_SESSION[&#39;UID&#39;]);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
$LogIn=false;
}else {
$this->MemberAuthorized=false;
return;
}
if (mysql_num_rows($mysql_result)==1) {
$Data=mysql_fetch_array($mysql_result);
$_SESSION[&#39;UID&#39;]=$Data[&#39;MemberID&#39;];
$this->MemberID=$Data[&#39;MemberID&#39;];
$this->MemberLogin=$Data[&#39;MemberLogin&#39;];
$this->MemberSurname=$Data[&#39;MemberSurname&#39;];
$this->MemberName=$Data[&#39;MemberName&#39;];
$this->MemberRegistrationDateTime=$Data[&#39;MemberRegistrationDateTime&#39;];
$this->MemberSex=$Data[&#39;MemberSex&#39;];
$mysql_result=$MySQL->SendQuery(&#39;SELECT `DepartmentName` FROM `departments` WHERE `DepartmentID`=?&#39;,$Data[&#39;MemberDepartment&#39;]);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
if (mysql_num_rows($mysql_result)==1) $this->MemberDepartment=mysql_result($mysql_result,0,&#39;DepartmentName&#39;);
else $this->MemberDepartment=&#39;No&#39;;
$this->MemberCourse=$Data[&#39;MemberCourse&#39;];
$mysql_result=$MySQL->SendQuery(&#39;SELECT `ProfessionName` FROM `professions` WHERE `ProfessionID`=?&#39;,$Data[&#39;MemberProfession&#39;]);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
if (mysql_num_rows($mysql_result)==1) $this->MemberProfession=mysql_result($mysql_result,0,&#39;ProfessionName&#39;);
else $this->MemberProfession=&#39;No&#39;;
$mysql_result=$MySQL->SendQuery(&#39;SELECT `LocationAddress` FROM `locations` WHERE `LocationID`=?&#39;,$Data[&#39;MemberLastLocation&#39;]);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
if (mysql_num_rows($mysql_result)==1) $this->MemberLastLocation=mysql_result($mysql_result,0,&#39;LocationAddress&#39;);
else $this->MemberLastLocation=&#39;/&#39;;
$this->MemberHealth=$Data[&#39;MemberHealth&#39;];
$this->MemberActiveMoney=$Data[&#39;MemberActiveMoney&#39;];
$this->MemberEnergy=$Data[&#39;MemberEnergy&#39;];
$this->MemberEuroMoney=$Data[&#39;MemberEuroMoney&#39;];
$this->MemberAuthorized=true;
if ($LogIn) Log::RegisterAuthorization($this->MemberID);
}else $this->MemberAuthorized=false;
}

function SetProperty($Name,$Value) {
global $MySQL$ENW;
$mysql_result=$MySQL->SendQuery(&#39;UPDATE `members` SET `&#39;.$Name.&#39;`=? WHERE `MemberID`=?&#39;,$Value,$this->MemberID);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
}

function GoLocation($Location) {
header(&#39;location: &#39;.$Location);
exit;
}
}
?>



Код: (php)
$Member=new GameMember();
if ($Member->MemberAuthorized) {
if ($Member->MemberLastLocation!='/') $Member->GoLocation($Member->MemberLastLocation);
if ($GameGringotts->ChangeEuroToMM()==0) $ChangeEuroToMM=true;
else $ChangeEuroToMM=false;
$Smarty->assign('ChangeEuroToMM',$ChangeEuroToMM);
}else {
$Member->GoLocation('/');
}

Код: (php)
$Smarty->register_modifier('CMU',array($GameGringotts,'CalculationMoneyUnits'));
Код: (php)
Cash: {$Member->MemberActiveMoney|CMU:'%Gg %Ss %Kk'}
//Smarty

Код: (php)
<?php
class 
GameGringotts {
function CalculationMoneyUnits($Knuts,$Format) {
$Sickles=floor($Knuts/29);
$Knuts=$Knuts-($Sickles*29);
$Galleons=floor($Sickles/17);
$Sickles=$Sickles-($Galleons*17);
$Result=str_replace(&#39;%G&#39;,$Galleons,$Format);
$Result=str_replace(&#39;%S&#39;,$Sickles,$Result);
$Result=str_replace(&#39;%K&#39;,$Knuts,$Result);
return $Result;
}
// MM - MagicMoneys
function CalculationExchangeEuroToMM() {
global $MySQL$ENW;
$mysql_result=$MySQL->SendQuery(&#39;SELECT COUNT(`GringottsBuyingID`) AS `CountMagicBuyings` FROM `gringotts-buyings` WHERE `GringottsBuyingFront`="m"&#39;);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
$CountMagicBuyings=mysql_result($mysql_result,0,&#39;CountMagicBuyings&#39;);
$mysql_result=$MySQL->SendQuery(&#39;SELECT COUNT(`GringottsBuyingID`) AS `CountPeopleBuyings` FROM `gringotts-buyings` WHERE `GringottsBuyingFront`="p"&#39;);
if (strlen(mysql_error())>0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
$CountPeopleBuyings=mysql_result($mysql_result,0,&#39;CountPeopleBuyings&#39;);
if ($CountPeopleBuyings==0$CountPeopleBuyings=1;
if ($CountMagicBuyings==0$CountMagicBuyings=1;
$KnutEuro=($CountMagicBuyings/$CountPeopleBuyings)*0.01;
$mysql_result=$MySQL->SendQuery(&#39;INSERT INTO `gringotts-exchanges` SET `GringottsExchangeDate`=NOW(), `GringottsExchange`=?&#39;,$KnutEuro);
return $KnutEuro;
}
function ChangeEuroToMM() {
global $MySQL$ENW$Member$GameGringotts;
if ($Member->MemberAuthorized && isset($_POST[&#39;GringottsExchangeOfficeEuro&#39;]) && is_numeric($_POST[&#39;GringottsExchangeOfficeEuro&#39;])) {
if ($_POST[&#39;GringottsExchangeOfficeEuro&#39;]>=0.01) {
$Knuts=$_POST[&#39;GringottsExchangeOfficeEuro&#39;]/($GameGringotts->CalculationExchangeEuroToMM());
$mysql_result=$MySQL->SendQuery(&#39;UPDATE `members` SET `MemberActiveMoney`=`MemberActiveMoney`+?, `MemberEuroMoney`=`MemberEuroMoney`-? WHERE `MemberID`=?&#39;,$Knuts,$_POST[&#39;GringottsExchangeOfficeEuro&#39;]+($_POST[&#39;GringottsExchangeOfficeEuro&#39;]*0.01),$Member->MemberID);
if (mysql_errno()==1264) {
return -1;
}elseif (mysql_errno()!=0$ENW->WriteErrorE(&#39;Error SQL - query&#39;,mysql_error());
else {
$Member->MemberActiveMoney+=$Knuts;
$Member->MemberEuroMoney-=$_POST[&#39;GringottsExchangeOfficeEuro&#39;]+($_POST[&#39;GringottsExchangeOfficeEuro&#39;]*0.01);
return 0;
}
}else return -1;
}else return -1;
}
}
?>



Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 02, 2007, 08:18:03
Какие данные реально попадают в поле класса?
в этой строке:
Код: (php)
$this->MemberActiveMoney=$Data['MemberActiveMoney'];
Я ведь не знаю, что у тебя там в базе. А телепатов тут нету, как я уже говорил ;)

ЗЫ: А ты сам ответ знаешь?


Название: Re: Траблы в PHP
Отправлено: naxellar от Июль 02, 2007, 08:23:54
Какие данные реально попадают в поле класса?
в этой строке:
Код: (php)
$this->MemberActiveMoney=$Data['MemberActiveMoney'];
Я ведь не знаю, что у тебя там в базе. А телепатов тут нету, как я уже говорил ;)

ЗЫ: А ты сам ответ знаешь?
Точно не знаю. Факт тот, что в нужный момент вызывается
Код: (php)
$Member->MemberActiveMoney+=$Knuts;

где он и становится равным 493


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 02, 2007, 08:26:31
Тогда тема не в этот раздел. Нужно перенести в Программирование.
см. Правила раздела "Викторины".
Цитировать
п. 1.3. Автор должен знать правильный ответ, иначе тема по смыслу из викторины превращается в простой раздел форума.
А вообще я тебе же сказал: 99%, что это потеря точности при делении.


Название: Re: Траблы в PHP
Отправлено: naxellar от Июль 02, 2007, 08:34:31
п. 1.3. Автор должен знать правильный ответ, иначе тема по смыслу из викторины превращается в простой раздел форума.
Ладна, перенесите его туда (веб-программирование) админы плиз с именем Траблы в PHP.


А вообще я тебе же сказал: 99%, что это потеря точности при делении.
Она была бы все время одна и та же. А так при обновлении она всегда потом правильно пашет.

Вобщем, я торжественно клянусь, что расскажу, в чем пряталсо сей хитроумный баг.
LazarusLong, спасибо, что отозвался. +1 тебе. ;)


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 02, 2007, 08:40:20
И еще это может быть связано с представлением в ПХП вещественных чисел, которое, как ивзестно, платформозависимо.

Кстати, говоря - попробуй все-таки вот это:
Цитата: http://www.php.net
Если вам действительно необходима высокая точность, вам следует использовать математические функции произвольной точности или gmp-функции.


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 02, 2007, 11:09:55
Кстати говоря, если тебе нужно округлять, то c помощью floor это делать не корректно - floor(4.549) == 4.54, хотя по правилам математики должно быть 4.55. floor округляет вниз, а ceil - вверх. Юзай round.


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 03, 2007, 10:01:35
Суть не в том. Важно то, что floor округляет вниз, а не по математичким правилам округления.


Название: Re: Траблы в PHP
Отправлено: naxellar от Июль 03, 2007, 10:05:48
Суть не в том. Важно то, что floor округляет вниз, а не по математичким правилам округления.
Эта функция не округления, а отрезания дробной части!!


Название: Re: Траблы в PHP
Отправлено: LazarusLong от Июль 03, 2007, 10:20:05
Нет floor это округление вниз.

floor(-4.1) = -5. Потому как -5 меньше чем -4. Для отрицательных в таком случае нужно использовать ceil


Название: Re: Траблы в PHP
Отправлено: naxellar от Июль 03, 2007, 11:09:15
Функция найдена: gmp_div();


Название: Re: Траблы в PHP
Отправлено: naxellar от Июль 03, 2007, 12:19:46
Несите меня в дурдом:

Код: (php)
var_dump($Knuts);
settype($Knuts,'string');
var_dump($Knuts);
settype($Knuts,'integer');
var_dump($Knuts);

Так оно нормально выдает:

float(493) string(3) "493" int(493)