code-distortion / currency
具有地区感知格式化的任意精度货币数学 - 与 Laravel 集成或独立使用
Requires
- php: 7.1.* | 7.2.* | 7.3.* | 7.4.* | 8.0.* | 8.1.* | 8.2.* | 8.3.*
- ext-intl: *
- code-distortion/options: ^0.5.8
- code-distortion/realnum: ^0.7.6
Requires (Dev)
- jchook/phpunit-assert-throws: ^1.0
- orchestra/testbench: ^3.2 | ^4.0 | ^5.0 | ^6.0 | ^7.0 | ^8.0 | ^9.0
- phpstan/phpstan: ^0.9 | ^0.10 | ^0.11 | ^0.12 | ^1.0
- phpunit/phpunit: ~4.8 | ^5.0 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0
- squizlabs/php_codesniffer: ^3.10
README
code-distortion/currency 是一个用于精确货币数学的 PHP 库,具有地区感知的格式化功能。它与 Laravel 5 - 9 集成,但也可以独立使用。
以下是一个示例,说明您可能需要任意精度计算的原因
// an example of floating-point inaccuracy var_dump(0.1 + 0.2 == 0.3); // bool(false) // for more details see: // The Floating-Point Guide - https://floating-point-gui.de/
如果您想使用常规的 浮点数 或 百分比 值,请考虑使用 code-distortion/realnum 包。
安装
使用 composer 安装此包
composer require code-distortion/currency
用法
实例化一个货币对象,您可以使用它开始进行计算,进行比较,并将其渲染为可读字符串
use CodeDistortion\Currency\Currency; $cur1 = new Currency(5555.55, 'USD'); // normal instantiation $cur1 = Currency::new(5555.55, 'USD'); // static instantiation which is more readable when chaining $cur2 = $cur1->add(4444.44); // (it's immutable so a new object is created) $cur2->between(8000, 10000); // true print $cur2->format(); // "$9,999.99"
默认货币代码
默认货币代码最初未设置,但您可以选择一个。如果您使用 Laravel,它可能已在 /config/code-distortion.currency.php 配置文件中设置。有关更多详细信息,请参阅下面的 Laravel 部分。否则您可能
Currency::new(5, 'USD'); // ok - $5 USD Currency::new(5); // InvalidCurrencyException: "Currency-code was not specified. Please pass one or specify a default" var_dump(Currency::getDefaultCurCode()); // null Currency::setDefaultCurCode('JPY'); print Currency::getDefaultCurCode(); // 'JPY' Currency::new(5, 'USD'); // ok - $5 USD Currency::new(5); // ok - $5 JPY
注意: 以下所有示例都使用默认货币代码为 USD。
设置值
您可以显式设置值
$cur1 = Currency::new(5); // the amount is set to $5.00 upon instantiation $cur2 = $cur1->val(10); // and is then set to $10.00 (it's immutable so a new object is created)
您可以传递给 Currency 的值类型包括
$cur1 = Currency::new(5); // an integer $cur2 = Currency::new(5.5); // a float $cur3 = Currency::new('6.78'); // a numeric string $cur4 = Currency::new($cur3); // another Currency object $cur5 = Currency::new(null); // null $cur6 = Currency::new(); // (will default to null)
提示: 为了在传递值时保持精度,请将它们作为字符串而不是浮点数传递
$cur = Currency::new()->customDecPl(20)->val(0.12345678901234567890); // "0.12345678901235" (precision lost because the number passed is a PHP float) $cur = Currency::new()->customDecPl(20)->val('0.12345678901234567890'); // "0.12345678901234567890" (passed as a string)
您还可以设置 Currency 使用的其他设置
$cur = Currency::new(); $cur = $cur->locale('en-US'); // sets the locale this object uses (see the 'locale' section below) $cur = $cur->curCode('NZD'); // change the currency used $cur = $cur->customDecPl(30); // sets the number of decimal places used (see the 'precision (custom decimal places)' section below) $cur = $cur->useCurrencyDecPl(); // uses the current currency's decimal places again $cur = $cur->immutable(false); // sets whether this object is immutable or not (see the 'immutability' section below) $cur = $cur->formatSettings('!thousands'); // alters the default options used when format() is called (see the 'formatting output' section below)
检索值
要检索包含在 Currency 对象中的值,您可以读取 val
和 cast
属性。val
属性保持精度,而 cast
会在某些情况下丢失精度,因此根据您的需求使用它们
$cur = Currency::new()->customDecPl(20)->val('0.12345678901234567890'); print $cur->val; // "0.12345678901234567890" (returned as a string, or null) print $cur->cast; // 0.12345678901235 (cast to either an integer, float or null - this is less accurate)
以下属性与货币相关联并可读取
$cur = Currency::new(1); print $cur->curCode; // USD (the current currency code) print $cur->symbol; // $ (the currency symbol in the current locale) print $cur->decPl; // 2 (the number of decimal places in the current currency)
您还可以读取 Currency 使用的其他设置
$cur = Currency::new(); print $cur->customDecPl; // null (see the 'precision (custom decimal places)' section below) print $cur->usingCustomDecPl; // false (see the 'precision (custom decimal places)' section below) print $cur->locale; // "en" print $cur->immutable; // true
您还可以获取每种货币的符号
print Currency::symbol('USD'); // "$" (will pick-up the current default locale 'en') print Currency::symbol('USD', 'en-US'); // "$" (for a specific locale) print Currency::symbol('USD', 'en-IN'); // "US$" (same currency, but a different symbol) print Currency::symbol('USD', 'en-AU'); // "USD" print Currency::symbol('JPY', 'en-US'); // "¥" print Currency::symbol('JPY', 'ja-JP'); // "¥"
注意: 请参阅下面的 格式化输出 部分,了解更多关于如何将值渲染为可读字符串的详细信息。
计算
可用的计算包括
$cur = Currency::new(5); $cur = $cur->inc(); // increment $cur = $cur->dec(); // decrement $cur = $cur->add(2); // add x $cur = $cur->sub(2); // subtract x $cur = $cur->div(2); // divide by x $cur = $cur->mul(2); // multiply by x $cur = $cur->round(); // round to zero decimal places $cur = $cur->round(2); // round to x decimal places $cur = $cur->floor(); // use the floor of the current value $cur = $cur->ceil(); // use the ceiling of the current value
add()
、sub()
、div()
和 mul()
方法接受多个值
Currency::new(5)->add(4, 3, 2, 1); // $15.00 Currency::new(5)->sub(4, 3, 2, 1); // -$5.00 Currency::new(5)->customDecPl(15)->div(4, 3, 2, 1); // $0.208333333333333 Currency::new(5)->mul(4, 3, 2, 1); // $120.00
整数、浮点数、数值字符串以及 Currency 对象等值,以及 null 值均可传递
$cur1 = Currency::new(5); $cur1 = $cur1->add(2); // pass an integer $cur1 = $cur1->add(2.0); // pass a float $cur1 = $cur1->add('2.34'); // pass a numeric string $cur1 = $cur1->add(null); // pass null (adds nothing) $cur2 = Currency::new(2); $cur1 = $cur1->add($cur2); // pass another Currency object
比较
您可以使用边界检查将金额与其他值进行比较
Currency::new(5)->lessThan(10); // alias of lt(..) Currency::new(5)->lessThanOrEqualTo(10); // alias of lte(..) Currency::new(5)->equalTo(10); // alias of eq(..) Currency::new(5)->greaterThanOrEqualTo(10); // alias of gte(..) Currency::new(5)->greaterThan(10); // alias of gt(..) $cur1 = Currency::new(5); $cur2 = Currency::new(10); $cur1->lt($cur2); // you can compare a Currency with others
您可以将多个值传递给这些比较方法。例如。
Currency::new(5)->lt(10, 15, 20); // will return true if 5 is less-than 10, 15 and 20
您可以使用给定的范围检查货币的值
Currency::new(5)->between(2, 8); // check if 5 is between x and y (inclusively) Currency::new(5)->between(2, 8, false); // check if 5 is between x and y (NOT inclusively)
您还可以检查值是否为 null
Currency::new(5)->isNull();
格式化输出
使用 format()
方法生成当前值的可读字符串版本
$cur = Currency::new(1234567.89); print $cur->format(); // "$1,234,567.89"
您可以通过传递选项来修改 format()
输出的方式。您可以更改的选项包括
null=x
、decPl=x
、trailZeros
、symbol
、thousands
、showPlus
、accountingNeg
、locale=x
和 breaking
。
布尔选项(没有等号的选项)可以通过在前面添加 !
来取消。
注意: format()
选项使用 code-distortion/options 包进行处理,因此它们可以以表达式字符串或关联数组的形式传递。
print Currency::new(null)->format('null=null'); // null (actual null - default) print Currency::new(null)->format('null="null"'); // "null" (returned as a string) print Currency::new(null)->format('null=0'); // "$0.00" print Currency::new(1)->format('!trailZeros'); // "$1" (hides the decimal places when zero) print Currency::new(1)->format('trailZeros'); // "$1.00" (includes the decimal places - default) // the amount can be rounded and shown to a specific number of decimal places (this is different to the internal customDecPl setting) print Currency::new()->customDecPl(20)->val(1.9876)->format('decPl=null'); // "$1.9876" (no rounding - default) print Currency::new()->customDecPl(20)->val(1.9876)->format('decPl=0'); // "$2" (rounded and shown to 0 decimal places) print Currency::new()->customDecPl(20)->val(1.9876)->format('decPl=1'); // "$2.0" (rounded and shown to 1 decimal place) print Currency::new()->customDecPl(20)->val(1.9876)->format('decPl=2'); // "$1.99" (rounded and shown to 2 decimal places) print Currency::new()->customDecPl(20)->val(1.9876)->format('decPl=6'); // "$1.987600" (rounded and shown to 6 decimal places) // the extra trailing zeros can be removed again with !trailZeros - but only if the decimal part is zero print Currency::new()->customDecPl(20)->val(1.9876)->format('decPl=6 !trailZeros'); // "$1.987600" (still shown to 6 decimal places) print Currency::new()->customDecPl(20)->val(1)->format('decPl=6 !trailZeros'); // "$1" (the trailing zeros removed) print Currency::new(123.45)->format('symbol'); // "$123.45" (default) print Currency::new(123.45)->format('!symbol'); // "123.45" (removes the currency symbol) print Currency::new(1234567.89)->format('thousands'); // "$1,234,567.89" (default) print Currency::new(1234567.89)->format('!thousands'); // "$1234567.89" (removes the thousands separator) print Currency::new(123.45)->format('showPlus'); // "+$123.45" (adds a '+' for positive values) print Currency::new(123.45)->format('!showPlus'); // "$123.45" (default) print Currency::new(-123.45)->format('accountingNeg'); // "($123.45)" (accounting negative - uses brackets for negative numbers) print Currency::new(-123.45)->format('!accountingNeg'); // "-$123.45" (default) // the locale can be chosen at the time of formatting - see the 'local' section below for more details print Currency::new(1234567.89)->format('locale=en'); // "$1,234,567.89" (English - default) print Currency::new(1234567.89)->format('locale=en-AU'); // "USD 1,234,567.89" (Australian English) print Currency::new(1234567.89)->format('locale=en-IN'); // "US$ 12,34,567.89" (Indian English) print Currency::new(1234567.89)->format('locale=de'); // "1.234.567,89 $" (German) print Currency::new(1234567.89)->format('locale=sv'); // "1 234 567,89 US$" (Swedish) print Currency::new(1234567.89)->format('locale=ar'); // "١٬٢٣٤٬٥٦٧٫٨٩ US$"" (Arabic) // non-breaking spaces can be returned instead of regular spaces - see the 'non-breaking whitespace' section below for more details print htmlentities(Currency::new(1234567.89)->format('locale=sv-SE !breaking')); // "1 234 567,89 US$" (default) print htmlentities(Currency::new(1234567.89)->format('locale=sv-SE breaking')); // "1 234 567,89 US$" (regular spaces) // the current currency symbol print Currency::new()->symbol; // "$"
可以同时使用多个设置
print Currency::new(1234567.89)->format('!thousands showPlus locale=de-DE'); // "+1234567,89 $"
将货币转换为字符串相当于调用没有参数的 format()
print (string) Currency::new(1234567.89); // "$1,234,567.89"
注意:货币使用PHP的NumberFormatter来渲染可读的输出,目前有一个限制,即只能显示大约17位数字(包括小数点前的数字)。因此,如果数字太多,format()
的输出可能会有些奇怪。但是,存储在内部的数字将保持其全部精度。您可以通过读取上面的val
属性来访问完整的数字(参见检索值部分)。
默认格式设置
当调用format()
时,货币使用以下默认设置:"null=null decPl=null trailZeros symbol thousands !showPlus !accountingNeg locale=en !breaking"
注意:在使用Laravel时,您可以在包配置文件中设置此设置。请参阅下面的Laravel部分。
注意: format()
选项使用 code-distortion/options 包进行处理,因此它们可以以表达式字符串或关联数组的形式传递。
可以按对象调整
$cur = Currency::new(1234567.89)->formatSettings('!thousands showPlus'); print $cur->format(); // "+$1234567.89" (no thousands separator, show-plus)
默认格式设置可以调整。所有新的Currency对象将以此设置开始。
var_dump(Currency::getDefaultFormatSettings()); // ['null' => null, 'decPl' => null … ] (default) Currency::setDefaultFormatSettings('null="NULL" decPl=5'); var_dump(Currency::getDefaultFormatSettings()); // ['null' => 'NULL', 'decPl' => 5 … ]
地区
注意:在使用Laravel时,这将自动设置。请参阅下面的Laravel部分。
Currency的默认地区是"en"(英语),但您可以选择使用哪个。
您可以按对象更改地区。
$cur1 = Currency::new(1234567.89); print $cur1->locale; // "en" (the default) print $cur1->format(); // "$1,234,567.89" $cur2 = $cur1->locale('fr-FR'); // (it's immutable so a new object is created) print $cur2->locale; // "fr-FR" print $cur2->format(); // "1 234 567,89 $US"
默认地区可以更改。所有新的Currency对象将以此设置开始。
Currency::setDefaultLocale('fr-FR'); print Currency::getDefaultLocale(); // "fr-FR"
精度(自定义小数位数)
当前货币的小数位数默认使用(例如,美元为2位小数)。但是,您可以指定要使用的数字以获得更高的精度。如果您希望执行一些计算然后在最后四舍五入到最近的美分,这可能很有用。
您可以使用customDecPl()
按对象更改此设置。
// without customDecPl $cur = Currency::new('0.98765'); // this has more decimal places than USD has print $cur->val; // "0.99" (ie. rounded to the default 2 decimal places) print $cur->decPl; // 2 print $cur->customDecPl; // null print $cur->usingCustomDecPl; // false // with customDecPl $cur = Currency::new()->customDecPl(30)->val('0.123456789012345678901234567890'); print $cur->val; // "0.123456789012345678901234567890" (the full 30 decimal places) print $cur->decPl; // 30 print $cur->customDecPl; // 30 print $cur->usingCustomDecPl; // true
您可以使用currencyDecPl()
恢复到正常的小数位数。
$cur = Currency::new()->customDecPl(30); print $cur->decPl; // 30 $cur = $cur->useCurrencyDecPl(); // goes back to USD's 2 decimal places - the amount inside will be rounded automatically print $cur->decPl; // 2
要找出货币有多少小数位数
print Currency::currencyDecPl('USD'); // 2 print Currency::currencyDecPl('JPY'); // 0
不可变性
注意:在使用Laravel时,您可以在包配置文件中设置此设置。请参阅下面的Laravel部分。
货币默认是不可变的,这意味着一旦创建对象,它就不会改变。任何更改其内容的东西都将返回一个新的Currency。这样,您可以向代码的其它部分传递Currency对象,并确信它不会意外改变。
$cur1 = Currency::new(1); $cur2 = $cur1->add(2); // $cur1 remains unchanged and $cur2 is a new object containing the new value print $cur1->format(); // "$1.00" print $cur2->format(); // "$3.00"
可以按对象关闭不可变性。
$cur1 = Currency::new(1)->immutable(false); $cur2 = $cur1->add(2); // $cur1 is changed and $cur2 points to the same object print $cur1->format(); // "$3.00" print $cur2->format(); // "$3.00"
可以默认关闭不可变性。所有新的Currency对象将以此设置开始。
Currency::setDefaultImmutability(false); var_dump(Currency::getDefaultImmutability()); // "bool(false)"
您可以显式地创建Currency对象的副本。
$cur1 = Currency::new(); $cur2 = $cur1->copy(); // this will return a clone regardless of the immutability setting
非断行空白
一些地区在渲染数字时使用空格(例如,瑞典使用空格作为千位分隔符)。format()
可以返回包含非断行空白字符或常规空格字符的字符串。
非断行空白的例子是UTF-8的\xc2\xa0
字符,它用于代替常规的\x20
空格字符。还有其他的,如\xe2\x80\xaf
,它是一个'窄非断行空格'。
\xc2\xa0
UTF-8字符将变成熟悉的
,当转换为html实体时。
由于format()
是为生成供人类阅读的数字而设计的,Currency默认使用非断行空白,但您可以指示它返回常规空格。
$cur = Currency::new(1234567.89)->locale('sv-SE'); // Swedish print htmlentities($cur->format('!breaking')); // "1 234 567,89 US$" (contains non-breaking whitespace - default) print htmlentities($cur->format('breaking')); // "1 234 567,89 US$" (regular spaces)
提示:非断行空白的设置可以按对象和默认设置更改。请参阅上面的格式化输出和默认格式设置部分。
链式调用
上面的设置和计算方法可以一起链式调用。例如。
print Currency::new(1) ->locale('en-US')->val(5)->customDecPl(3) // some "setting" methods ->add(4)->mul(3)->div(2)->sub(1) // some "calculation" methods ->format(); // "$12.50"
Laravel
Currency包是框架无关的,可以独立工作,但它也集成了Laravel 5、6、7、8 & 9。
服务提供者
由于Laravel的包自动检测,Currency可以从Laravel 5.5+自动集成。
Laravel的地区已注册到Currency中,并在更改时更新。
(点击此处查看Laravel <= 5.4)
对于Laravel 5.0 - 5.4,请将以下行添加到config/app.php
'providers' => [ … CodeDistortion\Currency\Laravel\ServiceProvider::class, … ],
配置
您可以通过发布并更新 config/code-distortion.currency.php 配置文件来指定默认不可变性和格式设置。
php artisan vendor:publish --provider="CodeDistortion\Currency\Laravel\ServiceProvider" --tag="config"
测试
composer test
变更日志
有关最近更改的更多信息,请参阅 变更日志。
语义版本
此库使用 SemVer 2.0.0 版本控制。这意味着对 X
的更改表示有破坏性的变化:0.0.X
、0.X.y
、X.y.z
。当此库版本升级到 1.0.0、2.0.0 等时,并不一定表示这是一个显著的发布,它仅仅表示发生了破坏性的更改。
树资源
此软件包是 树资源。如果在生产环境中使用,我们请求您为世界买一棵树以感谢我们的工作。通过向树资源森林贡献力量,您将为当地家庭创造就业机会并恢复野生动物栖息地。
贡献
有关详细信息,请参阅 贡献指南。
行为准则
有关详细信息,请参阅 行为准则。
安全
如果您发现任何与安全相关的问题,请通过电子邮件 tim@code-distortion.net 反馈,而不是使用问题跟踪器。
致谢
许可证
MIT 许可证 (MIT)。有关更多信息,请参阅 许可证文件。