byrokrat/amount

此包已被放弃,不再维护。作者建议使用moneyphp/money包代替。

货币金额的值对象

2.1.0 2019-08-20 11:11 UTC

This package is auto-updated.

Last update: 2019-12-15 13:22:13 UTC


README

已废弃!此包已停止维护,将不再更新。请查看moneyphp/money

Amount

Packagist Version Build Status Quality Score Scrutinizer Coverage

货币金额的值对象。

功能

  • 不可变值对象。
  • 使用bcmath扩展进行任意浮点精度算术。
  • 货币支持以防止货币混合。
  • 开箱即用的ISO 4217货币支持。
  • 简单接口用于定义新货币
  • 支持多种舍入策略。
  • 支持根据比例分配金额。
  • 支持瑞典直接借记系统中使用的信号字符串格式。

安装

composer require byrokrat/amount

使用方法

use byrokrat\amount\Amount;

$amount = new Amount('100.6');

// outputs 1 (true)
echo $amount->isGreaterThan(new Amount('50'));

// round to 0 decimal digits
$roundedAmount = $amount->roundTo(0);

// outputs 101.00
echo $roundedAmount;

API

Amount定义了以下API

方法签名 返回值 描述
__construct(string $amount) Amount 创建新实例
getAmount() string 获取原始金额
roundTo([int $precision, [Rounder $rounder]]) Amount 获取舍入到$precision的Amount
getString([int $precision, [Rounder $rounder]]) string 获取金额字符串
__tostring() string 获取金额字符串
getInt([Rounder $rounder]) integer 获取整数金额(警告)
getFloat([int $precision, [Rounder $rounder]]) float 获取浮点金额(警告)
getSignalString([Rounder $rounder]) string 获取信号字符串
add(Amount $amount) Amount 获取增加$amount后的新Amount
subtract(Amount $amount) Amount 获取减去$amount后的新Amount
multiplyWith(mixed $amount) Amount 获取乘以$amount后的新Amount
divideBy(mixed $amount) Amount 获取除以$amount后的新Amount
compareTo(Amount $amount) integer 等于返回0,大于返回1,否则返回-1
equals(Amount $amount) boolean 检查是否等于金额
isLessThan(Amount $amount) boolean 检查是否小于金额
isLessThanOrEquals(Amount $amount) boolean 检查是否小于或等于金额
isGreaterThan(Amount $amount) boolean 检查是否大于金额
isGreaterThanOrEquals(Amount $amount) boolean 检查是否大于或等于金额
isZero() boolean 检查金额是否为零
isPositive() boolean 检查金额是否大于零
isNegative() boolean 检查金额是否小于零
getInverted() Amount 获取符号反转后的新金额
getAbsolute() Amount 获取移除负号的新金额
allocate(array $ratios, [int $precision]) Amount[] 根据比率列表分配金额

从其他格式创建金额

浮点数

Amount 包含两个用于处理浮点数的便捷方法。 createFromNumber 可以从浮点数创建一个 Amount 对象,getFloat 可以将 Amount 对象转换为浮点数。应谨慎使用这些方法。

需要注意的是,计算机内部使用二进制浮点数格式,无法完全准确地表示像 0.1、0.2 或 0.3 这样的数字。使用浮点数会导致精度损失。例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期的 8,因为内部表示可能是类似于 7.9999999999999991118....

因此,浮点数绝不应用于存储货币数据。这些方法存在于不可避免地需要转换为或从原生格式的情况。除非你了解自己在做什么,否则它们不应被使用。

有关更多信息,请参阅 php 手册程序员应了解的关于浮点运算的每件事

格式化数字

您可以使用静态方法 createFromFormat 从格式化为非标准或依赖于区域的十进制点和分组字符的字符串创建 Amount。

use byrokrat\amount\Amount;

$formattedAmount = "2 000:50";

$amount = Amount::createFromFormat($formattedAmount, ":", " ");

echo $amount;  // outputs 2000.50

信号字符串

信号字符串格式不包含小数点,并且负金额使用字母而不是最后一位数字来表示。使用静态方法 createFromSignalString 从信号字符串创建 Amount。

处理货币

