私たちのお金に関連するすべての値は、データベースにセントとして格納されています(ODMではORMは同じように動作します)。 MoneyTypeを使用して、ユーザー向きの値(12,34€)をセントの表現(1234c)に変換しています。 typical float precision問題が発生します。精度が不十分なため、デバッグ時に表示されるだけの丸め誤差を生成するケースが多いです。 MoneyTypeは、受信した文字列をfloatに変換しますが、正確ではない場合があります( "1765" => 1764.9999999998)。symfony2除数を持つMoneyType:整数変換によりデータベースの値が間違っています
class Price {
/**
* @var int
* @MongoDB\Field(type="int")
**/
protected $cents;
}
が入力値変換されますように(フロートです!)::
namespace Doctrine\ODM\MongoDB\Types;
class IntType extends Type
{
public function convertToDatabaseValue($value)
{
return $value !== null ? (integer) $value : null;
}
}
(整数)キャストはあなたがこれらの値を持続して
物事はすぐに悪い取得します値を四捨五入するのではなく、値の仮数を取り除いて、誤った値をデータベースに書き込むことになります( "1765"が内部で1764.9999999998のときは1765ではなく1764)。この問題は、常に表示されていないことを、
//for better debugging: set ini_set('precision', 17);
class PrecisionTest extends WebTestCase
{
private function buildForm() {
$builder = $this->getContainer()->get('form.factory')->createBuilder(FormType::class, null, []);
$form = $builder->add('money', MoneyType::class, [
'divisor' => 100
])->getForm();
return $form;
}
// high-level symptom
public function testMoneyType() {
$form = $this->buildForm();
$form->submit(['money' => '12,34']);
$data = $form->getData();
$this->assertEquals(1234, $data['money']);
$this->assertEquals(1234, (int)$data['money']);
$form = $this->buildForm();
$form->submit(['money' => '17,65']);
$data = $form->getData();
$this->assertEquals(1765, $data['money']);
$this->assertEquals(1765, (int)$data['money']); //fails: data[money] === 1764
}
//root cause
public function testParsedIntegerPrecision() {
$string = "17,65";
$transformer = new MoneyToLocalizedStringTransformer(2, false,null, 100);
$value = $transformer->reverseTransform($string);
$int = (integer) $value;
$float = (float) $value;
$this->assertEquals(1765, (float)$float);
$this->assertEquals(1765, $int); //fails: $int === 1764
}
}
注:
は、ここで任意のSymfony2のコンテナ内から問題が表示されるはずですユニットテストです! 「12,34」がうまくいっているのを見ると、「17,65」または「18,65」は失敗します。
ここで(Symfony Forms/Doctrineに関して)回避する最も良い方法は何ですか? NumberTransformerまたはMoneyTypeは整数値を返すものではありません。浮動小数点を保存して問題を解決できない場合もあります。私は永続化層のIntTypeをオーバーライドすることを考えました。キャストの代わりにすべての着信整数値を丸めます。もう一つのアプローチは、MongoDBにfloatとしてフィールドを格納することです。
基本的なPHPの問題is discussed here。
浮動小数点数値を 'NUMERIC(precision、scale)'として保存する 'DecimalType'は、元の精度に影響を与えずに文字列として復帰します。 – yceruto
ええ、私はそう思った。しかし、私は明らかに "浮動小数点値"(1765.00)を "int"として格納するのがとても奇妙です。 – Stefan
100除数( "17.65")なしで伝えたい – yceruto