Appearance
Double类型
概述
Double(双精度浮点数)类型是MongoDB中用于存储小数和浮点数值的数据类型。Double类型遵循IEEE 754标准的64位双精度浮点数格式,提供大约15-17位有效数字的精度。在价格、评分、坐标、科学计算等需要小数精度的场景中广泛应用。
理解Double类型的精度特性、存储机制和最佳实践,对于避免精度丢失、正确处理数值计算和优化存储空间至关重要。本章节将全面介绍MongoDB中Double类型的使用方法和注意事项。
基本概念
Double类型特性
MongoDB的Double类型具有以下核心特性:
1. IEEE 754标准
- 64位双精度浮点数
- 约15-17位有效数字
- 支持特殊值:Infinity、-Infinity、NaN
2. 数值范围
- 最大值:约±1.8 × 10^308
- 最小正数:约±5.0 × 10^-324
- 存储空间:8字节
3. 精度特点
- 二进制浮点数表示
- 存在精度问题(如0.1 + 0.2 ≠ 0.3)
- 不适合精确的货币计算
4. 查询能力
- 支持精确匹配
- 支持范围查询
- 支持数值比较
Double类型语法
php
<?php
// 场景说明:演示MongoDB Double类型的基本语法和使用方式
// 1. 连接MongoDB数据库
$client = new MongoDB\Client("mongodb://localhost:27017");
$database = $client->selectDatabase("testdb");
$collection = $database->selectCollection("double_examples");
// 2. 插入不同类型的浮点数数据
$document = [
'price' => 99.99, // 商品价格
'rating' => 4.5, // 评分
'temperature' => 36.5, // 温度
'coordinate_x' => 116.404, // 经度
'coordinate_y' => 39.915, // 纬度
'percentage' => 0.85, // 百分比
'scientific' => 1.23e-10, // 科学计数法
'negative' => -123.456, // 负数
'zero' => 0.0, // 零值
'infinity' => INF, // 无穷大
'nan' => NAN // 非数字
];
// 关键行注释:插入包含各种浮点数的文档
$result = $collection->insertOne($document);
echo "插入成功,文档ID: " . $result->getInsertedId() . "\n";
// 3. 浮点数查询操作
// 精确匹配(注意精度问题)
$exactMatch = $collection->findOne(['price' => 99.99]);
echo "精确匹配结果: " . json_encode($exactMatch) . "\n";
// 范围查询
$rangeQuery = $collection->find([
'rating' => ['$gte' => 4.0, '$lte' => 5.0]
])->toArray();
echo "范围查询结果数量: " . count($rangeQuery) . "\n";
// 大于查询
$greaterThan = $collection->find([
'price' => ['$gt' => 50.0]
])->toArray();
echo "大于查询结果数量: " . count($greaterThan) . "\n";
// 4. 浮点数运算
// 使用$inc操作符增加数值
$collection->updateOne(
['rating' => 4.5],
['$inc' => ['rating' => 0.1]]
);
echo "评分已增加\n";
// 运行结果展示:
// 插入成功,文档ID: 507f1f77bcf86cd799439011
// 精确匹配结果: {"_id":"507f1f77bcf86cd799439011","price":99.99,...}
// 范围查询结果数量: 1
// 大于查询结果数量: 1
// 评分已增加
?>常见改法对比:
php
<?php
// 错误示例:浮点数精度问题
$price1 = 0.1;
$price2 = 0.2;
$total = $price1 + $price2; // 错误:结果可能是0.30000000000000004
$collection->insertOne(['total' => $total]);
// 正确示例:使用Decimal128处理精确计算
$price1 = new MongoDB\BSON\Decimal128('0.1');
$price2 = new MongoDB\BSON\Decimal128('0.2');
$total = bcadd('0.1', '0.2', 2); // 正确:结果为0.30
$collection->insertOne(['total' => new MongoDB\BSON\Decimal128($total)]);
// 错误示例:直接比较浮点数
if ($total == 0.3) { // 错误:可能不相等
echo "相等\n";
}
// 正确示例:使用精度范围比较
$epsilon = 0.0001;
if (abs($total - 0.3) < $epsilon) { // 正确:考虑精度误差
echo "相等\n";
}
?>Double类型规范
1. 使用场景规范
- 适用于不需要精确计算的场景
- 科学计算、统计分析
- 地理坐标存储
- 不适用于货币计算
2. 存储规范
- 统一使用浮点数类型
- 避免整数和浮点数混用
- 注意特殊值处理
3. 查询规范
- 为常用查询字段创建索引
- 使用范围查询优化性能
- 注意精度比较问题
原理深度解析
Double存储机制
MongoDB使用BSON格式存储Double,遵循IEEE 754标准:
存储结构:
[类型标识(1字节)] + [符号位(1位) + 指数位(11位) + 尾数位(52位)]IEEE 754表示:
- 符号位:1位,表示正负
- 指数位:11位,表示指数
- 尾数位:52位,表示有效数字
php
<?php
// 场景说明:深入分析Double的存储机制和精度特性
class DoubleStorageAnalyzer {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 分析浮点数精度
public function analyzePrecision() {
$testCases = [
'simple_decimal' => 0.1,
'repeating' => 1/3,
'large_number' => 1e20,
'small_number' => 1e-20,
'negative' => -123.456,
'zero' => 0.0,
'infinity' => INF,
'nan' => NAN
];
$analysis = [];
foreach ($testCases as $name => $value) {
$analysis[$name] = [
'value' => $value,
'type' => gettype($value),
'is_finite' => is_finite($value),
'is_infinite' => is_infinite($value),
'is_nan' => is_nan($value),
'precision' => $this->getPrecision($value)
];
}
// 关键行注释:插入分析结果到数据库
$collection = $this->database->selectCollection('double_analysis');
$collection->insertOne([
'analysis' => $analysis,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
return $analysis;
}
// 获取精度信息
private function getPrecision($value) {
if (!is_finite($value)) {
return 'N/A';
}
$str = (string)$value;
$parts = explode('.', $str);
if (count($parts) === 2) {
return strlen($parts[1]);
}
return 0;
}
// 演示精度问题
public function demonstratePrecisionIssues() {
$issues = [];
// 问题1:0.1 + 0.2 ≠ 0.3
$sum = 0.1 + 0.2;
$issues['addition_issue'] = [
'expected' => 0.3,
'actual' => $sum,
'difference' => abs($sum - 0.3),
'equal' => $sum === 0.3
];
// 问题2:浮点数累加误差
$total = 0.0;
for ($i = 0; $i < 10; $i++) {
$total += 0.1;
}
$issues['accumulation_issue'] = [
'expected' => 1.0,
'actual' => $total,
'difference' => abs($total - 1.0)
];
// 问题3:大数精度丢失
$large = 1e16;
$result = $large + 1;
$issues['large_number_issue'] = [
'large' => $large,
'plus_one' => $result,
'equal' => $large === $result
];
return $issues;
}
// 比较不同存储方式
public function compareStorageMethods() {
$collection = $this->database->selectCollection('storage_comparison');
$value = 99.99;
// 方法1:直接存储Double
$collection->insertOne([
'method' => 'double',
'value' => $value,
'storage_bytes' => 8
]);
// 方法2:存储为整数(分)
$collection->insertOne([
'method' => 'integer_cents',
'value' => (int)($value * 100),
'original' => $value,
'storage_bytes' => 4
]);
// 方法3:存储为Decimal128
$collection->insertOne([
'method' => 'decimal128',
'value' => new MongoDB\BSON\Decimal128((string)$value),
'storage_bytes' => 16
]);
// 方法4:存储为字符串
$collection->insertOne([
'method' => 'string',
'value' => (string)$value,
'storage_bytes' => strlen((string)$value)
]);
return $collection->find()->toArray();
}
}
// 使用示例
$analyzer = new DoubleStorageAnalyzer('testdb');
$precision = $analyzer->analyzePrecision();
print_r($precision);
$issues = $analyzer->demonstratePrecisionIssues();
print_r($issues);
$comparison = $analyzer->compareStorageMethods();
print_r($comparison);
?>浮点数运算原理
php
<?php
// 场景说明:演示浮点数的运算机制和注意事项
class DoubleOperations {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 安全的浮点数比较
public function safeCompare($a, $b, $epsilon = 1e-10) {
// 关键行注释:使用epsilon进行安全的浮点数比较
return abs($a - $b) < $epsilon;
}
// 精确的浮点数运算
public function preciseOperation($a, $b, $operation) {
switch ($operation) {
case 'add':
return bcadd((string)$a, (string)$b, 10);
case 'sub':
return bcsub((string)$a, (string)$b, 10);
case 'mul':
return bcmul((string)$a, (string)$b, 10);
case 'div':
return bcdiv((string)$a, (string)$b, 10);
default:
throw new Exception("未知操作");
}
}
// 聚合统计
public function aggregateStatistics($collectionName, $field) {
$collection = $this->database->selectCollection($collectionName);
$pipeline = [
[
'$group' => [
'_id' => null,
'total' => ['$sum' => '$' . $field],
'average' => ['$avg' => '$' . $field],
'max' => ['$max' => '$' . $field],
'min' => ['$min' => '$' . $field],
'stdDev' => ['$stdDevPop' => '$' . $field]
]
]
];
return $collection->aggregate($pipeline)->toArray();
}
// 地理坐标处理
public function handleGeoCoordinates() {
$collection = $this->database->selectCollection('locations');
// 创建地理空间索引
$collection->createIndex(['location' => '2dsphere']);
// 插入位置数据
$collection->insertOne([
'name' => 'Beijing',
'location' => [
'type' => 'Point',
'coordinates' => [116.404, 39.915] // [经度, 纬度]
]
]);
// 查询附近的位置
$nearby = $collection->find([
'location' => [
'$near' => [
'$geometry' => [
'type' => 'Point',
'coordinates' => [116.404, 39.915]
],
'$maxDistance' => 10000 // 10公里
]
]
])->toArray();
return $nearby;
}
}
// 使用示例
$ops = new DoubleOperations('testdb');
// 安全比较
$equal = $ops->safeCompare(0.1 + 0.2, 0.3);
echo "0.1 + 0.2 是否等于 0.3: " . ($equal ? '是' : '否') . "\n";
// 精确运算
$sum = $ops->preciseOperation('0.1', '0.2', 'add');
echo "精确加法: 0.1 + 0.2 = $sum\n";
?>常见错误与踩坑点
错误1:浮点数精度丢失
错误表现:
- 0.1 + 0.2 ≠ 0.3
- 累加结果不准确
- 比较操作失败
产生原因:
- 二进制浮点数表示限制
- 无法精确表示某些十进制小数
- 累加误差放大
解决方案:
php
<?php
// 场景说明:演示浮点数精度问题的正确处理
class PrecisionHandler {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 错误示例:直接比较
public function wrongComparison() {
$a = 0.1 + 0.2;
$b = 0.3;
if ($a == $b) {
echo "相等\n";
} else {
echo "不相等: $a != $b\n";
}
}
// 正确示例:使用epsilon比较
public function correctComparison() {
$a = 0.1 + 0.2;
$b = 0.3;
$epsilon = 1e-10;
// 关键行注释:使用epsilon进行安全的浮点数比较
if (abs($a - $b) < $epsilon) {
echo "相等(考虑精度误差)\n";
} else {
echo "不相等\n";
}
}
// 错误示例:货币计算
public function wrongMonetaryCalculation() {
$collection = $this->database->selectCollection('wrong_monetary');
$price = 19.99;
$quantity = 3;
$total = $price * $quantity; // 错误:可能不等于59.97
$collection->insertOne([
'price' => $price,
'quantity' => $quantity,
'total' => $total,
'expected' => 59.97,
'error' => '精度丢失'
]);
echo "错误:$price * $quantity = $total (期望: 59.97)\n";
}
// 正确示例:使用整数或Decimal128
public function correctMonetaryCalculation() {
$collection = $this->database->selectCollection('correct_monetary');
// 方法1:使用整数(分)
$priceCents = 1999; // 19.99元 = 1999分
$quantity = 3;
$totalCents = $priceCents * $quantity; // 5997分
$collection->insertOne([
'method' => 'integer_cents',
'price_cents' => $priceCents,
'quantity' => $quantity,
'total_cents' => $totalCents,
'total_yuan' => $totalCents / 100
]);
// 方法2:使用Decimal128
$price = new MongoDB\BSON\Decimal128('19.99');
$quantity = 3;
$total = bcmul('19.99', '3', 2);
$collection->insertOne([
'method' => 'decimal128',
'price' => $price,
'quantity' => $quantity,
'total' => new MongoDB\BSON\Decimal128($total)
]);
echo "正确:使用整数或Decimal128进行货币计算\n";
}
// 错误示例:浮点数累加
public function wrongAccumulation() {
$total = 0.0;
for ($i = 0; $i < 10; $i++) {
$total += 0.1;
}
echo "错误:累加结果 = $total (期望: 1.0)\n";
}
// 正确示例:使用Kahan算法
public function correctAccumulation() {
$total = 0.0;
$compensation = 0.0;
for ($i = 0; $i < 10; $i++) {
$y = 0.1 - $compensation;
$t = $total + $y;
$compensation = ($t - $total) - $y;
$total = $t;
}
echo "正确:Kahan算法累加结果 = $total\n";
}
}
// 使用示例
$handler = new PrecisionHandler('testdb');
$handler->wrongComparison();
$handler->correctComparison();
$handler->wrongMonetaryCalculation();
$handler->correctMonetaryCalculation();
$handler->wrongAccumulation();
$handler->correctAccumulation();
?>错误2:特殊值处理不当
错误表现:
- NaN导致查询失败
- Infinity引起计算错误
- 未正确处理特殊值
产生原因:
- 未检查特殊值
- 缺少异常处理
- 不了解特殊值特性
解决方案:
php
<?php
// 场景说明:演示特殊值的正确处理
class SpecialValueHandler {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 错误示例:未处理特殊值
public function wrongSpecialValueHandling() {
$collection = $this->database->selectCollection('wrong_special');
// 错误:直接插入NaN和Infinity
$collection->insertOne([
'value' => NAN,
'description' => 'NaN value'
]);
$collection->insertOne([
'value' => INF,
'description' => 'Infinity'
]);
// 查询NaN会失败
$result = $collection->findOne(['value' => NAN]);
if (!$result) {
echo "错误:无法查询到NaN值\n";
}
}
// 正确示例:正确处理特殊值
public function correctSpecialValueHandling() {
$collection = $this->database->selectCollection('correct_special');
// 检查并处理特殊值
$values = [1.5, NAN, INF, -INF, 0.0];
foreach ($values as $value) {
$document = [
'original_value' => $value,
'is_nan' => is_nan($value),
'is_infinite' => is_infinite($value),
'is_finite' => is_finite($value),
'type' => $this->classifyValue($value)
];
$collection->insertOne($document);
}
// 查询特殊值
$nanDocs = $collection->find(['is_nan' => true])->toArray();
$infDocs = $collection->find(['is_infinite' => true])->toArray();
echo "NaN文档数量: " . count($nanDocs) . "\n";
echo "Infinity文档数量: " . count($infDocs) . "\n";
}
// 分类值类型
private function classifyValue($value) {
if (is_nan($value)) return 'NaN';
if (is_infinite($value)) return $value > 0 ? 'PositiveInfinity' : 'NegativeInfinity';
if ($value === 0.0) return 'Zero';
return 'Normal';
}
// 安全的数学运算
public function safeMathOperation($a, $b, $operation) {
// 检查特殊值
if (!is_finite($a) || !is_finite($b)) {
return [
'success' => false,
'error' => '输入包含特殊值',
'a_type' => $this->classifyValue($a),
'b_type' => $this->classifyValue($b)
];
}
$result = null;
switch ($operation) {
case 'add':
$result = $a + $b;
break;
case 'sub':
$result = $a - $b;
break;
case 'mul':
$result = $a * $b;
break;
case 'div':
if ($b == 0) {
return [
'success' => false,
'error' => '除零错误'
];
}
$result = $a / $b;
break;
}
// 检查结果是否有效
if (!is_finite($result)) {
return [
'success' => false,
'error' => '结果为特殊值',
'result_type' => $this->classifyValue($result)
];
}
return [
'success' => true,
'result' => $result
];
}
}
// 使用示例
$handler = new SpecialValueHandler('testdb');
$handler->wrongSpecialValueHandling();
$handler->correctSpecialValueHandling();
$safe = $handler->safeMathOperation(1.5, 0.0, 'div');
print_r($safe);
?>错误3:类型混淆
错误表现:
- 整数和浮点数混淆
- 查询不到预期结果
- 类型比较错误
产生原因:
- 未进行类型转换
- 数据来源不一致
- 缺少类型验证
解决方案:
php
<?php
// 场景说明:演示浮点数类型混淆的正确处理
class TypeConfusionHandler {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 错误示例:类型混淆
public function wrongTypeExample() {
$collection = $this->database->selectCollection('wrong_type');
// 插入整数
$collection->insertOne(['value' => 100]);
// 错误:使用浮点数查询整数
$result = $collection->findOne(['value' => 100.0]);
if ($result) {
echo "找到结果\n";
} else {
echo "错误:类型不匹配导致查询失败\n";
}
}
// 正确示例:统一类型
public function correctTypeExample() {
$collection = $this->database->selectCollection('correct_type');
// 统一使用浮点数
$collection->insertOne(['value' => 100.0]);
// 使用相同类型查询
$result = $collection->findOne(['value' => 100.0]);
if ($result) {
echo "正确:类型匹配查询成功\n";
}
}
// 类型转换工具
public function ensureFloat($value) {
if (is_int($value)) {
return (float)$value;
} elseif (is_string($value)) {
return (float)$value;
} elseif (is_bool($value)) {
return $value ? 1.0 : 0.0;
} elseif (is_null($value)) {
return 0.0;
}
return (float)$value;
}
// 类型检查
public function checkType($value) {
return [
'value' => $value,
'php_type' => gettype($value),
'is_float' => is_float($value),
'is_int' => is_int($value),
'is_numeric' => is_numeric($value)
];
}
}
// 使用示例
$handler = new TypeConfusionHandler('testdb');
$handler->wrongTypeExample();
$handler->correctTypeExample();
$floatValue = $handler->ensureFloat('123.45');
print_r($handler->checkType($floatValue));
?>常见应用场景
场景1:商品价格系统
场景描述:实现商品价格的存储和计算。
使用方法:
- 使用整数存储(分为单位)
- 或使用Decimal128
- 避免使用Double
示例代码:
php
<?php
// 场景说明:商品价格系统实现
class ProductPriceSystem {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 创建商品(使用整数存储价格)
public function createProduct($name, $priceYuan) {
$collection = $this->database->selectCollection('products');
// 关键行注释:将价格转换为分存储,避免浮点数精度问题
$priceCents = (int)($priceYuan * 100);
$product = [
'name' => $name,
'price_cents' => $priceCents,
'price_yuan' => $priceYuan,
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($product);
return (string)$result->getInsertedId();
}
// 计算总价
public function calculateTotal($productId, $quantity) {
$collection = $this->database->selectCollection('products');
$product = $collection->findOne(['_id' => new MongoDB\BSON\ObjectId($productId)]);
if (!$product) {
throw new Exception("商品不存在");
}
// 使用整数计算,避免精度问题
$totalCents = $product['price_cents'] * $quantity;
$totalYuan = $totalCents / 100;
return [
'product_name' => $product['name'],
'unit_price' => $product['price_yuan'],
'quantity' => $quantity,
'total_cents' => $totalCents,
'total_yuan' => $totalYuan
];
}
// 应用折扣
public function applyDiscount($productId, $discountPercent) {
$collection = $this->database->selectCollection('products');
$product = $collection->findOne(['_id' => new MongoDB\BSON\ObjectId($productId)]);
if (!$product) {
throw new Exception("商品不存在");
}
// 计算折扣价格
$originalCents = $product['price_cents'];
$discountCents = (int)($originalCents * $discountPercent / 100);
$finalCents = $originalCents - $discountCents;
$collection->updateOne(
['_id' => new MongoDB\BSON\ObjectId($productId)],
[
'$set' => [
'discounted_price_cents' => $finalCents,
'discount_percent' => $discountPercent
]
]
);
return [
'original_price' => $originalCents / 100,
'discount_percent' => $discountPercent,
'final_price' => $finalCents / 100
];
}
}
// 使用示例
$priceSystem = new ProductPriceSystem('price_db');
// 创建商品
$productId = $priceSystem->createProduct('iPhone 15', 6999.99);
echo "商品ID: $productId\n";
// 计算总价
$total = $priceSystem->calculateTotal($productId, 3);
print_r($total);
// 应用折扣
$discount = $priceSystem->applyDiscount($productId, 10);
print_r($discount);
?>场景2:评分系统
场景描述:实现产品评分和统计功能。
示例代码:
php
<?php
// 场景说明:评分系统实现
class RatingSystem {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 添加评分
public function addRating($productId, $userId, $rating) {
$collection = $this->database->selectCollection('ratings');
// 验证评分范围
if ($rating < 0.0 || $rating > 5.0) {
throw new Exception("评分必须在0-5之间");
}
$collection->insertOne([
'product_id' => $productId,
'user_id' => $userId,
'rating' => (float)$rating,
'created_at' => new MongoDB\BSON\UTCDateTime()
]);
}
// 计算平均评分
public function calculateAverageRating($productId) {
$collection = $this->database->selectCollection('ratings');
$pipeline = [
[
'$match' => ['product_id' => $productId]
],
[
'$group' => [
'_id' => '$product_id',
'average_rating' => ['$avg' => '$rating'],
'total_ratings' => ['$sum' => 1],
'rating_distribution' => [
'$push' => '$rating'
]
]
]
];
$result = $collection->aggregate($pipeline)->toArray();
if (empty($result)) {
return [
'product_id' => $productId,
'average_rating' => 0.0,
'total_ratings' => 0
];
}
$data = $result[0];
// 四舍五入到小数点后一位
$averageRating = round($data['average_rating'], 1);
return [
'product_id' => $productId,
'average_rating' => $averageRating,
'total_ratings' => $data['total_ratings'],
'distribution' => $this->calculateDistribution($data['rating_distribution'])
];
}
// 计算评分分布
private function calculateDistribution($ratings) {
$distribution = [
'5_star' => 0,
'4_star' => 0,
'3_star' => 0,
'2_star' => 0,
'1_star' => 0
];
foreach ($ratings as $rating) {
if ($rating >= 4.5) $distribution['5_star']++;
elseif ($rating >= 3.5) $distribution['4_star']++;
elseif ($rating >= 2.5) $distribution['3_star']++;
elseif ($rating >= 1.5) $distribution['2_star']++;
else $distribution['1_star']++;
}
return $distribution;
}
}
// 使用示例
$ratingSystem = new RatingSystem('rating_db');
// 添加评分
$ratingSystem->addRating('PROD001', 'USER001', 4.5);
$ratingSystem->addRating('PROD001', 'USER002', 5.0);
$ratingSystem->addRating('PROD001', 'USER003', 4.0);
// 计算平均评分
$stats = $ratingSystem->calculateAverageRating('PROD001');
print_r($stats);
?>场景3:地理坐标系统
场景描述:存储和查询地理位置信息。
示例代码:
php
<?php
// 场景说明:地理坐标系统实现
class GeoLocationSystem {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 初始化地理索引
public function initializeGeoIndex() {
$collection = $this->database->selectCollection('locations');
// 关键行注释:创建2dsphere索引支持地理空间查询
$collection->createIndex(['location' => '2dsphere']);
}
// 添加位置
public function addLocation($name, $longitude, $latitude, $description = '') {
$collection = $this->database->selectCollection('locations');
$document = [
'name' => $name,
'location' => [
'type' => 'Point',
'coordinates' => [(float)$longitude, (float)$latitude]
],
'description' => $description,
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$result = $collection->insertOne($document);
return (string)$result->getInsertedId();
}
// 查询附近位置
public function findNearby($longitude, $latitude, $maxDistance = 1000) {
$collection = $this->database->selectCollection('locations');
$query = [
'location' => [
'$near' => [
'$geometry' => [
'type' => 'Point',
'coordinates' => [(float)$longitude, (float)$latitude]
],
'$maxDistance' => $maxDistance
]
]
];
return $collection->find($query)->toArray();
}
// 查询范围内的位置
public function findWithinPolygon($polygon) {
$collection = $this->database->selectCollection('locations');
$query = [
'location' => [
'$geoWithin' => [
'$polygon' => $polygon
]
]
];
return $collection->find($query)->toArray();
}
// 计算两点距离
public function calculateDistance($lon1, $lat1, $lon2, $lat2) {
// 使用Haversine公式计算球面距离
$earthRadius = 6371; // 地球半径(公里)
$dLat = deg2rad($lat2 - $lat1);
$dLon = deg2rad($lon2 - $lon1);
$a = sin($dLat / 2) * sin($dLat / 2) +
cos(deg2rad($lat1)) * cos(deg2rad($lat2)) *
sin($dLon / 2) * sin($dLon / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$distance = $earthRadius * $c;
return round($distance, 2);
}
}
// 使用示例
$geoSystem = new GeoLocationSystem('geo_db');
$geoSystem->initializeGeoIndex();
// 添加位置
$geoSystem->addLocation('北京天安门', 116.397128, 39.916527, '中国北京');
$geoSystem->addLocation('北京故宫', 116.397026, 39.918058, '中国北京');
$geoSystem->addLocation('北京王府井', 116.410889, 39.915599, '中国北京');
// 查询附近位置
$nearby = $geoSystem->findNearby(116.397128, 39.916527, 2000);
echo "附近位置数量: " . count($nearby) . "\n";
// 计算距离
$distance = $geoSystem->calculateDistance(
116.397128, 39.916527, // 天安门
116.397026, 39.918058 // 故宫
);
echo "天安门到故宫距离: {$distance} 公里\n";
?>企业级进阶应用场景
场景1:科学数据计算
场景描述:处理科学实验数据和统计分析。
示例代码:
php
<?php
// 场景说明:科学数据计算系统
class ScientificDataSystem {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 存储实验数据
public function storeExperimentData($experimentId, $measurements) {
$collection = $this->database->selectCollection('experiments');
$document = [
'experiment_id' => $experimentId,
'measurements' => $measurements,
'statistics' => $this->calculateStatistics($measurements),
'created_at' => new MongoDB\BSON\UTCDateTime()
];
$collection->insertOne($document);
}
// 计算统计信息
private function calculateStatistics($data) {
$count = count($data);
$sum = array_sum($data);
$mean = $sum / $count;
// 计算标准差
$variance = 0.0;
foreach ($data as $value) {
$variance += pow($value - $mean, 2);
}
$stdDev = sqrt($variance / $count);
// 排序计算中位数
$sorted = $data;
sort($sorted);
$median = $count % 2 === 0 ?
($sorted[$count / 2 - 1] + $sorted[$count / 2]) / 2 :
$sorted[floor($count / 2)];
return [
'count' => $count,
'sum' => $sum,
'mean' => round($mean, 6),
'median' => round($median, 6),
'std_dev' => round($stdDev, 6),
'min' => min($data),
'max' => max($data)
];
}
// 聚合分析
public function aggregateAnalysis($experimentIds) {
$collection = $this->database->selectCollection('experiments');
$pipeline = [
[
'$match' => ['experiment_id' => ['$in' => $experimentIds]]
],
[
'$group' => [
'_id' => null,
'avg_mean' => ['$avg' => '$statistics.mean'],
'avg_std_dev' => ['$avg' => '$statistics.std_dev'],
'total_measurements' => ['$sum' => '$statistics.count']
]
]
];
return $collection->aggregate($pipeline)->toArray();
}
}
// 使用示例
$scienceSystem = new ScientificDataSystem('science_db');
// 存储实验数据
$measurements = [1.234, 1.235, 1.233, 1.236, 1.234, 1.235, 1.237, 1.234, 1.235, 1.236];
$scienceSystem->storeExperimentData('EXP001', $measurements);
// 聚合分析
$analysis = $scienceSystem->aggregateAnalysis(['EXP001']);
print_r($analysis);
?>场景2:传感器数据监控
场景描述:实时监控传感器数据。
示例代码:
php
<?php
// 场景说明:传感器数据监控系统
class SensorMonitoringSystem {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 记录传感器数据
public function recordSensorData($sensorId, $value, $unit) {
$collection = $this->database->selectCollection('sensor_data');
$document = [
'sensor_id' => $sensorId,
'value' => (float)$value,
'unit' => $unit,
'timestamp' => new MongoDB\BSON\UTCDateTime()
];
$collection->insertOne($document);
}
// 查询时间范围内的数据
public function queryTimeRange($sensorId, $startTime, $endTime) {
$collection = $this->database->selectCollection('sensor_data');
return $collection->find([
'sensor_id' => $sensorId,
'timestamp' => [
'$gte' => $startTime,
'$lte' => $endTime
]
], [
'sort' => ['timestamp' => 1]
])->toArray();
}
// 计算移动平均
public function calculateMovingAverage($sensorId, $windowSize = 10) {
$collection = $this->database->selectCollection('sensor_data');
$data = $collection->find([
'sensor_id' => $sensorId
], [
'sort' => ['timestamp' => -1],
'limit' => $windowSize
])->toArray();
if (count($data) < $windowSize) {
return null;
}
$sum = 0.0;
foreach ($data as $doc) {
$sum += $doc['value'];
}
return $sum / $windowSize;
}
// 异常检测
public function detectAnomalies($sensorId, $threshold) {
$collection = $this->database->selectCollection('sensor_data');
return $collection->find([
'sensor_id' => $sensorId,
'$or' => [
['value' => ['$gt' => $threshold['max']]],
['value' => ['$lt' => $threshold['min']]]
]
])->toArray();
}
}
// 使用示例
$sensorSystem = new SensorMonitoringSystem('sensor_db');
// 记录传感器数据
for ($i = 0; $i < 20; $i++) {
$temperature = 20.0 + rand(-50, 50) / 10.0;
$sensorSystem->recordSensorData('TEMP001', $temperature, '°C');
}
// 计算移动平均
$movingAvg = $sensorSystem->calculateMovingAverage('TEMP001', 10);
echo "移动平均温度: {$movingAvg}°C\n";
// 异常检测
$anomalies = $sensorSystem->detectAnomalies('TEMP001', ['min' => 15.0, 'max' => 25.0]);
echo "异常数据数量: " . count($anomalies) . "\n";
?>行业最佳实践
实践1:选择合适的数据类型
实践内容:
- 货币计算使用Decimal128或整数
- 科学计算使用Double
- 地理坐标使用Double
推荐理由:
- 避免精度问题
- 提高计算准确性
- 优化存储空间
php
<?php
// 好的实践:根据场景选择数据类型
class DataTypeSelector {
public static function selectForMoney($amount) {
// 使用Decimal128处理货币
return new MongoDB\BSON\Decimal128((string)$amount);
}
public static function selectForScience($value) {
// 使用Double处理科学数据
return (float)$value;
}
public static function selectForGeo($coordinate) {
// 使用Double处理地理坐标
return (float)$coordinate;
}
}
?>实践2:使用epsilon比较
实践内容:
- 不直接比较浮点数
- 使用epsilon进行范围比较
- 根据场景选择合适的epsilon
推荐理由:
- 避免精度问题
- 提高比较准确性
- 减少错误
php
<?php
// 好的实践:使用epsilon比较
class FloatComparator {
const DEFAULT_EPSILON = 1e-10;
public static function equal($a, $b, $epsilon = self::DEFAULT_EPSILON) {
return abs($a - $b) < $epsilon;
}
public static function greaterThan($a, $b, $epsilon = self::DEFAULT_EPSILON) {
return $a - $b > $epsilon;
}
public static function lessThan($a, $b, $epsilon = self::DEFAULT_EPSILON) {
return $b - $a > $epsilon;
}
}
// 使用示例
$a = 0.1 + 0.2;
$b = 0.3;
if (FloatComparator::equal($a, $b)) {
echo "相等\n";
}
?>实践3:创建合适的索引
实践内容:
- 为常用查询字段创建索引
- 使用复合索引优化多字段查询
- 为地理坐标创建2dsphere索引
推荐理由:
- 提高查询性能
- 减少资源消耗
- 优化用户体验
php
<?php
// 好的实践:创建索引
$collection->createIndex(['price' => 1]);
$collection->createIndex(['rating' => -1]);
$collection->createIndex(['location' => '2dsphere']);
?>常见问题答疑(FAQ)
问题1:为什么0.1 + 0.2不等于0.3?
问题描述:在编程中,0.1 + 0.2的结果不等于0.3,这是为什么?
回答内容:
这是由于浮点数的二进制表示限制导致的:
php
<?php
// 场景说明:解释浮点数精度问题
$a = 0.1;
$b = 0.2;
$sum = $a + $b;
echo "0.1 + 0.2 = " . $sum . "\n";
echo "期望结果: 0.3\n";
echo "实际结果: $sum\n";
echo "差值: " . ($sum - 0.3) . "\n";
// 原因:二进制表示
echo "\n二进制表示:\n";
echo "0.1: " . decbin(unpack('Q', pack('d', 0.1))[1]) . "\n";
echo "0.2: " . decbin(unpack('Q', pack('d', 0.2))[1]) . "\n";
echo "0.3: " . decbin(unpack('Q', pack('d', 0.3))[1]) . "\n";
// 解决方案:使用epsilon比较
$epsilon = 1e-10;
if (abs($sum - 0.3) < $epsilon) {
echo "\n使用epsilon比较: 相等\n";
}
// 或使用Decimal128
$precise = bcadd('0.1', '0.2', 2);
echo "使用Decimal128: 0.1 + 0.2 = $precise\n";
?>问题2:如何正确处理货币计算?
问题描述:在电商系统中如何正确处理价格计算?
回答内容:
使用整数或Decimal128处理货币:
php
<?php
// 场景说明:货币计算的正确方法
class MoneyCalculator {
// 方法1:使用整数(分为单位)
public static function calculateWithInt($priceYuan, $quantity) {
$priceCents = (int)($priceYuan * 100);
$totalCents = $priceCents * $quantity;
return [
'total_cents' => $totalCents,
'total_yuan' => $totalCents / 100
];
}
// 方法2:使用Decimal128
public static function calculateWithDecimal($price, $quantity) {
$total = bcmul((string)$price, (string)$quantity, 2);
return new MongoDB\BSON\Decimal128($total);
}
// 方法3:使用bcmath函数
public static function calculateWithBcmath($price, $quantity) {
return bcmul($price, $quantity, 2);
}
}
// 使用示例
$price = '19.99';
$quantity = 3;
$result1 = MoneyCalculator::calculateWithInt(19.99, 3);
echo "整数方法: {$result1['total_yuan']} 元\n";
$result2 = MoneyCalculator::calculateWithDecimal($price, $quantity);
echo "Decimal128方法: {$result2} 元\n";
$result3 = MoneyCalculator::calculateWithBcmath($price, $quantity);
echo "bcmath方法: {$result3} 元\n";
?>问题3:如何处理特殊值(NaN、Infinity)?
问题描述:如何正确处理浮点数的特殊值?
回答内容:
正确识别和处理特殊值:
php
<?php
// 场景说明:特殊值处理
class SpecialValueProcessor {
// 检查并分类值
public static function classify($value) {
if (is_nan($value)) {
return 'NaN';
}
if (is_infinite($value)) {
return $value > 0 ? 'PositiveInfinity' : 'NegativeInfinity';
}
if ($value === 0.0) {
return 'Zero';
}
return 'Normal';
}
// 安全的数学运算
public static function safeDivide($a, $b) {
if ($b == 0) {
if ($a > 0) return INF;
if ($a < 0) return -INF;
return NAN;
}
return $a / $b;
}
// 过滤特殊值
public static function filterSpecialValues($array) {
return array_filter($array, function($value) {
return is_finite($value);
});
}
}
// 使用示例
$values = [1.5, NAN, INF, -INF, 0.0, 2.5];
foreach ($values as $value) {
echo "值: $value, 类型: " . SpecialValueProcessor::classify($value) . "\n";
}
$filtered = SpecialValueProcessor::filterSpecialValues($values);
echo "过滤后的值: " . implode(', ', $filtered) . "\n";
?>实战练习
练习1:基础练习 - 温度转换
解题思路:
- 存储温度数据
- 实现温度转换
- 计算统计信息
常见误区:
- 未处理精度问题
- 未验证输入范围
- 未处理特殊值
参考代码:
php
<?php
// 练习1:温度转换系统
class TemperatureConverter {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 摄氏度转华氏度
public function celsiusToFahrenheit($celsius) {
return $celsius * 9 / 5 + 32;
}
// 华氏度转摄氏度
public function fahrenheitToCelsius($fahrenheit) {
return ($fahrenheit - 32) * 5 / 9;
}
// 存储温度数据
public function storeTemperature($location, $celsius) {
$collection = $this->database->selectCollection('temperatures');
$fahrenheit = $this->celsiusToFahrenheit($celsius);
$collection->insertOne([
'location' => $location,
'celsius' => (float)$celsius,
'fahrenheit' => (float)$fahrenheit,
'timestamp' => new MongoDB\BSON\UTCDateTime()
]);
}
// 查询温度统计
public function getStatistics($location) {
$collection = $this->database->selectCollection('temperatures');
$pipeline = [
['$match' => ['location' => $location]],
[
'$group' => [
'_id' => '$location',
'avg_celsius' => ['$avg' => '$celsius'],
'max_celsius' => ['$max' => '$celsius'],
'min_celsius' => ['$min' => '$celsius']
]
]
];
return $collection->aggregate($pipeline)->toArray();
}
}
// 运行练习
$converter = new TemperatureConverter('temp_db');
// 存储温度
$converter->storeTemperature('Beijing', 25.5);
$converter->storeTemperature('Beijing', 26.3);
$converter->storeTemperature('Beijing', 24.8);
// 查询统计
$stats = $converter->getStatistics('Beijing');
print_r($stats);
?>练习2:进阶练习 - 商品折扣计算
解题思路:
- 设计价格存储结构
- 实现折扣计算
- 处理精度问题
参考代码:
php
<?php
// 练习2:商品折扣计算
class DiscountCalculator {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 应用百分比折扣
public function applyPercentageDiscount($productId, $percentage) {
$collection = $this->database->selectCollection('products');
$product = $collection->findOne(['_id' => new MongoDB\BSON\ObjectId($productId)]);
$originalPrice = $product['price_cents'];
$discountAmount = (int)($originalPrice * $percentage / 100);
$finalPrice = $originalPrice - $discountAmount;
$collection->updateOne(
['_id' => new MongoDB\BSON\ObjectId($productId)],
[
'$set' => [
'discounted_price_cents' => $finalPrice,
'discount_percentage' => $percentage
]
]
);
return [
'original_price' => $originalPrice / 100,
'discount_percentage' => $percentage,
'final_price' => $finalPrice / 100,
'saved_amount' => $discountAmount / 100
];
}
}
// 运行练习
$calculator = new DiscountCalculator('shop_db');
// 创建商品
$collection = $calculator->database->selectCollection('products');
$result = $collection->insertOne([
'name' => 'iPhone 15',
'price_cents' => 699999
]);
$productId = (string)$result->getInsertedId();
// 应用折扣
$discount = $calculator->applyPercentageDiscount($productId, 15);
print_r($discount);
?>练习3:挑战练习 - 科学数据分析
解题思路:
- 存储实验数据
- 计算统计指标
- 实现异常检测
参考代码:
php
<?php
// 练习3:科学数据分析
class DataAnalyzer {
private $database;
public function __construct($databaseName) {
$client = new MongoDB\Client("mongodb://localhost:27017");
$this->database = $client->selectDatabase($databaseName);
}
// 分析数据
public function analyze($data) {
$count = count($data);
$mean = array_sum($data) / $count;
$variance = 0.0;
foreach ($data as $value) {
$variance += pow($value - $mean, 2);
}
$stdDev = sqrt($variance / $count);
return [
'count' => $count,
'mean' => round($mean, 6),
'std_dev' => round($stdDev, 6),
'variance' => round($variance / $count, 6)
];
}
// 异常检测(使用3σ原则)
public function detectOutliers($data) {
$stats = $this->analyze($data);
$mean = $stats['mean'];
$stdDev = $stats['std_dev'];
$outliers = [];
foreach ($data as $value) {
$zScore = abs($value - $mean) / $stdDev;
if ($zScore > 3) {
$outliers[] = $value;
}
}
return $outliers;
}
}
// 运行练习
$analyzer = new DataAnalyzer('analysis_db');
$data = [1.2, 1.3, 1.4, 1.3, 1.5, 1.2, 1.4, 10.0]; // 10.0是异常值
$stats = $analyzer->analyze($data);
print_r($stats);
$outliers = $analyzer->detectOutliers($data);
echo "异常值: " . implode(', ', $outliers) . "\n";
?>知识点总结
核心要点
Double类型特性
- IEEE 754标准64位浮点数
- 约15-17位有效数字
- 支持特殊值:NaN、Infinity
精度问题
- 二进制浮点数表示限制
- 0.1 + 0.2 ≠ 0.3
- 累加误差放大
存储机制
- 8字节存储
- 符号位 + 指数位 + 尾数位
- BSON格式存储
查询优化
- 支持精确和范围查询
- 创建索引提高性能
- 使用epsilon比较
易错点回顾
精度丢失
- 直接比较浮点数
- 货币计算使用Double
- 未考虑精度误差
特殊值处理
- 未检查NaN和Infinity
- 缺少异常处理
- 查询特殊值失败
类型混淆
- 整数和浮点数混用
- 未进行类型转换
- 查询类型不匹配
拓展参考资料
官方文档链接
- MongoDB Double类型: https://docs.mongodb.com/manual/reference/bson-types/#numeric
- IEEE 754标准: https://en.wikipedia.org/wiki/IEEE_754
- 数值类型比较: https://docs.mongodb.com/manual/core/document/#numeric-types
进阶学习路径建议
本知识点承接:《Integer类型》→《数值类型概述》
后续延伸至:《Decimal128类型》→《数值运算优化》→《科学计算应用》
建议学习顺序:
- Integer类型
- Double类型(本章节)
- Decimal128类型
- 数值运算优化
- 科学计算应用