货币子系统有助于防止将不同货币的值混合(例如相加)的错误。货币对象是 Amount 的子类,并以相同的方式工作,增加了它们知道自己的货币定义的功能。

use byrokrat\amount\Currency\SEK;
use byrokrat\amount\Currency\EUR;

$sek = new SEK('100');

// throws an exception
$sek->add(new EUR('1'));
use byrokrat\amount\Currency\SEK;

$sek = new SEK('100');

// works as intended, outputs 101.00
echo $sek->add(new SEK('1'));

ISO 4217

byrokrat\amount\Currency 命名空间中捆绑了一套全面的 ISO 4217 货币。

请注意,土耳其里拉(ISO 代码 TRY)表示为 _TRY,因为 try 是一个保留关键字。

创建新货币

然而,创建新货币很简单。只需从 Currency 类中派生一个子类,并定义 getCurrencyCode()

另外,您可能需要重写 getDisplayPrecision()getInternalPrecision()getDefaultRounder() 以进一步定义您的货币行为。

兑换

使用 createFromExchange 支持货币兑换。请注意,您必须提供正确的汇率。

use byrokrat\amount\Currency\SEK;
use byrokrat\amount\Currency\EUR;

// One euro is exchanged into swedish kronas using the exchange rate 9.27198929
// resulting in the value of SEK 9.27198929
echo $sek = SEK::createFromExchange(new EUR('1'), '9.27198929');

货币格式化

可以使用 PHP 内置的 NumberFormatter 对象轻松地格式化货币。

use byrokrat\amount\Currency\EUR;

// Create some amount of euros
$money = new EUR('1234567.89');

// Create a currency formatter with swedish formatting rules
$formatter = new NumberFormatter('sv_SE', NumberFormatter::CURRENCY);

// Format euros according to swedish standards, outputs 1 234 567:89 €
echo $formatter->formatCurrency($money->getFloat(), $money->getCurrencyCode());

舍入

支持多种舍入策略。要实现自己的舍入策略,请查看 Rounder 接口。

namespace byrokrat\amount;
$amount = new Amount('1.5');

// outputs 2
echo $amount->roundTo(0, new Rounder\RoundUp);

// outputs 1
echo $amount->roundTo(0, new Rounder\RoundDown);

// outputs 1
echo $amount->roundTo(0, new Rounder\RoundTowardsZero);

// outputs 2
echo $amount->roundTo(0, new Rounder\RoundAwayFromZero);

// outputs 2
echo $amount->roundTo(0, new Rounder\RoundHalfUp);

// outputs 1
echo $amount->roundTo(0, new Rounder\RoundHalfDown);

// outputs 1
echo $amount->roundTo(0, new Rounder\RoundHalfTowardsZero);

// outputs 2
echo $amount->roundTo(0, new Rounder\RoundHalfAwayFromZero);

// outputs 2
echo $amount->roundTo(0, new Rounder\RoundHalfToEven);

// outputs 1
echo $amount->roundTo(0, new Rounder\RoundHalfToOdd);

有关舍入策略的更多信息,请参阅 维基百科

分配

分配是将金额按比例划分的过程,这样最小的单位不会被划分(每种货币都有一个不可划分的最小单位),而是传递给下一个接收者。

这些比例可以看作(但不一定必须是)百分比。一百个单位可以分成两个接收者,如下所示

use byrokrat\amount\Amount;
$money = new Amount('100');

list($receiverA, $receiverB) = $money->allocate([30, 70]);

// outputs 30
echo $receiverA;

// outputs 70
echo $receiverB;

当我们分配一个无法均匀划分的值时,分配的优势变得明显。在这种情况下,接收者的顺序很重要。

use byrokrat\amount\Amount;

$money = new Amount('0.05');

list($receiverA, $receiverB) = $money->allocate([30, 70]);

// outputs 0.03
echo $receiverA;

// outputs 0.02
echo $receiverB;

list($receiverA, $receiverB) = $money->allocate([70, 30]);

// outputs 0.04
echo $receiverA;

// outputs 0.01
echo $receiverB;

在这些例子中,使用的不可划分的单位是 0.01。这是默认行为。可以通过指定 $precision 参数或在您的货币类中覆盖 getDisplayPrecision() 来更改它。