Appearance
Decimal128类型
概述
Decimal128(128位十进制数)类型是MongoDB 3.4版本引入的高精度十进制浮点数类型。它遵循IEEE 754-2008标准的Decimal128格式,提供34位有效数字的精度,专门用于需要精确十进制计算的场景,如金融系统、会计软件、科学计算等。
与Double类型不同,Decimal128使用十进制表示,避免了二进制浮点数的精度问题(如0.1 + 0.2 = 0.3在Decimal128中成立)。理解Decimal128类型对于构建精确数值计算系统至关重要。
基本概念
Decimal128类型特性
MongoDB的Decimal128类型具有以下核心特性:
1. IEEE 754-2008标准
- 128位十进制浮点数
- 34位有效数字精度
- 支持精确的十进制运算
2. 数值范围
- 最大值:约±9.999999999999999999999999999999999 × 10^6144
- 最小正数:约±1.0 × 10^-6176
- 存储空间:16字节
3. 精度特点
- 十进制浮点数表示
- 无二进制精度问题
- 适合货币和金融计算
4. 特殊值支持
- 正无穷大(Infinity)
- 负无穷大(-Infinity)
- 静默NaN(Quiet NaN)
- 信号NaN(Signaling NaN)
Decimal128类型语法
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:演示MongoDB Decimal128类型的基本语法和使用方式
// 1. 创建Decimal128对象
$price = new Decimal128('99.99');
$balance = new Decimal128('1000000.50');
$rate = new Decimal128('0.00000123');
// 从整数创建
$amount = new Decimal128('12345');
// 负数
$debt = new Decimal128('-5000.75');
// 科学计数法
$scientific = new Decimal128('1.23E+10');
// 2. 连接MongoDB并插入数据
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->selectDatabase("financial_db");
$collection = $database->selectCollection("accounts");
$document = [
'account_id' => 'ACC001',
'balance' => new Decimal128('10000.50'),
'interest_rate' => new Decimal128('0.035'),
'transaction_limit' => new Decimal128('50000.00'),
'min_balance' => new Decimal128('100.00'),
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($document);
echo "插入成功,文档ID: " . $result->getInsertedId() . "\n";
// 3. 查询Decimal128数据
$account = $collection->findOne(['account_id' => 'ACC001']);
echo "账户余额: " . $account['balance'] . "\n";
echo "利率: " . $account['interest_rate'] . "\n";
// 4. Decimal128比较查询
$highBalance = $collection->find([
'balance' => ['$gte' => new Decimal128('5000.00')]
])->toArray();
echo "高余额账户数量: " . count($highBalance) . "\n";
// 运行结果展示:
// 插入成功,文档ID: 507f1f77bcf86cd799439011
// 账户余额: 10000.50
// 利率: 0.035
// 高余额账户数量: 1
?>常见改法对比:
| 方案 | 代码示例 | 优缺点 |
|---|---|---|
| 使用Double存储金额 | 'price' => 99.99 | ❌ 存在精度问题,不适合金融 |
| 使用字符串存储 | 'price' => '99.99' | ⚠️ 无法直接数值计算 |
| 使用整数存储分 | 'price' => 9999 | ✅ 精确但需要转换 |
| 使用Decimal128 | 'price' => new Decimal128('99.99') | ✅ 最佳方案,精确且支持计算 |
Decimal128与Double对比
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:对比Decimal128和Double在精度计算上的差异
// 1. Double精度问题演示
$doubleResult = 0.1 + 0.2;
echo "Double: 0.1 + 0.2 = " . $doubleResult . "\n";
echo "是否等于0.3: " . ($doubleResult === 0.3 ? 'true' : 'false') . "\n";
// 2. Decimal128精确计算
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->decimal_demo;
$collection->drop();
$collection->insertOne([
'value1' => new Decimal128('0.1'),
'value2' => new Decimal128('0.2'),
'sum' => new Decimal128('0.3')
]);
$pipeline = [
['$project' => [
'value1' => 1,
'value2' => 1,
'sum' => 1,
'calculated_sum' => ['$add' => ['$value1', '$value2']],
'is_equal' => ['$eq' => ['$sum', ['$add' => ['$value1', '$value2']]]]
]]
];
$result = $collection->aggregate($pipeline)->toArray()[0];
echo "Decimal128: 0.1 + 0.2 = " . $result['calculated_sum'] . "\n";
echo "是否等于0.3: " . ($result['is_equal'] ? 'true' : 'false') . "\n";
// 运行结果展示:
// Double: 0.1 + 0.2 = 0.30000000000004
// 是否等于0.3: false
// Decimal128: 0.1 + 0.2 = 0.3
// 是否等于0.3: true
?>原理深度解析
存储机制
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:深入理解Decimal128的存储机制和BSON编码
// 1. BSON存储结构
// Decimal128在BSON中存储为16字节(128位)
// 结构组成:
// - 1位符号位
// - 5位组合位(用于指数和特殊值)
// - 12位指数位
// - 110位有效数字位
// 2. 创建不同精度的Decimal128
$examples = [
'small' => new Decimal128('0.0000000000000000000000000000000001'),
'large' => new Decimal128('9999999999999999999999999999999999'),
'normal' => new Decimal128('123.456'),
'negative' => new Decimal128('-123.456'),
'scientific' => new Decimal128('1.23E+100')
];
foreach ($examples as $name => $decimal) {
echo "$name: " . $decimal . "\n";
}
// 3. 存储空间对比
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->storage_test;
$collection->drop();
$collection->insertMany([
['type' => 'double', 'value' => 123.456],
['type' => 'decimal128', 'value' => new Decimal128('123.456')],
['type' => 'string', 'value' => '123.456'],
['type' => 'int32', 'value' => 123],
['type' => 'int64', 'value' => 1234567890123]
]);
$docs = $collection->find()->toArray();
foreach ($docs as $doc) {
$bson = MongoDB\BSON\fromPHP($doc);
echo $doc['type'] . " BSON大小: " . strlen($bson) . " 字节\n";
}
// 运行结果展示:
// small: 1E-34
// large: 9.999999999999999999999999999999999E+33
// normal: 123.456
// negative: -123.456
// scientific: 1.23E+100
// double BSON大小: 35 字节
// decimal128 BSON大小: 43 字节
// string BSON大小: 35 字节
// int32 BSON大小: 30 字节
// int64 BSON大小: 34 字节
?>精度与舍入
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:理解Decimal128的精度特性和舍入规则
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->precision_test;
$collection->drop();
// 1. 有效数字测试(最多34位)
$precisionTests = [
'max_precision' => new Decimal128('1.234567890123456789012345678901234'),
'overflow' => new Decimal128('1.234567890123456789012345678901234567'),
'integer_max' => new Decimal128('9999999999999999999999999999999999')
];
$collection->insertOne($precisionTests);
$doc = $collection->findOne();
echo "最大精度测试:\n";
echo " 原始: 1.234567890123456789012345678901234\n";
echo " 存储: " . $doc['max_precision'] . "\n";
echo " 超出: " . $doc['overflow'] . "\n";
echo " 整数: " . $doc['integer_max'] . "\n\n";
// 2. 聚合运算中的舍入
$collection->drop();
$collection->insertMany([
['amount' => new Decimal128('10.555')],
['amount' => new Decimal128('20.444')],
['amount' => new Decimal128('30.001')]
]);
$pipeline = [
['$project' => [
'amount' => 1,
'round_2' => ['$round' => ['$amount', 2]],
'round_0' => ['$round' => ['$amount', 0]],
'floor' => ['$floor' => '$amount'],
'ceil' => ['$ceil' => '$amount'],
'trunc' => ['$trunc' => ['$amount', 2]]
]]
];
echo "舍入运算结果:\n";
foreach ($collection->aggregate($pipeline) as $doc) {
echo "原始: " . $doc['amount'] . "\n";
echo " round(2): " . $doc['round_2'] . "\n";
echo " round(0): " . $doc['round_0'] . "\n";
echo " floor: " . $doc['floor'] . "\n";
echo " ceil: " . $doc['ceil'] . "\n";
echo " trunc(2): " . $doc['trunc'] . "\n\n";
}
// 运行结果展示:
// 最大精度测试:
// 原始: 1.234567890123456789012345678901234
// 存储: 1.234567890123456789012345678901234
// 超出: 1.2345678901234567890123456789012346
// 整数: 9.999999999999999999999999999999999E+33
//
// 舍入运算结果:
// 原始: 10.555
// round(2): 10.56
// round(0): 11
// floor: 10
// ceil: 11
// trunc(2): 10.55
?>类型转换机制
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:理解Decimal128与其他数值类型的转换机制
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->conversion_test;
$collection->drop();
// 1. PHP类型到Decimal128转换
echo "PHP类型转换:\n";
$fromInt = new Decimal128((string)12345);
echo "从整数: " . $fromInt . "\n";
$floatVal = 123.45;
$fromFloat = new Decimal128((string)$floatVal);
echo "从浮点数: " . $fromFloat . " (可能有精度问题)\n";
$fromString = new Decimal128('123.4567890123456789012345678901234');
echo "从字符串: " . $fromString . "\n";
// 2. MongoDB聚合中的类型转换
$collection->insertMany([
['value' => 123.456, 'type' => 'double'],
['value' => '789.012', 'type' => 'string'],
['value' => 100, 'type' => 'int']
]);
$pipeline = [
['$project' => [
'value' => 1,
'type' => 1,
'as_decimal' => ['$toDecimal' => '$value']
]]
];
echo "\n聚合类型转换:\n";
foreach ($collection->aggregate($pipeline) as $doc) {
echo "类型: " . $doc['type'] . "\n";
echo " 原始值: " . json_encode($doc['value']) . "\n";
echo " Decimal: " . $doc['as_decimal'] . "\n\n";
}
// 3. Decimal128到其他类型转换
$collection->drop();
$collection->insertOne([
'decimal_val' => new Decimal128('123.456')
]);
$convertPipeline = [
['$project' => [
'decimal_val' => 1,
'as_double' => ['$toDouble' => '$decimal_val'],
'as_string' => ['$toString' => '$decimal_val'],
'as_int' => ['$toInt' => '$decimal_val'],
'as_long' => ['$toLong' => '$decimal_val']
]]
];
echo "Decimal128转换输出:\n";
$result = $collection->aggregate($convertPipeline)->toArray()[0];
echo " Decimal: " . $result['decimal_val'] . "\n";
echo " Double: " . $result['as_double'] . "\n";
echo " String: " . $result['as_string'] . "\n";
echo " Int: " . $result['as_int'] . "\n";
echo " Long: " . $result['as_long'] . "\n";
// 运行结果展示:
// PHP类型转换:
// 从整数: 12345
// 从浮点数: 123.45 (可能有精度问题)
// 从字符串: 123.4567890123456789012345678901234
//
// 聚合类型转换:
// 类型: double
// 原始值: 123.456
// Decimal: 123.456
//
// Decimal128转换输出:
// Decimal: 123.456
// Double: 123.456
// String: 123.456
// Int: 123
// Long: 123
?>常见错误与踩坑点
错误1:从浮点数直接创建Decimal128
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:错误地从PHP浮点数创建Decimal128导致精度问题
// ❌ 错误做法:直接从浮点数创建
$wrongDecimal = new Decimal128((string)0.1);
echo "错误方式: " . $wrongDecimal . "\n";
// 输出: 0.1000000000000000055511151231257827021181583404541015625
// ✅ 正确做法:从字符串创建
$correctDecimal = new Decimal128('0.1');
echo "正确方式: " . $correctDecimal . "\n";
// 输出: 0.1
// 问题根源分析
$floatVal = 0.1;
echo "PHP浮点数内部表示: ";
printf("%.60f\n", $floatVal);
// 最佳实践:始终使用字符串
$price = new Decimal128('99.99');
$rate = new Decimal128('0.035');
$amount = new Decimal128('1000000.50');
// 运行结果展示:
// 错误方式: 0.1000000000000000055511151231257827021181583404541015625
// 正确方式: 0.1
// PHP浮点数内部表示: 0.100000000000000005551115123125782702118158340454101562500000
?>错误2:超出精度范围
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:超出Decimal128精度范围的处理
// ❌ 错误做法:超出34位有效数字
try {
$tooManyDigits = new Decimal128('1.12345678901234567890123456789012345');
echo "超出精度: " . $tooManyDigits . "\n";
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}
// ✅ 正确做法:了解精度限制
$maxPrecision = new Decimal128('1.234567890123456789012345678901234');
echo "最大精度(34位): " . $maxPrecision . "\n";
// 运行结果展示:
// 超出精度: 1.1234567890123456789012345678901235
// 最大精度(34位): 1.234567890123456789012345678901234
?>错误3:混合类型比较问题
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:Decimal128与其他数值类型比较时的陷阱
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->compare_test;
$collection->drop();
$collection->insertMany([
['value' => new Decimal128('100.50'), 'name' => 'decimal'],
['value' => 100.50, 'name' => 'double'],
['value' => 100, 'name' => 'int']
]);
// ❌ 错误做法:期望精确匹配所有类型
$wrongQuery = $collection->find(['value' => 100.50])->toArray();
echo "使用Double查询: 找到 " . count($wrongQuery) . " 条记录\n";
// ✅ 正确做法:使用$expr进行类型转换比较
$correctQuery = $collection->find([
'$expr' => ['$eq' => ['$value', 100.50]]
])->toArray();
echo "使用$expr查询: 找到 " . count($correctQuery) . " 条记录\n";
// ✅ 更好的做法:统一类型后比较
$decimalQuery = $collection->find([
'$expr' => ['$eq' => [
['$toDecimal' => '$value'],
new Decimal128('100.50')
]]
])->toArray();
echo "统一类型查询: 找到 " . count($decimalQuery) . " 条记录\n";
// 运行结果展示:
// 使用Double查询: 找到 1 条记录
// 使用$expr查询: 找到 3 条记录
// 统一类型查询: 找到 3 条记录
?>错误4:聚合运算中的类型丢失
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:聚合运算中Decimal128精度保持问题
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->aggregate_test;
$collection->drop();
$collection->insertMany([
['amount' => new Decimal128('100.123456789012345678901234567890123')],
['amount' => new Decimal128('200.234567890123456789012345678901234')],
['amount' => new Decimal128('300.345678901234567890123456789012345')]
]);
// ✅ 正确做法:验证精度保持
$exactPipeline = [
['$group' => [
'_id' => null,
'total' => ['$sum' => '$amount'],
'count' => ['$sum' => 1],
'min' => ['$min' => '$amount'],
'max' => ['$max' => '$amount']
]],
['$project' => [
'total' => 1,
'count' => 1,
'min' => 1,
'max' => 1,
'calculated_avg' => ['$divide' => ['$total', '$count']]
]]
];
$exact = $collection->aggregate($exactPipeline)->toArray()[0];
echo "精确计算:\n";
echo " 总和: " . $exact['total'] . "\n";
echo " 最小值: " . $exact['min'] . "\n";
echo " 最大值: " . $exact['max'] . "\n";
echo " 计算平均值: " . $exact['calculated_avg'] . "\n";
// 运行结果展示:
// 精确计算:
// 总和: 600.70370358037037037037037037037037
// 最小值: 100.123456789012345678901234567890123
// 最大值: 300.345678901234567890123456789012345
// 计算平均值: 200.234567860123456789012345678901257
?>错误5:无效字符串格式
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:创建Decimal128时使用无效字符串格式
$invalidFormats = [
'empty' => '',
'letters' => 'abc',
'mixed' => '12.34abc',
'multiple_dots' => '12.34.56',
'spaces' => ' 123.45 ',
];
foreach ($invalidFormats as $name => $value) {
try {
$decimal = new Decimal128($value);
echo "$name: 创建成功 - " . $decimal . "\n";
} catch (InvalidArgumentException $e) {
echo "$name: 创建失败 - " . $e->getMessage() . "\n";
}
}
// ✅ 正确做法:验证和清理输入
function createSafeDecimal(string $value): Decimal128 {
$trimmed = trim($value);
if (!is_numeric($trimmed)) {
throw new InvalidArgumentException("无效的数值格式: $value");
}
return new Decimal128($trimmed);
}
echo "\n安全创建:\n";
$safeValues = [
'normal' => '123.45',
'negative' => '-123.45',
'scientific' => '1.23E+10',
'with_spaces' => ' 123.45 ',
];
foreach ($safeValues as $name => $value) {
try {
$decimal = createSafeDecimal($value);
echo "$name: " . $decimal . "\n";
} catch (Exception $e) {
echo "$name: 错误 - " . $e->getMessage() . "\n";
}
}
// 运行结果展示:
// empty: 创建失败 - ...
// letters: 创建失败 - ...
// mixed: 创建失败 - ...
// multiple_dots: 创建失败 - ...
// spaces: 创建失败 - ...
//
// 安全创建:
// normal: 123.45
// negative: -123.45
// scientific: 1.23E+10
// with_spaces: 123.45
?>错误6:索引和排序问题
php
<?php
use MongoDB\BSON\Decimal128;
// 场景说明:Decimal128字段的索引和排序注意事项
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->index_test;
$collection->drop();
$collection->insertMany([
['price' => new Decimal128('99.99'), 'name' => 'A'],
['price' => new Decimal128('100.00'), 'name' => 'B'],
['price' => new Decimal128('99.999'), 'name' => 'C'],
['price' => 99.99, 'name' => 'D'],
['price' => new Decimal128('100.01'), 'name' => 'E']
]);
$collection->createIndex(['price' => 1]);
echo "索引创建成功\n";
// ✅ 正确做法:统一类型后排序
$collection->drop();
$collection->insertMany([
['price' => new Decimal128('99.99'), 'name' => 'A'],
['price' => new Decimal128('100.00'), 'name' => 'B'],
['price' => new Decimal128('99.999'), 'name' => 'C'],
['price' => new Decimal128('100.01'), 'name' => 'E']
]);
$collection->createIndex(['price' => 1]);
$correctSorted = $collection->find([], [
'sort' => ['price' => 1]
])->toArray();
echo "\n统一类型后排序:\n";
foreach ($correctSorted as $doc) {
echo " " . $doc['name'] . ": " . $doc['price'] . "\n";
}
// 运行结果展示:
// 索引创建成功
//
// 统一类型后排序:
// A: 99.99
// C: 99.999
// B: 100.00
// E: 100.01
?>常见应用场景
场景1:金融交易系统
php
<?php
use MongoDB\BSON\Decimal128;
class FinancialTransactionSystem
{
private MongoDB\Collection $accounts;
private MongoDB\Collection $transactions;
public function __construct(MongoDB\Client $client)
{
$this->accounts = $client->financial->accounts;
$this->transactions = $client->financial->transactions;
}
public function createAccount(string $accountId, string $initialBalance): void
{
$this->accounts->insertOne([
'account_id' => $accountId,
'balance' => new Decimal128($initialBalance),
'currency' => 'CNY',
'status' => 'active',
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
public function transfer(
string $fromAccount,
string $toAccount,
string $amount,
string $description = ''
): string {
$decimalAmount = new Decimal128($amount);
$from = $this->accounts->findOne(['account_id' => $fromAccount]);
if (!$from) {
throw new Exception("转出账户不存在");
}
$compareResult = $this->accounts->aggregate([
['$match' => ['account_id' => $fromAccount]],
['$project' => [
'balance' => 1,
'can_transfer' => ['$gte' => ['$balance', $decimalAmount]]
]]
])->toArray()[0];
if (!$compareResult['can_transfer']) {
throw new Exception("余额不足");
}
$transactionId = new MongoDB\BSON\ObjectId();
$this->transactions->insertOne([
'_id' => $transactionId,
'type' => 'transfer',
'from_account' => $fromAccount,
'to_account' => $toAccount,
'amount' => $decimalAmount,
'description' => $description,
'status' => 'completed',
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
$this->accounts->updateOne(
['account_id' => $fromAccount],
['$inc' => ['balance' => new Decimal128('-' . $amount)]]
);
$this->accounts->updateOne(
['account_id' => $toAccount],
['$inc' => ['balance' => $decimalAmount]]
);
return (string)$transactionId;
}
public function calculateInterest(string $accountId, string $rate): array
{
$account = $this->accounts->findOne(['account_id' => $accountId]);
if (!$account) {
throw new Exception("账户不存在");
}
$pipeline = [
['$match' => ['account_id' => $accountId]],
['$project' => [
'balance' => 1,
'rate' => ['$literal' => new Decimal128($rate)],
'interest' => ['$multiply' => ['$balance', ['$literal' => new Decimal128($rate)]]]
]]
];
$result = $this->accounts->aggregate($pipeline)->toArray()[0];
return [
'balance' => (string)$result['balance'],
'rate' => $rate,
'interest' => (string)$result['interest']
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$fts = new FinancialTransactionSystem($client);
$fts->createAccount('ACC001', '10000.00');
$fts->createAccount('ACC002', '5000.00');
$transactionId = $fts->transfer('ACC001', 'ACC002', '1000.50', '测试转账');
echo "交易ID: $transactionId\n";
$interest = $fts->calculateInterest('ACC001', '0.035');
echo "利息计算: " . json_encode($interest) . "\n";
// 运行结果展示:
// 交易ID: 507f1f77bcf86cd799439011
// 利息计算: {"balance":"8999.50","rate":"0.035","interest":"314.9825"}
?>场景2:电商价格管理
php
<?php
use MongoDB\BSON\Decimal128;
class EcommercePriceManager
{
private MongoDB\Collection $products;
private MongoDB\Collection $priceHistory;
public function __construct(MongoDB\Client $client)
{
$this->products = $client->ecommerce->products;
$this->priceHistory = $client->ecommerce->price_history;
}
public function createProduct(array $data): string
{
$productId = (string)new MongoDB\BSON\ObjectId();
$product = [
'_id' => $productId,
'name' => $data['name'],
'sku' => $data['sku'],
'base_price' => new Decimal128($data['base_price']),
'cost_price' => new Decimal128($data['cost_price']),
'discount_rate' => new Decimal128($data['discount_rate'] ?? '0'),
'tax_rate' => new Decimal128($data['tax_rate'] ?? '0.13'),
'currency' => $data['currency'] ?? 'CNY',
'status' => 'active',
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$this->products->insertOne($product);
return $productId;
}
public function calculateFinalPrice(string $productId, int $quantity = 1): array
{
$pipeline = [
['$match' => ['_id' => $productId]],
['$project' => [
'name' => 1,
'base_price' => 1,
'discount_rate' => 1,
'tax_rate' => 1,
'discount_amount' => ['$multiply' => ['$base_price', '$discount_rate']],
'price_after_discount' => [
'$multiply' => ['$base_price', ['$subtract' => [1, '$discount_rate']]]
]
]],
['$project' => [
'name' => 1,
'base_price' => 1,
'discount_rate' => 1,
'tax_rate' => 1,
'discount_amount' => 1,
'price_after_discount' => 1,
'tax_amount' => ['$multiply' => ['$price_after_discount', '$tax_rate']],
'final_price' => [
'$multiply' => ['$price_after_discount', ['$add' => [1, '$tax_rate']]]
]
]]
];
$result = $this->products->aggregate($pipeline)->toArray();
if (empty($result)) {
throw new Exception("产品不存在");
}
$price = $result[0];
return [
'product_name' => $price['name'],
'base_price' => (string)$price['base_price'],
'discount_rate' => (string)$price['discount_rate'],
'discount_amount' => (string)$price['discount_amount'],
'tax_rate' => (string)$price['tax_rate'],
'tax_amount' => (string)$price['tax_amount'],
'unit_price' => (string)$price['final_price'],
'quantity' => $quantity,
'total_price' => bcmul((string)$price['final_price'], (string)$quantity, 2)
];
}
public function calculateProfitMargin(string $productId): array
{
$pipeline = [
['$match' => ['_id' => $productId]],
['$project' => [
'name' => 1,
'base_price' => 1,
'cost_price' => 1,
'profit' => ['$subtract' => ['$base_price', '$cost_price']],
'profit_margin' => [
'$cond' => [
['$eq' => ['$base_price', 0]],
0,
['$divide' => [
['$subtract' => ['$base_price', '$cost_price']],
'$base_price'
]]
]
]
]]
];
$result = $this->products->aggregate($pipeline)->toArray()[0];
return [
'product_name' => $result['name'],
'selling_price' => (string)$result['base_price'],
'cost_price' => (string)$result['cost_price'],
'profit' => (string)$result['profit'],
'profit_margin' => (string)$result['profit_margin']
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$pm = new EcommercePriceManager($client);
$productId = $pm->createProduct([
'name' => '高端笔记本电脑',
'sku' => 'LAPTOP-001',
'base_price' => '8999.00',
'cost_price' => '6500.00',
'discount_rate' => '0.10',
'tax_rate' => '0.13'
]);
echo "产品ID: $productId\n";
$finalPrice = $pm->calculateFinalPrice($productId, 2);
echo "价格计算: " . json_encode($finalPrice, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
$profit = $pm->calculateProfitMargin($productId);
echo "利润分析: " . json_encode($profit, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 产品ID: 507f1f77bcf86cd799439011
// 价格计算: {
// "product_name": "高端笔记本电脑",
// "base_price": "8999.00",
// "discount_rate": "0.10",
// "discount_amount": "899.90",
// "tax_rate": "0.13",
// "tax_amount": "1052.877",
// "unit_price": "9152.877",
// "quantity": 2,
// "total_price": "18305.75"
// }
?>场景3:税务计算系统
php
<?php
use MongoDB\BSON\Decimal128;
class TaxCalculationSystem
{
private MongoDB\Collection $taxRates;
private MongoDB\Collection $taxRecords;
public function __construct(MongoDB\Client $client)
{
$this->taxRates = $client->tax->rates;
$this->taxRecords = $client->tax->records;
}
public function setupTaxRates(): void
{
$this->taxRates->insertMany([
[
'type' => 'VAT',
'name' => '增值税',
'rate' => new Decimal128('0.13'),
'category' => 'standard'
],
[
'type' => 'VAT',
'name' => '增值税(简易征收)',
'rate' => new Decimal128('0.03'),
'category' => 'simplified'
],
[
'type' => 'SURTAX',
'name' => '城建税',
'rate' => new Decimal128('0.07'),
'base' => 'VAT',
'category' => 'urban'
],
[
'type' => 'SURTAX',
'name' => '教育费附加',
'rate' => new Decimal128('0.03'),
'base' => 'VAT',
'category' => 'education'
}
]);
}
public function calculateVAT(string $amount, string $taxCategory = 'standard'): array
{
$rate = $this->taxRates->findOne([
'type' => 'VAT',
'category' => $taxCategory
]);
if (!$rate) {
throw new Exception("税率不存在");
}
$pipeline = [
['$match' => ['type' => 'VAT', 'category' => $taxCategory]],
['$project' => [
'rate' => 1,
'name' => 1,
'amount' => ['$literal' => new Decimal128($amount)],
'tax_amount' => ['$multiply' => [['$literal' => new Decimal128($amount)], '$rate']],
'amount_with_tax' => ['$multiply' => [['$literal' => new Decimal128($amount)], ['$add' => [1, '$rate']]]]
]]
];
$result = $this->taxRates->aggregate($pipeline)->toArray()[0];
return [
'tax_name' => $result['name'],
'rate' => (string)$result['rate'],
'amount' => $amount,
'tax_amount' => (string)$result['tax_amount'],
'amount_with_tax' => (string)$result['amount_with_tax']
];
}
public function calculateSurtax(string $vatAmount): array
{
$surtaxes = $this->taxRates->find(['type' => 'SURTAX'])->toArray();
$results = [];
$totalSurtax = '0';
foreach ($surtaxes as $surtax) {
$surtaxAmount = bcmul($vatAmount, (string)$surtax['rate'], 34);
$results[] = [
'name' => $surtax['name'],
'rate' => (string)$surtax['rate'],
'amount' => $surtaxAmount
];
$totalSurtax = bcadd($totalSurtax, $surtaxAmount, 34);
}
return [
'surtaxes' => $results,
'total_surtax' => $totalSurtax
];
}
public function calculateTotalTax(string $amount, string $taxCategory = 'standard'): array
{
$vat = $this->calculateVAT($amount, $taxCategory);
$surtax = $this->calculateSurtax($vat['tax_amount']);
$totalTax = bcadd($vat['tax_amount'], $surtax['total_surtax'], 34);
$recordId = $this->taxRecords->insertOne([
'amount' => new Decimal128($amount),
'tax_category' => $taxCategory,
'vat' => [
'rate' => new Decimal128($vat['rate']),
'amount' => new Decimal128($vat['tax_amount'])
],
'surtax' => [
'total' => new Decimal128($surtax['total_surtax']),
'details' => $surtax['surtaxes']
],
'total_tax' => new Decimal128($totalTax),
'calculated_at' => new MongoDB\BSON\UTCDateTime()
])->getInsertedId();
return [
'record_id' => (string)$recordId,
'base_amount' => $amount,
'vat' => $vat,
'surtax' => $surtax,
'total_tax' => $totalTax
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$tcs = new TaxCalculationSystem($client);
$tcs->setupTaxRates();
$vat = $tcs->calculateVAT('10000.00', 'standard');
echo "增值税计算: " . json_encode($vat, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
$totalTax = $tcs->calculateTotalTax('10000.00', 'standard');
echo "总税额: " . json_encode($totalTax, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 增值税计算: {
// "tax_name": "增值税",
// "rate": "0.13",
// "amount": "10000.00",
// "tax_amount": "1300.00",
// "amount_with_tax": "11300.00"
// }
// 总税额: {
// "record_id": "507f1f77bcf86cd799439011",
// "base_amount": "10000.00",
// "total_tax": "1456.00"
// }
?>场景4:货币兑换系统
php
<?php
use MongoDB\BSON\Decimal128;
class CurrencyExchangeSystem
{
private MongoDB\Collection $rates;
private MongoDB\Collection $transactions;
public function __construct(MongoDB\Client $client)
{
$this->rates = $client->currency->rates;
$this->transactions = $client->currency->transactions;
}
public function updateRate(string $fromCurrency, string $toCurrency, string $rate): void
{
$this->rates->updateOne(
['from_currency' => $fromCurrency, 'to_currency' => $toCurrency],
[
'$set' => [
'rate' => new Decimal128($rate),
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
],
['upsert' => true]
);
$inverseRate = bcdiv('1', $rate, 34);
$this->rates->updateOne(
['from_currency' => $toCurrency, 'to_currency' => $fromCurrency],
[
'$set' => [
'rate' => new Decimal128($inverseRate),
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
],
['upsert' => true]
);
}
public function exchange(
string $fromCurrency,
string $toCurrency,
string $amount,
string $feeRate = '0.01'
): array {
$rateDoc = $this->rates->findOne([
'from_currency' => $fromCurrency,
'to_currency' => $toCurrency
]);
if (!$rateDoc) {
throw new Exception("汇率不存在");
}
$grossAmount = bcmul($amount, (string)$rateDoc['rate'], 34);
$feeAmount = bcmul($grossAmount, $feeRate, 34);
$netAmount = bcsub($grossAmount, $feeAmount, 34);
$transactionId = $this->transactions->insertOne([
'from_currency' => $fromCurrency,
'to_currency' => $toCurrency,
'amount' => new Decimal128($amount),
'rate' => $rateDoc['rate'],
'gross_amount' => new Decimal128($grossAmount),
'fee_rate' => new Decimal128($feeRate),
'fee_amount' => new Decimal128($feeAmount),
'net_amount' => new Decimal128($netAmount),
'created_at' => new MongoDB\BSON\UTCDateTime()
])->getInsertedId();
return [
'transaction_id' => (string)$transactionId,
'from_currency' => $fromCurrency,
'to_currency' => $toCurrency,
'amount' => $amount,
'rate' => (string)$rateDoc['rate'],
'gross_amount' => $grossAmount,
'fee_rate' => $feeRate,
'fee_amount' => $feeAmount,
'net_amount' => $netAmount
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$ces = new CurrencyExchangeSystem($client);
$ces->updateRate('USD', 'CNY', '7.2500');
$ces->updateRate('EUR', 'CNY', '7.8500');
$result = $ces->exchange('USD', 'CNY', '1000.00', '0.005');
echo "兑换结果: " . json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 兑换结果: {
// "transaction_id": "507f1f77bcf86cd799439011",
// "from_currency": "USD",
// "to_currency": "CNY",
// "amount": "1000.00",
// "rate": "7.2500",
// "gross_amount": "7250.00",
// "fee_rate": "0.005",
// "fee_amount": "36.25",
// "net_amount": "7213.75"
// }
?>场景5:预算管理系统
php
<?php
use MongoDB\BSON\Decimal128;
class BudgetManagementSystem
{
private MongoDB\Collection $budgets;
private MongoDB\Collection $budgetItems;
private MongoDB\Collection $actuals;
public function __construct(MongoDB\Client $client)
{
$this->budgets = $client->budget->budgets;
$this->budgetItems = $client->budget->budget_items;
$this->actuals = $client->budget->actuals;
}
public function createBudget(array $data): string
{
$budgetId = (string)new MongoDB\BSON\ObjectId();
$budget = [
'_id' => $budgetId,
'name' => $data['name'],
'department' => $data['department'],
'fiscal_year' => $data['fiscal_year'],
'total_amount' => new Decimal128($data['total_amount']),
'used_amount' => new Decimal128('0'),
'remaining_amount' => new Decimal128($data['total_amount']),
'status' => 'active',
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$this->budgets->insertOne($budget);
foreach ($data['items'] as $item) {
$this->budgetItems->insertOne([
'budget_id' => $budgetId,
'name' => $item['name'],
'category' => $item['category'],
'allocated_amount' => new Decimal128($item['allocated_amount']),
'used_amount' => new Decimal128('0'),
'remaining_amount' => new Decimal128($item['allocated_amount']),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
return $budgetId;
}
public function recordExpense(array $data): string
{
$expenseId = (string)new MongoDB\BSON\ObjectId();
$budgetItem = $this->budgetItems->findOne([
'budget_id' => $data['budget_id'],
'category' => $data['category']
]);
if (!$budgetItem) {
throw new Exception("预算项目不存在");
}
$amount = new Decimal128($data['amount']);
$checkPipeline = [
['$match' => ['_id' => $budgetItem['_id']]],
['$project' => [
'remaining_amount' => 1,
'sufficient' => ['$gte' => ['$remaining_amount', $amount]]
]]
];
$check = $this->budgetItems->aggregate($checkPipeline)->toArray()[0];
if (!$check['sufficient']) {
throw new Exception("预算不足");
}
$this->actuals->insertOne([
'_id' => $expenseId,
'budget_id' => $data['budget_id'],
'budget_item_id' => $budgetItem['_id'],
'category' => $data['category'],
'amount' => $amount,
'description' => $data['description'],
'expense_date' => new MongoDB\BSON\UTCDateTime(strtotime($data['expense_date']) * 1000),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
$this->budgetItems->updateOne(
['_id' => $budgetItem['_id']],
[
'$inc' => [
'used_amount' => $amount,
'remaining_amount' => new Decimal128('-' . $data['amount'])
]
]
);
$this->budgets->updateOne(
['_id' => $data['budget_id']],
[
'$inc' => [
'used_amount' => $amount,
'remaining_amount' => new Decimal128('-' . $data['amount'])
]
]
);
return $expenseId;
}
public function getBudgetStatus(string $budgetId): array
{
$budget = $this->budgets->findOne(['_id' => $budgetId]);
if (!$budget) {
throw new Exception("预算不存在");
}
$pipeline = [
['$match' => ['budget_id' => $budgetId]],
['$project' => [
'name' => 1,
'category' => 1,
'allocated_amount' => 1,
'used_amount' => 1,
'remaining_amount' => 1,
'usage_percentage' => [
'$cond' => [
['$eq' => ['$allocated_amount', 0]],
0,
['$multiply' => [['$divide' => ['$used_amount', '$allocated_amount']], 100]]
]
]
]],
['$sort' => ['usage_percentage' => -1]]
];
$itemStatus = [];
foreach ($this->budgetItems->aggregate($pipeline) as $item) {
$itemStatus[] = [
'name' => $item['name'],
'category' => $item['category'],
'allocated' => (string)$item['allocated_amount'],
'used' => (string)$item['used_amount'],
'remaining' => (string)$item['remaining_amount'],
'usage_percentage' => round((float)(string)$item['usage_percentage'], 2)
];
}
$totalUsagePipeline = [
['$match' => ['_id' => $budgetId]],
['$project' => [
'total_amount' => 1,
'used_amount' => 1,
'remaining_amount' => 1,
'usage_percentage' => ['$multiply' => [['$divide' => ['$used_amount', '$total_amount']], 100]]
]]
];
$totalUsage = $this->budgets->aggregate($totalUsagePipeline)->toArray()[0];
return [
'budget_id' => $budgetId,
'name' => $budget['name'],
'department' => $budget['department'],
'fiscal_year' => $budget['fiscal_year'],
'total_amount' => (string)$budget['total_amount'],
'used_amount' => (string)$budget['used_amount'],
'remaining_amount' => (string)$budget['remaining_amount'],
'usage_percentage' => round((float)(string)$totalUsage['usage_percentage'], 2),
'status' => $budget['status'],
'items' => $itemStatus
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$bms = new BudgetManagementSystem($client);
$budgetId = $bms->createBudget([
'name' => '2024年度研发预算',
'department' => '研发部',
'fiscal_year' => '2024',
'total_amount' => '1000000.00',
'items' => [
['name' => '设备采购', 'category' => 'equipment', 'allocated_amount' => '500000.00'],
['name' => '人员成本', 'category' => 'personnel', 'allocated_amount' => '300000.00'],
['name' => '研发材料', 'category' => 'materials', 'allocated_amount' => '200000.00']
]
]);
echo "预算ID: $budgetId\n";
$expenseId = $bms->recordExpense([
'budget_id' => $budgetId,
'category' => 'equipment',
'amount' => '50000.00',
'description' => '服务器采购',
'expense_date' => '2024-01-15'
]);
echo "支出ID: $expenseId\n";
$status = $bms->getBudgetStatus($budgetId);
echo "预算状态: " . json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 预算ID: 507f1f77bcf86cd799439011
// 支出ID: 507f1f77bcf86cd799439012
// 预算状态: {
// "budget_id": "507f1f77bcf86cd799439011",
// "name": "2024年度研发预算",
// "total_amount": "1000000.00",
// "used_amount": "50000.00",
// "remaining_amount": "950000.00",
// "usage_percentage": 5.0,
// "items": [...]
// }
?>企业级进阶应用场景
场景1:分布式事务处理
php
<?php
use MongoDB\BSON\Decimal128;
class DistributedTransactionManager
{
private MongoDB\Client $client;
private MongoDB\Collection $accounts;
private MongoDB\Collection $transactions;
public function __construct(MongoDB\Client $client)
{
$this->client = $client;
$this->accounts = $client->banking->accounts;
$this->transactions = $client->banking->transactions;
}
public function transferWithTransaction(
string $fromAccount,
string $toAccount,
string $amount
): array {
$decimalAmount = new Decimal128($amount);
$session = $this->client->startSession();
$session->startTransaction([
'readConcern' => new MongoDB\Driver\ReadConcern('snapshot'),
'writeConcern' => new MongoDB\Driver\WriteConcern(MongoDB\Driver\WriteConcern::MAJORITY)
]);
try {
$from = $this->accounts->findOne(
['account_id' => $fromAccount],
['session' => $session]
);
if (!$from) {
throw new Exception("转出账户不存在");
}
$comparePipeline = [
['$match' => ['account_id' => $fromAccount]],
['$project' => [
'balance' => 1,
'sufficient' => ['$gte' => ['$balance', $decimalAmount]]
]]
];
$compare = $this->accounts->aggregate(
$comparePipeline,
['session' => $session]
)->toArray()[0];
if (!$compare['sufficient']) {
throw new Exception("余额不足");
}
$this->accounts->updateOne(
['account_id' => $fromAccount],
['$inc' => ['balance' => new Decimal128('-' . $amount)]],
['session' => $session]
);
$this->accounts->updateOne(
['account_id' => $toAccount],
['$inc' => ['balance' => $decimalAmount]],
['session' => $session]
);
$transactionId = $this->transactions->insertOne([
'type' => 'transfer',
'from_account' => $fromAccount,
'to_account' => $toAccount,
'amount' => $decimalAmount,
'status' => 'completed',
'created_at' => new MongoDB\BSON\UTCDateTime()
], ['session' => $session])->getInsertedId();
$session->commitTransaction();
return [
'transaction_id' => (string)$transactionId,
'status' => 'completed',
'amount' => $amount
];
} catch (Exception $e) {
$session->abortTransaction();
throw $e;
} finally {
$session->endSession();
}
}
public function reconcileTransactions(string $date): array
{
$startOfDay = new MongoDB\BSON\UTCDateTime(strtotime($date . ' 00:00:00') * 1000);
$endOfDay = new MongoDB\BSON\UTCDateTime(strtotime($date . ' 23:59:59') * 1000);
$pipeline = [
['$match' => [
'created_at' => ['$gte' => $startOfDay, '$lte' => $endOfDay],
'status' => 'completed'
]],
['$group' => [
'_id' => null,
'total_amount' => ['$sum' => '$amount'],
'transaction_count' => ['$sum' => 1],
'min_amount' => ['$min' => '$amount'],
'max_amount' => ['$max' => '$amount']
]]
];
$summary = $this->transactions->aggregate($pipeline)->toArray();
$balanceCheck = $this->accounts->aggregate([
['$group' => [
'_id' => null,
'total_balance' => ['$sum' => '$balance']
]]
])->toArray();
return [
'date' => $date,
'transactions' => $summary[0] ?? ['total_amount' => '0', 'transaction_count' => 0],
'balance_summary' => $balanceCheck[0] ?? ['total_balance' => '0']
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$dtm = new DistributedTransactionManager($client);
try {
$result = $dtm->transferWithTransaction('ACC001', 'ACC002', '5000.00');
echo "转账成功: " . json_encode($result) . "\n";
} catch (Exception $e) {
echo "转账失败: " . $e->getMessage() . "\n";
}
$reconciliation = $dtm->reconcileTransactions('2024-01-15');
echo "对账结果: " . json_encode($reconciliation, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 转账成功: {"transaction_id":"507f1f77bcf86cd799439011","status":"completed","amount":"5000.00"}
// 对账结果: {
// "date": "2024-01-15",
// "transactions": {
// "total_amount": "5000.00",
// "transaction_count": 1
// },
// "balance_summary": {
// "total_balance": "100000.00"
// }
// }
?>场景2:会计系统
php
<?php
use MongoDB\BSON\Decimal128;
class AccountingSystem
{
private MongoDB\Collection $accounts;
private MongoDB\Collection $journalEntries;
private MongoDB\Collection $ledgers;
public function __construct(MongoDB\Client $client)
{
$this->accounts = $client->accounting->accounts;
$this->journalEntries = $client->accounting->journal_entries;
$this->ledgers = $client->accounting->ledgers;
}
public function createAccount(array $data): string
{
$accountId = 'ACC' . str_pad(
$this->accounts->countDocuments([]) + 1,
6,
'0',
STR_PAD_LEFT
);
$this->accounts->insertOne([
'account_id' => $accountId,
'name' => $data['name'],
'type' => $data['type'],
'category' => $data['category'],
'balance' => new Decimal128('0'),
'currency' => $data['currency'] ?? 'CNY',
'is_active' => true,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
return $accountId;
}
public function createJournalEntry(array $entry): string
{
$entryId = 'JE' . date('Ymd') . str_pad(
$this->journalEntries->countDocuments([
'created_at' => ['$gte' => new MongoDB\BSON\UTCDateTime(strtotime('today') * 1000)]
]) + 1,
4,
'0',
STR_PAD_LEFT
);
$totalDebit = '0';
$totalCredit = '0';
foreach ($entry['lines'] as $line) {
if (isset($line['debit'])) {
$totalDebit = bcadd($totalDebit, $line['debit'], 34);
}
if (isset($line['credit'])) {
$totalCredit = bcadd($totalCredit, $line['credit'], 34);
}
}
if (bccomp($totalDebit, $totalCredit, 34) !== 0) {
throw new Exception("借贷不平衡: 借方 {$totalDebit}, 贷方 {$totalCredit}");
}
$journalEntry = [
'entry_id' => $entryId,
'date' => new MongoDB\BSON\UTCDateTime(strtotime($entry['date'] ?? 'now') * 1000),
'description' => $entry['description'],
'lines' => array_map(function($line) {
return [
'account_id' => $line['account_id'],
'debit' => isset($line['debit']) ? new Decimal128($line['debit']) : new Decimal128('0'),
'credit' => isset($line['credit']) ? new Decimal128($line['credit']) : new Decimal128('0'),
'description' => $line['description'] ?? null
];
}, $entry['lines']),
'total_debit' => new Decimal128($totalDebit),
'total_credit' => new Decimal128($totalCredit),
'status' => 'posted',
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$this->journalEntries->insertOne($journalEntry);
foreach ($entry['lines'] as $line) {
$this->updateAccountBalance(
$line['account_id'],
$line['debit'] ?? '0',
$line['credit'] ?? '0'
);
$this->ledgers->insertOne([
'entry_id' => $entryId,
'account_id' => $line['account_id'],
'date' => $journalEntry['date'],
'debit' => isset($line['debit']) ? new Decimal128($line['debit']) : new Decimal128('0'),
'credit' => isset($line['credit']) ? new Decimal128($line['credit']) : new Decimal128('0'),
'description' => $line['description'] ?? $entry['description'],
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
return $entryId;
}
private function updateAccountBalance(string $accountId, string $debit, string $credit): void
{
$account = $this->accounts->findOne(['account_id' => $accountId]);
if (!$account) {
throw new Exception("账户不存在: $accountId");
}
$isDebitAccount = in_array($account['type'], ['asset', 'expense']);
if ($isDebitAccount) {
$newBalance = bcadd(bcsub((string)$account['balance'], $credit, 34), $debit, 34);
} else {
$newBalance = bcadd(bcsub((string)$account['balance'], $debit, 34), $credit, 34);
}
$this->accounts->updateOne(
['account_id' => $accountId],
[
'$set' => [
'balance' => new Decimal128($newBalance),
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
}
public function getTrialBalance(string $asOfDate): array
{
$pipeline = [
['$match' => ['is_active' => true]],
['$project' => [
'account_id' => 1,
'name' => 1,
'type' => 1,
'balance' => 1,
'debit_balance' => [
'$cond' => [
['$in' => ['$type', ['asset', 'expense']]],
['$max' => ['$balance', 0]],
['$max' => ['$multiply' => ['$balance', -1]], 0]
]
],
'credit_balance' => [
'$cond' => [
['$in' => ['$type', ['liability', 'equity', 'revenue']]],
['$max' => ['$balance', 0]],
['$max' => ['$multiply' => ['$balance', -1]], 0]
]
]
]],
['$group' => [
'_id' => null,
'accounts' => ['$push' => '$$ROOT'],
'total_debit' => ['$sum' => '$debit_balance'],
'total_credit' => ['$sum' => '$credit_balance']
]]
];
$result = $this->accounts->aggregate($pipeline)->toArray();
if (empty($result)) {
return ['accounts' => [], 'total_debit' => '0', 'total_credit' => '0', 'is_balanced' => true];
}
$data = $result[0];
return [
'as_of_date' => $asOfDate,
'accounts' => array_map(function($acc) {
return [
'account_id' => $acc['account_id'],
'name' => $acc['name'],
'type' => $acc['type'],
'debit' => (string)$acc['debit_balance'],
'credit' => (string)$acc['credit_balance']
];
}, $data['accounts']),
'total_debit' => (string)$data['total_debit'],
'total_credit' => (string)$data['total_credit'],
'is_balanced' => bccomp((string)$data['total_debit'], (string)$data['total_credit'], 34) === 0
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$accounting = new AccountingSystem($client);
$cashAccountId = $accounting->createAccount([
'name' => '现金',
'type' => 'asset',
'category' => 'current_asset'
]);
$revenueAccountId = $accounting->createAccount([
'name' => '主营业务收入',
'type' => 'revenue',
'category' => 'operating_revenue'
]);
$entryId = $accounting->createJournalEntry([
'date' => '2024-01-15',
'description' => '销售商品收入',
'lines' => [
['account_id' => $cashAccountId, 'debit' => '10000.00', 'description' => '收到现金'],
['account_id' => $revenueAccountId, 'credit' => '10000.00', 'description' => '确认收入']
]
]);
echo "日记账分录ID: $entryId\n";
$trialBalance = $accounting->getTrialBalance('2024-01-15');
echo "试算平衡: " . json_encode($trialBalance, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 日记账分录ID: JE202401150001
// 试算平衡: {
// "as_of_date": "2024-01-15",
// "accounts": [...],
// "total_debit": "10000.00",
// "total_credit": "10000.00",
// "is_balanced": true
// }
?>场景3:投资组合管理
php
<?php
use MongoDB\BSON\Decimal128;
class InvestmentPortfolioManager
{
private MongoDB\Collection $portfolios;
private MongoDB\Collection $holdings;
private MongoDB\Collection $transactions;
public function __construct(MongoDB\Client $client)
{
$this->portfolios = $client->investment->portfolios;
$this->holdings = $client->investment->holdings;
$this->transactions = $client->investment->transactions;
}
public function createPortfolio(array $data): string
{
$portfolioId = (string)new MongoDB\BSON\ObjectId();
$this->portfolios->insertOne([
'_id' => $portfolioId,
'name' => $data['name'],
'owner_id' => $data['owner_id'],
'base_currency' => $data['base_currency'] ?? 'CNY',
'total_value' => new Decimal128('0'),
'cash_balance' => new Decimal128($data['initial_cash'] ?? '0'),
'cost_basis' => new Decimal128('0'),
'unrealized_gain' => new Decimal128('0'),
'realized_gain' => new Decimal128('0'),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
return $portfolioId;
}
public function buyAsset(
string $portfolioId,
string $symbol,
string $quantity,
string $price,
string $commission = '0'
): string {
$quantityDecimal = new Decimal128($quantity);
$priceDecimal = new Decimal128($price);
$commissionDecimal = new Decimal128($commission);
$totalCost = bcadd(bcmul($quantity, $price, 34), $commission, 34);
$portfolio = $this->portfolios->findOne(['_id' => $portfolioId]);
$checkPipeline = [
['$match' => ['_id' => $portfolioId]],
['$project' => [
'cash_balance' => 1,
'sufficient' => ['$gte' => ['$cash_balance', new Decimal128($totalCost)]]
]]
];
$check = $this->portfolios->aggregate($checkPipeline)->toArray()[0];
if (!$check['sufficient']) {
throw new Exception("现金余额不足");
}
$transactionId = $this->transactions->insertOne([
'portfolio_id' => $portfolioId,
'type' => 'buy',
'symbol' => $symbol,
'quantity' => $quantityDecimal,
'price' => $priceDecimal,
'commission' => $commissionDecimal,
'total_cost' => new Decimal128($totalCost),
'executed_at' => new MongoDB\BSON\UTCDateTime(),
'created_at' => new MongoDB\BSON\UTCDateTime()
])->getInsertedId();
$existingHolding = $this->holdings->findOne([
'portfolio_id' => $portfolioId,
'symbol' => $symbol
]);
if ($existingHolding) {
$newQuantity = bcadd((string)$existingHolding['quantity'], $quantity, 34);
$newCostBasis = bcadd((string)$existingHolding['cost_basis'], $totalCost, 34);
$newAvgCost = bcdiv($newCostBasis, $newQuantity, 34);
$this->holdings->updateOne(
['portfolio_id' => $portfolioId, 'symbol' => $symbol],
[
'$set' => [
'quantity' => new Decimal128($newQuantity),
'cost_basis' => new Decimal128($newCostBasis),
'avg_cost' => new Decimal128($newAvgCost),
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
} else {
$this->holdings->insertOne([
'portfolio_id' => $portfolioId,
'symbol' => $symbol,
'quantity' => $quantityDecimal,
'cost_basis' => new Decimal128($totalCost),
'avg_cost' => $priceDecimal,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
$this->portfolios->updateOne(
['_id' => $portfolioId],
[
'$inc' => [
'cash_balance' => new Decimal128('-' . $totalCost),
'cost_basis' => new Decimal128($totalCost)
]
]
);
return (string)$transactionId;
}
public function updateMarketValues(array $marketPrices): void
{
foreach ($marketPrices as $symbol => $price) {
$this->holdings->updateMany(
['symbol' => $symbol],
['$set' => ['market_price' => new Decimal128($price), 'updated_at' => new MongoDB\BSON\UTCDateTime()]]
);
}
$portfolios = $this->portfolios->find()->toArray();
foreach ($portfolios as $portfolio) {
$pipeline = [
['$match' => ['portfolio_id' => $portfolio['_id']]],
['$project' => [
'quantity' => 1,
'market_price' => 1,
'cost_basis' => 1,
'market_value' => ['$multiply' => ['$quantity', '$market_price']],
'unrealized_gain' => ['$subtract' => [['$multiply' => ['$quantity', '$market_price']], '$cost_basis']]
]],
['$group' => [
'_id' => null,
'total_market_value' => ['$sum' => '$market_value'],
'total_unrealized_gain' => ['$sum' => '$unrealized_gain']
]]
];
$result = $this->holdings->aggregate($pipeline)->toArray();
if (!empty($result)) {
$this->portfolios->updateOne(
['_id' => $portfolio['_id']],
[
'$set' => [
'total_value' => $result[0]['total_market_value'],
'unrealized_gain' => $result[0]['total_unrealized_gain'],
'updated_at' => new MongoDB\BSON\UTCDateTime()
]
]
);
}
}
}
public function getPortfolioSummary(string $portfolioId): array
{
$portfolio = $this->portfolios->findOne(['_id' => $portfolioId]);
if (!$portfolio) {
throw new Exception("投资组合不存在");
}
$holdingsPipeline = [
['$match' => ['portfolio_id' => $portfolioId]],
['$project' => [
'symbol' => 1,
'quantity' => 1,
'cost_basis' => 1,
'avg_cost' => 1,
'market_price' => 1,
'market_value' => ['$multiply' => ['$quantity', '$market_price']],
'unrealized_gain' => ['$subtract' => [['$multiply' => ['$quantity', '$market_price']], '$cost_basis']],
'return_percentage' => [
'$cond' => [
['$eq' => ['$cost_basis', 0],
0,
['$multiply' => [['$divide' => [['$subtract' => [['$multiply' => ['$quantity', '$market_price']], '$cost_basis']], '$cost_basis']], 100]]
]
]
]],
['$sort' => ['market_value' => -1]]
];
$holdings = [];
foreach ($this->holdings->aggregate($holdingsPipeline) as $holding) {
$holdings[] = [
'symbol' => $holding['symbol'],
'quantity' => (string)$holding['quantity'],
'avg_cost' => (string)$holding['avg_cost'],
'market_price' => (string)($holding['market_price'] ?? new Decimal128('0')),
'market_value' => (string)$holding['market_value'],
'unrealized_gain' => (string)$holding['unrealized_gain'],
'return_percentage' => round((float)(string)$holding['return_percentage'], 2)
];
}
$totalGain = bcadd((string)$portfolio['realized_gain'], (string)$portfolio['unrealized_gain'], 34);
$totalReturnPercentage = bccomp((string)$portfolio['cost_basis'], '0', 34) === 0
? '0'
: bcmul(bcdiv($totalGain, (string)$portfolio['cost_basis'], 34), '100', 2);
return [
'portfolio_id' => $portfolioId,
'name' => $portfolio['name'],
'base_currency' => $portfolio['base_currency'],
'total_value' => (string)$portfolio['total_value'],
'cash_balance' => (string)$portfolio['cash_balance'],
'cost_basis' => (string)$portfolio['cost_basis'],
'realized_gain' => (string)$portfolio['realized_gain'],
'unrealized_gain' => (string)$portfolio['unrealized_gain'],
'total_gain' => $totalGain,
'total_return_percentage' => $totalReturnPercentage,
'holdings' => $holdings
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$ipm = new InvestmentPortfolioManager($client);
$portfolioId = $ipm->createPortfolio([
'name' => '我的股票组合',
'owner_id' => 'user001',
'base_currency' => 'CNY',
'initial_cash' => '1000000.00'
]);
echo "投资组合ID: $portfolioId\n";
$ipm->buyAsset($portfolioId, 'AAPL', '100', '150.00', '10.00');
$ipm->buyAsset($portfolioId, 'GOOGL', '50', '2800.00', '15.00');
$ipm->updateMarketValues(['AAPL' => '175.00', 'GOOGL' => '2900.00']);
$summary = $ipm->getPortfolioSummary($portfolioId);
echo "投资组合摘要: " . json_encode($summary, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 投资组合ID: 507f1f77bcf86cd799439011
// 投资组合摘要: {
// "portfolio_id": "507f1f77bcf86cd799439011",
// "name": "我的股票组合",
// "total_value": "32000.00",
// "cash_balance": "549975.00",
// "unrealized_gain": "16490.00",
// "holdings": [...]
// }
?>场景4:保险精算系统
php
<?php
use MongoDB\BSON\Decimal128;
class InsuranceActuarialSystem
{
private MongoDB\Collection $policies;
private MongoDB\Collection $claims;
private MongoDB\Collection $reserves;
public function __construct(MongoDB\Client $client)
{
$this->policies = $client->insurance->policies;
$this->claims = $client->insurance->claims;
$this->reserves = $client->insurance->reserves;
}
public function createPolicy(array $data): string
{
$policyId = 'POL' . date('Ymd') . str_pad(
$this->policies->countDocuments([]) + 1,
6,
'0',
STR_PAD_LEFT
);
$premium = $this->calculatePremium($data);
$this->policies->insertOne([
'policy_id' => $policyId,
'policyholder' => $data['policyholder'],
'type' => $data['type'],
'coverage_amount' => new Decimal128($data['coverage_amount']),
'premium' => new Decimal128($premium),
'deductible' => new Decimal128($data['deductible'] ?? '0'),
'start_date' => new MongoDB\BSON\UTCDateTime(strtotime($data['start_date']) * 1000),
'end_date' => new MongoDB\BSON\UTCDateTime(strtotime($data['end_date']) * 1000),
'status' => 'active',
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
return $policyId;
}
private function calculatePremium(array $data): string
{
$baseRate = $data['base_rate'] ?? '0.01';
$coverageAmount = $data['coverage_amount'];
$premium = bcmul($coverageAmount, $baseRate, 34);
if (isset($data['risk_factor'])) {
$premium = bcmul($premium, $data['risk_factor'], 34);
}
return $premium;
}
public function fileClaim(array $data): string
{
$claimId = 'CLM' . date('Ymd') . str_pad(
$this->claims->countDocuments([]) + 1,
6,
'0',
STR_PAD_LEFT
);
$policy = $this->policies->findOne(['policy_id' => $data['policy_id']]);
if (!$policy) {
throw new Exception("保单不存在");
}
$claimAmount = new Decimal128($data['claim_amount']);
$deductible = $policy['deductible'];
$pipeline = [
['$match' => ['policy_id' => $data['policy_id']]],
['$project' => [
'coverage_amount' => 1,
'deductible' => 1,
'claim_amount' => ['$literal' => $claimAmount],
'claim_after_deductible' => ['$subtract' => [['$literal' => $claimAmount], '$deductible']],
'is_within_coverage' => ['$lte' => [['$literal' => $claimAmount], '$coverage_amount']]
]]
];
$calc = $this->policies->aggregate($pipeline)->toArray()[0];
if (!$calc['is_within_coverage']) {
throw new Exception("索赔金额超过保额");
}
$payoutAmount = bccomp((string)$calc['claim_after_deductible'], '0', 34) > 0
? (string)$calc['claim_after_deductible']
: '0';
$this->claims->insertOne([
'claim_id' => $claimId,
'policy_id' => $data['policy_id'],
'claim_amount' => $claimAmount,
'deductible_applied' => $deductible,
'payout_amount' => new Decimal128($payoutAmount),
'description' => $data['description'],
'status' => 'pending',
'filed_at' => new MongoDB\BSON\UTCDateTime(),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
return $claimId;
}
public function calculateReserves(string $date): array
{
$asOfDate = new MongoDB\BSON\UTCDateTime(strtotime($date) * 1000);
$pipeline = [
['$match' => [
'status' => 'active',
'end_date' => ['$gte' => $asOfDate]
]],
['$project' => [
'policy_id' => 1,
'premium' => 1,
'coverage_amount' => 1,
'start_date' => 1,
'end_date' => 1,
'policy_duration_days' => [
'$divide' => [
['$subtract' => ['$end_date', '$start_date']],
86400000
]
],
'elapsed_days' => [
'$divide' => [
['$subtract' => [['$literal' => $asOfDate], '$start_date']],
86400000
]
]
]],
['$project' => [
'policy_id' => 1,
'premium' => 1,
'coverage_amount' => 1,
'earned_premium' => [
'$multiply' => [
'$premium',
['$divide' => ['$elapsed_days', '$policy_duration_days']]
]
],
'unearned_premium' => [
'$multiply' => [
'$premium',
['$subtract' => [1, ['$divide' => ['$elapsed_days', '$policy_duration_days']]]]
]
]
]],
['$group' => [
'_id' => null,
'total_premium' => ['$sum' => '$premium'],
'total_earned_premium' => ['$sum' => '$earned_premium'],
'total_unearned_premium' => ['$sum' => '$unearned_premium'],
'total_coverage' => ['$sum' => '$coverage_amount'],
'policy_count' => ['$sum' => 1]
]]
];
$result = $this->policies->aggregate($pipeline)->toArray();
if (empty($result)) {
return [
'date' => $date,
'total_premium' => '0',
'earned_premium' => '0',
'unearned_premium' => '0',
'total_coverage' => '0',
'policy_count' => 0
];
}
$data = $result[0];
$this->reserves->insertOne([
'date' => $asOfDate,
'total_premium' => $data['total_premium'],
'earned_premium' => $data['total_earned_premium'],
'unearned_premium' => $data['total_unearned_premium'],
'total_coverage' => $data['total_coverage'],
'policy_count' => $data['policy_count'],
'calculated_at' => new MongoDB\BSON\UTCDateTime()
]);
return [
'date' => $date,
'total_premium' => (string)$data['total_premium'],
'earned_premium' => (string)$data['total_earned_premium'],
'unearned_premium' => (string)$data['total_unearned_premium'],
'total_coverage' => (string)$data['total_coverage'],
'policy_count' => $data['policy_count']
];
}
public function getLossRatio(string $startDate, string $endDate): array
{
$start = new MongoDB\BSON\UTCDateTime(strtotime($startDate) * 1000);
$end = new MongoDB\BSON\UTCDateTime(strtotime($endDate) * 1000);
$claimsPipeline = [
['$match' => [
'status' => 'approved',
'filed_at' => ['$gte' => $start, '$lte' => $end]
]],
['$group' => [
'_id' => null,
'total_claims' => ['$sum' => '$claim_amount'],
'total_payouts' => ['$sum' => '$payout_amount'],
'claim_count' => ['$sum' => 1]
]]
];
$claimsResult = $this->claims->aggregate($claimsPipeline)->toArray()[0] ?? [
'total_claims' => new Decimal128('0'),
'total_payouts' => new Decimal128('0'),
'claim_count' => 0
];
$premiumPipeline = [
['$match' => [
'status' => 'active',
'start_date' => ['$gte' => $start, '$lte' => $end]
]],
['$group' => [
'_id' => null,
'total_premium' => ['$sum' => '$premium']
]]
];
$premiumResult = $this->policies->aggregate($premiumPipeline)->toArray()[0] ?? [
'total_premium' => new Decimal128('0')
];
$totalPremium = (string)$premiumResult['total_premium'];
$totalPayouts = (string)$claimsResult['total_payouts'];
$lossRatio = bccomp($totalPremium, '0', 34) === 0
? '0'
: bcmul(bcdiv($totalPayouts, $totalPremium, 34), '100', 2);
return [
'period' => ['start' => $startDate, 'end' => $endDate],
'total_premium' => $totalPremium,
'total_claims' => (string)$claimsResult['total_claims'],
'total_payouts' => $totalPayouts,
'claim_count' => $claimsResult['claim_count'],
'loss_ratio' => $lossRatio . '%'
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$ias = new InsuranceActuarialSystem($client);
$policyId = $ias->createPolicy([
'policyholder' => '张三',
'type' => 'health',
'coverage_amount' => '500000.00',
'deductible' => '1000.00',
'base_rate' => '0.02',
'start_date' => '2024-01-01',
'end_date' => '2024-12-31'
]);
echo "保单ID: $policyId\n";
$claimId = $ias->fileClaim([
'policy_id' => $policyId,
'claim_amount' => '50000.00',
'description' => '住院医疗费用'
]);
echo "理赔ID: $claimId\n";
$reserves = $ias->calculateReserves('2024-06-30');
echo "准备金计算: " . json_encode($reserves, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
$lossRatio = $ias->getLossRatio('2024-01-01', '2024-12-31');
echo "赔付率: " . json_encode($lossRatio, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 保单ID: POL20240115000001
// 理赔ID: CLM20240115000001
// 准备金计算: {
// "date": "2024-06-30",
// "total_premium": "10000.00",
// "earned_premium": "5000.00",
// "unearned_premium": "5000.00",
// "total_coverage": "500000.00",
// "policy_count": 1
// }
// 赔付率: {
// "period": {"start": "2024-01-01", "end": "2024-12-31"},
// "total_premium": "10000.00",
// "total_payouts": "49000.00",
// "loss_ratio": "490.00%"
// }
?>行业最佳实践
实践1:类型统一策略
php
<?php
use MongoDB\BSON\Decimal128;
class DecimalTypeStrategy
{
public static function fromString(string $value): Decimal128
{
$trimmed = trim($value);
if (!is_numeric($trimmed)) {
throw new InvalidArgumentException("Invalid decimal value: $value");
}
return new Decimal128($trimmed);
}
public static function fromInt(int $value): Decimal128
{
return new Decimal128((string)$value);
}
public static function add(Decimal128 $a, Decimal128 $b): Decimal128
{
return new Decimal128(bcadd((string)$a, (string)$b, 34));
}
public static function subtract(Decimal128 $a, Decimal128 $b): Decimal128
{
return new Decimal128(bcsub((string)$a, (string)$b, 34));
}
public static function multiply(Decimal128 $a, Decimal128 $b): Decimal128
{
return new Decimal128(bcmul((string)$a, (string)$b, 34));
}
public static function divide(Decimal128 $a, Decimal128 $b, int $scale = 34): Decimal128
{
if (bccomp((string)$b, '0', 34) === 0) {
throw new InvalidArgumentException("Division by zero");
}
return new Decimal128(bcdiv((string)$a, (string)$b, $scale));
}
public static function compare(Decimal128 $a, Decimal128 $b): int
{
return bccomp((string)$a, (string)$b, 34);
}
public static function round(Decimal128 $value, int $precision = 2): Decimal128
{
$str = (string)$value;
$rounded = round((float)$str, $precision);
return new Decimal128(number_format($rounded, $precision, '.', ''));
}
public static function abs(Decimal128 $value): Decimal128
{
$str = (string)$value;
if (bccomp($str, '0', 34) < 0) {
return new Decimal128(bcmul($str, '-1', 34));
}
return $value;
}
}
// 使用示例
$a = DecimalTypeStrategy::fromString('100.50');
$b = DecimalTypeStrategy::fromString('50.25');
$sum = DecimalTypeStrategy::add($a, $b);
echo "加法: $a + $b = $sum\n";
$difference = DecimalTypeStrategy::subtract($a, $b);
echo "减法: $a - $b = $difference\n";
$product = DecimalTypeStrategy::multiply($a, $b);
echo "乘法: $a × $b = $product\n";
$quotient = DecimalTypeStrategy::divide($a, $b, 4);
echo "除法: $a ÷ $b = $quotient\n";
$comparison = DecimalTypeStrategy::compare($a, $b);
echo "比较: $a vs $b = $comparison\n";
// 运行结果展示:
// 加法: 100.50 + 50.25 = 150.75
// 减法: 100.50 - 50.25 = 50.25
// 乘法: 100.50 × 50.25 = 5050.1250
// 除法: 100.50 ÷ 50.25 = 2.0000
// 比较: 100.50 vs 50.25 = 1
?>实践2:验证和清理
php
<?php
use MongoDB\BSON\Decimal128;
class DecimalValidator
{
public static function validate(string $value, array $rules = []): array
{
$errors = [];
if (!is_numeric(trim($value))) {
$errors[] = "无效的数值格式";
return ['valid' => false, 'errors' => $errors];
}
$decimal = new Decimal128(trim($value));
$strValue = (string)$decimal;
if (isset($rules['min']) && bccomp($strValue, $rules['min'], 34) < 0) {
$errors[] = "值不能小于 {$rules['min']}";
}
if (isset($rules['max']) && bccomp($strValue, $rules['max'], 34) > 0) {
$errors[] = "值不能大于 {$rules['max']}";
}
if (isset($rules['scale'])) {
$parts = explode('.', $strValue);
if (isset($parts[1]) && strlen($parts[1]) > $rules['scale']) {
$errors[] = "小数位数不能超过 {$rules['scale']} 位";
}
}
if (isset($rules['positive']) && $rules['positive'] && bccomp($strValue, '0', 34) <= 0) {
$errors[] = "值必须为正数";
}
return [
'valid' => empty($errors),
'errors' => $errors,
'value' => $strValue,
'decimal' => $decimal
];
}
public static function sanitize(string $value, array $options = []): Decimal128
{
$config = array_merge([
'trim' => true,
'scale' => null
], $options);
if ($config['trim']) {
$value = trim($value);
}
$value = str_replace(',', '', $value);
if (!is_numeric($value)) {
throw new InvalidArgumentException("Invalid decimal value: $value");
}
if ($config['scale'] !== null) {
$float = (float)$value;
$rounded = round($float, $config['scale']);
$value = number_format($rounded, $config['scale'], '.', '');
}
return new Decimal128($value);
}
}
// 使用示例
$validation = DecimalValidator::validate('100.50', [
'min' => '0',
'max' => '1000',
'scale' => 2,
'positive' => true
]);
echo "验证结果: " . json_encode($validation, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
$sanitized = DecimalValidator::sanitize(' 1,234.5678 ', ['scale' => 2]);
echo "清理结果: $sanitized\n";
// 运行结果展示:
// 验证结果: {
// "valid": true,
// "errors": [],
// "value": "100.50",
// "decimal": {}
// }
// 清理结果: 1234.57
?>实践3:精度配置管理
php
<?php
use MongoDB\BSON\Decimal128;
class PrecisionConfig
{
private static array $configs = [
'currency' => ['scale' => 2, 'round_mode' => PHP_ROUND_HALF_UP],
'tax_rate' => ['scale' => 4, 'round_mode' => PHP_ROUND_HALF_UP],
'percentage' => ['scale' => 6, 'round_mode' => PHP_ROUND_HALF_UP],
'scientific' => ['scale' => 34, 'round_mode' => PHP_ROUND_HALF_UP]
];
public static function format(Decimal128 $value, string $type): string
{
$config = self::$configs[$type] ?? ['scale' => 2, 'round_mode' => PHP_ROUND_HALF_UP];
$str = (string)$value;
$float = (float)$str;
$rounded = round($float, $config['scale'], $config['round_mode']);
return number_format($rounded, $config['scale'], '.', '');
}
public static function create(string $value, string $type): Decimal128
{
$config = self::$configs[$type] ?? ['scale' => 2, 'round_mode' => PHP_ROUND_HALF_UP];
$sanitized = self::sanitize($value, $config);
return new Decimal128($sanitized);
}
private static function sanitize(string $value, array $config): string
{
$value = trim(str_replace(',', '', $value));
if (!is_numeric($value)) {
throw new InvalidArgumentException("Invalid decimal value: $value");
}
$float = (float)$value;
$rounded = round($float, $config['scale'], $config['round_mode']);
return number_format($rounded, $config['scale'], '.', '');
}
}
// 使用示例
$price = PrecisionConfig::create('99.999', 'currency');
echo "货币格式: $price\n";
$rate = PrecisionConfig::create('0.12345678', 'tax_rate');
echo "税率格式: $rate\n";
$percentage = PrecisionConfig::create('0.123456789012', 'percentage');
echo "百分比格式: $percentage\n";
// 运行结果展示:
// 货币格式: 100.00
// 税率格式: 0.1235
// 百分比格式: 0.123457
?>实践4:审计日志
php
<?php
use MongoDB\BSON\Decimal128;
class DecimalAuditLogger
{
private MongoDB\Collection $auditLog;
public function __construct(MongoDB\Client $client)
{
$this->auditLog = $client->audit->decimal_changes;
}
public function logChange(
string $entityType,
string $entityId,
string $field,
?Decimal128 $oldValue,
Decimal128 $newValue,
string $reason,
string $userId
): void {
$this->auditLog->insertOne([
'entity_type' => $entityType,
'entity_id' => $entityId,
'field' => $field,
'old_value' => $oldValue,
'new_value' => $newValue,
'change_amount' => $oldValue
? new Decimal128(bcsub((string)$newValue, (string)$oldValue, 34))
: $newValue,
'reason' => $reason,
'user_id' => $userId,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
public function getChangeHistory(
string $entityType,
string $entityId,
?string $field = null
): array {
$query = [
'entity_type' => $entityType,
'entity_id' => $entityId
];
if ($field) {
$query['field'] = $field;
}
$pipeline = [
['$match' => $query],
['$sort' => ['created_at' => -1]],
['$project' => [
'field' => 1,
'old_value' => 1,
'new_value' => 1,
'change_amount' => 1,
'reason' => 1,
'user_id' => 1,
'created_at' => 1
]]
];
$history = [];
foreach ($this->auditLog->aggregate($pipeline) as $doc) {
$history[] = [
'field' => $doc['field'],
'old_value' => $doc['old_value'] ? (string)$doc['old_value'] : null,
'new_value' => (string)$doc['new_value'],
'change_amount' => (string)$doc['change_amount'],
'reason' => $doc['reason'],
'user_id' => $doc['user_id'],
'created_at' => $doc['created_at']->toDateTime()->format('Y-m-d H:i:s')
];
}
return $history;
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$logger = new DecimalAuditLogger($client);
$logger->logChange(
'account',
'ACC001',
'balance',
new Decimal128('10000.00'),
new Decimal128('9000.00'),
'转账支出',
'user001'
);
$history = $logger->getChangeHistory('account', 'ACC001', 'balance');
echo "变更历史: " . json_encode($history, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 变更历史: [
// {
// "field": "balance",
// "old_value": "10000.00",
// "new_value": "9000.00",
// "change_amount": "-1000.00",
// "reason": "转账支出",
// "user_id": "user001",
// "created_at": "2024-01-15 10:30:00"
// }
// ]
?>常见问题答疑
问题1:Decimal128和Double应该如何选择?
答:选择依据如下:
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 金融金额 | Decimal128 | 需要精确计算,避免精度丢失 |
| 科学计算 | Decimal128 | 需要高精度,34位有效数字 |
| 坐标数据 | Double | 精度足够,存储空间小 |
| 评分统计 | Double | 不需要绝对精确,性能更好 |
| 利率/税率 | Decimal128 | 需要精确计算百分比 |
| 物理测量 | Double | 测量本身有误差,Double精度足够 |
php
<?php
use MongoDB\BSON\Decimal128;
// 金融场景必须用Decimal128
$price = new Decimal128('99.99');
$tax = new Decimal128('0.13');
$total = new Decimal128(bcmul((string)$price, bcadd('1', (string)$tax, 34), 34));
echo "含税价格: $total\n";
// 坐标场景可以用Double
$location = [
'latitude' => 39.9042,
'longitude' => 116.4074
];
?>问题2:如何在PHP中进行Decimal128的数学运算?
答:PHP中需要使用bcmath扩展进行精确运算:
php
<?php
use MongoDB\BSON\Decimal128;
class DecimalMath
{
public static function add(Decimal128 $a, Decimal128 $b): Decimal128
{
return new Decimal128(bcadd((string)$a, (string)$b, 34));
}
public static function subtract(Decimal128 $a, Decimal128 $b): Decimal128
{
return new Decimal128(bcsub((string)$a, (string)$b, 34));
}
public static function multiply(Decimal128 $a, Decimal128 $b): Decimal128
{
return new Decimal128(bcmul((string)$a, (string)$b, 34));
}
public static function divide(Decimal128 $a, Decimal128 $b, int $scale = 34): Decimal128
{
return new Decimal128(bcdiv((string)$a, (string)$b, $scale));
}
public static function compare(Decimal128 $a, Decimal128 $b): int
{
return bccomp((string)$a, (string)$b, 34);
}
}
// 使用示例
$a = new Decimal128('100.50');
$b = new Decimal128('3');
echo "加法: " . DecimalMath::add($a, $b) . "\n";
echo "减法: " . DecimalMath::subtract($a, $b) . "\n";
echo "乘法: " . DecimalMath::multiply($a, $b) . "\n";
echo "除法: " . DecimalMath::divide($a, $b, 4) . "\n";
echo "比较: " . DecimalMath::compare($a, $b) . "\n";
// 运行结果展示:
// 加法: 103.50
// 减法: 97.50
// 乘法: 301.50
// 除法: 33.5000
// 比较: 1
?>问题3:Decimal128在聚合管道中如何使用?
答:MongoDB聚合管道提供了丰富的数值运算操作符:
php
<?php
use MongoDB\BSON\Decimal128;
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->products;
// 常用聚合操作符
$pipeline = [
['$project' => [
'name' => 1,
'price' => 1,
'quantity' => 1,
// 基本运算
'total' => ['$multiply' => ['$price', '$quantity']],
'discounted' => ['$multiply' => ['$price', ['$subtract' => [1, '$discountRate']]]],
// 类型转换
'price_as_double' => ['$toDouble' => '$price'],
'price_as_string' => ['$toString' => '$price'],
// 舍入
'rounded' => ['$round' => ['$price', 2]],
'floored' => ['$floor' => '$price'],
'ceiled' => ['$ceil' => '$price'],
// 比较
'is_expensive' => ['$gte' => ['$price', new Decimal128('100')]]
]]
];
// 分组聚合
$groupPipeline = [
['$group' => [
'_id' => '$category',
'total_value' => ['$sum' => '$price'],
'avg_price' => ['$avg' => '$price'],
'min_price' => ['$min' => '$price'],
'max_price' => ['$max' => '$price'],
'count' => ['$sum' => 1]
]]
];
?>问题4:如何处理Decimal128的序列化和反序列化?
答:Decimal128的序列化处理:
php
<?php
use MongoDB\BSON\Decimal128;
class DecimalSerializer
{
public static function toJson(Decimal128 $value): string
{
return json_encode(['$numberDecimal' => (string)$value]);
}
public static function fromJson(string $json): Decimal128
{
$data = json_decode($json, true);
if (isset($data['$numberDecimal'])) {
return new Decimal128($data['$numberDecimal']);
}
throw new InvalidArgumentException("Invalid Decimal128 JSON format");
}
public static function formatForDisplay(Decimal128 $value, string $format = 'currency'): string
{
$str = (string)$value;
switch ($format) {
case 'currency':
return '¥' . number_format((float)$str, 2, '.', ',');
case 'percent':
return bcmul($str, '100', 2) . '%';
case 'scientific':
return sprintf('%.6E', (float)$str);
default:
return $str;
}
}
}
// 使用示例
$decimal = new Decimal128('12345.67');
echo "JSON: " . DecimalSerializer::toJson($decimal) . "\n";
echo "货币格式: " . DecimalSerializer::formatForDisplay($decimal, 'currency') . "\n";
echo "百分比格式: " . DecimalSerializer::formatForDisplay(new Decimal128('0.1234'), 'percent') . "\n";
echo "科学计数法: " . DecimalSerializer::formatForDisplay($decimal, 'scientific') . "\n";
// 运行结果展示:
// JSON: {"$numberDecimal":"12345.67"}
// 货币格式: ¥12,345.67
// 百分比格式: 12.34%
// 科学计数法: 1.234567E+4
?>问题5:Decimal128在不同MongoDB版本中的兼容性如何?
答:版本兼容性说明:
php
<?php
use MongoDB\BSON\Decimal128;
// MongoDB版本兼容性
// - MongoDB 3.4+: 支持Decimal128类型
// - MongoDB 3.2及以下: 不支持,会报错
// 驱动兼容性检查
function checkDecimal128Support(MongoDB\Client $client): array
{
try {
$admin = $client->admin;
$result = $admin->command(['buildInfo' => 1]);
$version = $result->toArray()[0]['version'];
$versionParts = explode('.', $version);
$majorVersion = (int)$versionParts[0];
$minorVersion = (int)($versionParts[1] ?? 0);
$supported = ($majorVersion > 3) || ($majorVersion === 3 && $minorVersion >= 4);
return [
'version' => $version,
'supported' => $supported,
'message' => $supported
? 'Decimal128 is supported'
: 'Decimal128 requires MongoDB 3.4 or later'
];
} catch (Exception $e) {
return [
'version' => 'unknown',
'supported' => false,
'message' => 'Unable to check version: ' . $e->getMessage()
];
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$compatibility = checkDecimal128Support($client);
echo "兼容性检查: " . json_encode($compatibility, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 兼容性检查: {
// "version": "5.0.0",
// "supported": true,
// "message": "Decimal128 is supported"
// }
?>问题6:如何优化Decimal128字段的查询性能?
答:性能优化策略:
php
<?php
use MongoDB\BSON\Decimal128;
class DecimalQueryOptimizer
{
private MongoDB\Collection $collection;
public function __construct(MongoDB\Collection $collection)
{
$this->collection = $collection;
}
public function createOptimalIndexes(): void
{
// 1. 单字段索引
$this->collection->createIndex(['price' => 1]);
// 2. 复合索引
$this->collection->createIndex(['category' => 1, 'price' => -1]);
// 3. 部分索引
$this->collection->createIndex(
['price' => 1],
[
'partialFilterExpression' => [
'price' => ['$gte' => new Decimal128('0')]
]
]
);
}
public function optimizedRangeQuery(string $field, string $min, string $max): array
{
return $this->collection->find([
$field => [
'$gte' => new Decimal128($min),
'$lte' => new Decimal128($max)
]
], [
'sort' => [$field => 1],
'projection' => [$field => 1, 'name' => 1]
])->toArray();
}
}
// 使用示例
$client = new MongoDB\Client("mongodb://localhost:27017");
$collection = $client->testdb->products;
$optimizer = new DecimalQueryOptimizer($collection);
$optimizer->createOptimalIndexes();
$results = $optimizer->optimizedRangeQuery('price', '100', '500');
echo "查询结果数量: " . count($results) . "\n";
// 运行结果展示:
// 查询结果数量: 10
?>实战练习
练习1:创建金融账户系统
任务描述:创建一个简单的金融账户系统,实现账户创建、存款、取款和转账功能。
php
<?php
use MongoDB\BSON\Decimal128;
class SimpleBankAccount
{
private MongoDB\Collection $accounts;
public function __construct(MongoDB\Client $client)
{
$this->accounts = $client->bank->accounts;
}
public function createAccount(string $accountId, string $initialBalance = '0'): void
{
$this->accounts->insertOne([
'account_id' => $accountId,
'balance' => new Decimal128($initialBalance),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
public function deposit(string $accountId, string $amount): void
{
$this->accounts->updateOne(
['account_id' => $accountId],
['$inc' => ['balance' => new Decimal128($amount)]]
);
}
public function withdraw(string $accountId, string $amount): bool
{
$account = $this->accounts->findOne(['account_id' => $accountId]);
if (bccomp((string)$account['balance'], $amount, 34) < 0) {
return false;
}
$this->accounts->updateOne(
['account_id' => $accountId],
['$inc' => ['balance' => new Decimal128('-' . $amount)]]
);
return true;
}
public function transfer(string $fromId, string $toId, string $amount): bool
{
if (!$this->withdraw($fromId, $amount)) {
return false;
}
$this->deposit($toId, $amount);
return true;
}
public function getBalance(string $accountId): string
{
$account = $this->accounts->findOne(['account_id' => $accountId]);
return $account ? (string)$account['balance'] : '0';
}
}
// 测试代码
$client = new MongoDB\Client("mongodb://localhost:27017");
$bank = new SimpleBankAccount($client);
$bank->createAccount('A001', '1000.00');
$bank->createAccount('A002', '500.00');
$bank->deposit('A001', '200.00');
echo "A001余额: " . $bank->getBalance('A001') . "\n";
$bank->transfer('A001', 'A002', '300.00');
echo "A001余额: " . $bank->getBalance('A001') . "\n";
echo "A002余额: " . $bank->getBalance('A002') . "\n";
// 运行结果展示:
// A001余额: 1200.00
// A001余额: 900.00
// A002余额: 800.00
?>练习2:实现价格计算器
任务描述:创建一个价格计算器,支持折扣、税费和总价计算。
php
<?php
use MongoDB\BSON\Decimal128;
class PriceCalculator
{
private MongoDB\Collection $products;
public function __construct(MongoDB\Client $client)
{
$this->products = $client->shop->products;
}
public function addProduct(string $name, string $price, string $discountRate = '0', string $taxRate = '0.13'): void
{
$this->products->insertOne([
'name' => $name,
'base_price' => new Decimal128($price),
'discount_rate' => new Decimal128($discountRate),
'tax_rate' => new Decimal128($taxRate)
]);
}
public function calculateFinalPrice(string $name, int $quantity = 1): array
{
$pipeline = [
['$match' => ['name' => $name]],
['$project' => [
'name' => 1,
'base_price' => 1,
'discount_rate' => 1,
'tax_rate' => 1,
'discounted_price' => [
'$multiply' => ['$base_price', ['$subtract' => [1, '$discount_rate']]]
]
]],
['$project' => [
'name' => 1,
'base_price' => 1,
'discount_rate' => 1,
'tax_rate' => 1,
'discounted_price' => 1,
'tax_amount' => ['$multiply' => ['$discounted_price', '$tax_rate']],
'final_unit_price' => [
'$multiply' => ['$discounted_price', ['$add' => [1, '$tax_rate']]]
]
]]
];
$result = $this->products->aggregate($pipeline)->toArray()[0];
$finalUnitPrice = (string)$result['final_unit_price'];
$totalPrice = bcmul($finalUnitPrice, (string)$quantity, 2);
return [
'name' => $name,
'base_price' => (string)$result['base_price'],
'discount_rate' => (string)$result['discount_rate'],
'discounted_price' => (string)$result['discounted_price'],
'tax_rate' => (string)$result['tax_rate'],
'tax_amount' => (string)$result['tax_amount'],
'final_unit_price' => $finalUnitPrice,
'quantity' => $quantity,
'total_price' => $totalPrice
];
}
}
// 测试代码
$client = new MongoDB\Client("mongodb://localhost:27017");
$calc = new PriceCalculator($client);
$calc->addProduct('笔记本电脑', '5999.00', '0.10', '0.13');
$calc->addProduct('手机', '2999.00', '0.05', '0.13');
$price1 = $calc->calculateFinalPrice('笔记本电脑', 2);
echo "价格计算: " . json_encode($price1, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
$price2 = $calc->calculateFinalPrice('手机', 1);
echo "价格计算: " . json_encode($price2, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 价格计算: {
// "name": "笔记本电脑",
// "base_price": "5999.00",
// "discount_rate": "0.10",
// "discounted_price": "5399.10",
// "tax_rate": "0.13",
// "tax_amount": "701.883",
// "final_unit_price": "6100.983",
// "quantity": 2,
// "total_price": "12201.97"
// }
?>练习3:实现预算跟踪系统
任务描述:创建一个预算跟踪系统,支持预算创建、支出记录和预算状态查询。
php
<?php
use MongoDB\BSON\Decimal128;
class BudgetTracker
{
private MongoDB\Collection $budgets;
private MongoDB\Collection $expenses;
public function __construct(MongoDB\Client $client)
{
$this->budgets = $client->budget->budgets;
$this->expenses = $client->budget->expenses;
}
public function createBudget(string $name, string $totalAmount): string
{
$budgetId = (string)new MongoDB\BSON\ObjectId();
$this->budgets->insertOne([
'_id' => $budgetId,
'name' => $name,
'total_amount' => new Decimal128($totalAmount),
'spent_amount' => new Decimal128('0'),
'remaining_amount' => new Decimal128($totalAmount),
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
return $budgetId;
}
public function addExpense(string $budgetId, string $category, string $amount, string $description): string
{
$budget = $this->budgets->findOne(['_id' => $budgetId]);
if (!$budget) {
throw new Exception("预算不存在");
}
if (bccomp((string)$budget['remaining_amount'], $amount, 34) < 0) {
throw new Exception("预算不足");
}
$expenseId = (string)new MongoDB\BSON\ObjectId();
$this->expenses->insertOne([
'_id' => $expenseId,
'budget_id' => $budgetId,
'category' => $category,
'amount' => new Decimal128($amount),
'description' => $description,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
$this->budgets->updateOne(
['_id' => $budgetId],
[
'$inc' => [
'spent_amount' => new Decimal128($amount),
'remaining_amount' => new Decimal128('-' . $amount)
]
]
);
return $expenseId;
}
public function getBudgetStatus(string $budgetId): array
{
$budget = $this->budgets->findOne(['_id' => $budgetId]);
if (!$budget) {
throw new Exception("预算不存在");
}
$usagePercentage = bcmul(
bcdiv((string)$budget['spent_amount'], (string)$budget['total_amount'], 34),
'100',
2
);
return [
'name' => $budget['name'],
'total_amount' => (string)$budget['total_amount'],
'spent_amount' => (string)$budget['spent_amount'],
'remaining_amount' => (string)$budget['remaining_amount'],
'usage_percentage' => $usagePercentage . '%'
];
}
public function getExpensesByCategory(string $budgetId): array
{
$pipeline = [
['$match' => ['budget_id' => $budgetId]],
['$group' => [
'_id' => '$category',
'total' => ['$sum' => '$amount'],
'count' => ['$sum' => 1]
]],
['$sort' => ['total' => -1]]
];
$result = [];
foreach ($this->expenses->aggregate($pipeline) as $doc) {
$result[] = [
'category' => $doc['_id'],
'total' => (string)$doc['total'],
'count' => $doc['count']
];
}
return $result;
}
}
// 测试代码
$client = new MongoDB\Client("mongodb://localhost:27017");
$tracker = new BudgetTracker($client);
$budgetId = $tracker->createBudget('月度生活预算', '5000.00');
echo "预算ID: $budgetId\n";
$tracker->addExpense($budgetId, '餐饮', '800.00', '日常餐饮');
$tracker->addExpense($budgetId, '交通', '300.00', '地铁公交');
$tracker->addExpense($budgetId, '餐饮', '200.00', '聚餐');
$status = $tracker->getBudgetStatus($budgetId);
echo "预算状态: " . json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
$byCategory = $tracker->getExpensesByCategory($budgetId);
echo "分类统计: " . json_encode($byCategory, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
// 运行结果展示:
// 预算ID: 507f1f77bcf86cd799439011
// 预算状态: {
// "name": "月度生活预算",
// "total_amount": "5000.00",
// "spent_amount": "1300.00",
// "remaining_amount": "3700.00",
// "usage_percentage": "26.00%"
// }
// 分类统计: [
// {"category": "餐饮", "total": "1000.00", "count": 2},
// {"category": "交通", "total": "300.00", "count": 1}
// ]
?>知识点总结
核心要点
存储机制
- Decimal128使用16字节存储
- 支持34位有效数字精度
- 遵循IEEE 754-2008标准
PHP操作
- 使用MongoDB\BSON\Decimal128类
- 始终从字符串创建,避免精度问题
- 使用bcmath扩展进行数学运算
精度特点
- 十进制浮点数表示
- 无二进制精度问题
- 适合金融和会计计算
聚合运算
- 支持$add、$subtract、$multiply、$divide
- 支持$round、$floor、$ceil
- 支持$toDecimal、$toDouble类型转换
易错点回顾
从浮点数创建
- 错误:
new Decimal128((string)0.1) - 正确:
new Decimal128('0.1')
- 错误:
超出精度范围
- 最多34位有效数字
- 超出部分会被自动舍入
混合类型比较
- 使用$expr或$toDecimal统一类型
- 避免直接比较不同类型
聚合运算精度
- Decimal128保持精度
- 使用$divide计算平均值
无效字符串格式
- 必须是有效的数值字符串
- 使用trim清理空白字符
索引和排序
- 统一字段类型
- 使用Decimal128对象查询
拓展参考资料
官方文档
- MongoDB BSON Types - Decimal128
- PHP MongoDB Driver - Decimal128
- IEEE 754-2008 Standard
- MongoDB Aggregation Pipeline Operators
学习路径
基础阶段
- 掌握Decimal128的基本创建和查询
- 理解精度问题和解决方案
- 学会使用bcmath扩展
进阶阶段
- 掌握聚合管道中的数值运算
- 学习类型转换和比较
- 理解索引优化策略
高级阶段
- 分布式事务处理
- 金融系统设计
- 性能优化和监控
相关知识点
- bcmath扩展:PHP高精度数学运算库
- IEEE 754标准:浮点数运算标准
- 金融计算:精确金额处理最佳实践
- MongoDB聚合:数据处理和分析
- 事务处理:ACID特性和分布式事务
