mongo/jstests/aggregation/expressions/unary_numeric.js

318 lines
9.5 KiB
JavaScript

// Tests the behavior of $ceil, $floor, $exp, $log10, $ln and $sqrt when used in agg expressions.
(function() {
"use strict";
load("jstests/aggregation/extras/utils.js"); // For assertErrorCode.
load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs.
const coll = db.unary_numeric;
coll.drop();
// Testing behavior for common cases.
assert.commandWorked(coll.insert([
{_id: 0, x: null},
{_id: 1, x: undefined},
{_id: 2},
{_id: 3, x: 1},
{_id: 4, x: 10},
{_id: 5, x: 100},
{_id: 6, x: 2.3},
{_id: 7, x: 2.6},
{_id: 8, x: NumberDecimal("40")},
{_id: 9, x: NumberLong(4)},
{_id: 10, x: NumberLong("9223372036854775807")}, // LLONG_MAX
]));
let results = coll.aggregate([
{
$project: {
ceil: {$ceil: "$x"},
exp: {$exp: "$x"},
floor: {$floor: "$x"},
ln: {$ln: "$x"},
log10: {$log10: "$x"},
sqrt: {$sqrt: "$x"},
abs: {$abs: "$x"},
}
},
{$sort: {_id: 1}}
])
.toArray();
let expectedResults = [
{
_id: 0,
"ceil": null,
"exp": null,
"floor": null,
"ln": null,
"log10": null,
"sqrt": null,
"abs": null
},
{
_id: 1,
"ceil": null,
"exp": null,
"floor": null,
"ln": null,
"log10": null,
"sqrt": null,
"abs": null
},
{
_id: 2,
"ceil": null,
"exp": null,
"floor": null,
"ln": null,
"log10": null,
"sqrt": null,
"abs": null
},
{
_id: 3,
"ceil": 1,
"exp": 2.718281828459045,
"floor": 1,
"ln": 0,
"log10": 0,
"sqrt": 1,
"abs": 1
},
{
_id: 4,
"ceil": 10,
"exp": 22026.465794806718,
"floor": 10,
"ln": 2.302585092994046,
"log10": 1,
"sqrt": 3.1622776601683795,
"abs": 10
},
{
_id: 5,
"ceil": 100,
"exp": 2.6881171418161356e+43,
"floor": 100,
"ln": 4.605170185988092,
"log10": 2,
"sqrt": 10,
"abs": 100
},
{
_id: 6,
"ceil": 3,
"exp": 9.974182454814718,
"floor": 2,
"ln": 0.8329091229351039,
"log10": 0.36172783601759284,
"sqrt": 1.51657508881031,
"abs": 2.3
},
{
_id: 7,
"ceil": 3,
"exp": 13.463738035001692,
"floor": 2,
"ln": 0.9555114450274363,
"log10": 0.414973347970818,
"sqrt": 1.61245154965971,
"abs": 2.6
},
{
// Note that we do not test exp, ln, log10, and sqrt here, because they provide inexact
// results, and there is no simple way to do an approximate comparison of two NumberDecimal
// objects. The unit tests for these operators should give us enough coverage already.
_id: 8,
"ceil": NumberDecimal("40"),
"floor": NumberDecimal("40"),
"abs": NumberDecimal("40")
},
{
_id: 9,
"ceil": NumberLong(4),
"exp": 54.598150033144236,
"floor": NumberLong(4),
"ln": 1.3862943611198906,
"log10": 0.6020599913279624,
"sqrt": 2,
"abs": NumberLong(4)
},
{
_id: 10,
"ceil": NumberLong("9223372036854775807"),
"exp": Infinity,
"floor": NumberLong("9223372036854775807"),
"ln": 43.66827237527655,
"log10": 18.964889726830815,
"sqrt": 3037000499.97605,
"abs": NumberLong("9223372036854775807")
},
];
// Compare each document in the 'results' array with each document in the 'expectedResults' array,
// using an approximate equality comparison for numbers. Many of the operators tested may vary in
// their last few bits depending on platform.
for (const resultDoc of results) {
const expectedDoc = expectedResults.find(doc => (resultDoc._id === doc._id));
assert(expectedDoc);
for (const [key, expectedValue] of Object.entries(expectedDoc)) {
assert(resultDoc.hasOwnProperty(key));
const resultValue = resultDoc[key];
let matches = false;
if (((typeof expectedValue) == "object") && ((typeof resultValue) == "object")) {
// NumberDecimal case.
matches = (bsonWoCompare({value: expectedValue}, {value: resultValue}) === 0);
} else if (isFinite(expectedValue) && isFinite(resultValue)) {
// Regular numbers; do an approximate comparison, expecting 48 bits of precision.
const epsilon = Math.pow(2, -48);
const delta = Math.abs(expectedValue - resultValue);
matches = (delta === 0) ||
((delta /
Math.min(Math.abs(expectedValue) + Math.abs(resultValue), Number.MAX_VALUE)) <
epsilon);
} else {
matches = (expectedValue === resultValue);
}
assert(matches,
`Mismatched ${key} field in document with _id ${resultDoc._id} -- Expected: ${
expectedValue}, Actual: ${resultValue}`);
}
}
// Testing behavior for special cases: negative numbers, 0, NaN & Infinity on expressions that don't
// error.
assert(coll.drop());
assert.commandWorked(coll.insert([
{_id: 0, x: 0},
{_id: 1, x: NaN},
{_id: 2, x: Infinity},
{_id: 3, x: -Infinity},
{_id: 4, x: -2.6}
]));
results =
coll.aggregate([
{$project: {ceil: {$ceil: "$x"}, floor: {$floor: "$x"}, exp: {$ceil: {$exp: "$x"}}}},
{$sort: {_id: 1}}
])
.toArray();
assert.eq(results, [
{
_id: 0,
"ceil": 0,
"floor": 0,
"exp": 1,
},
{
_id: 1,
"ceil": NaN,
"floor": NaN,
"exp": NaN,
},
{
_id: 2,
"ceil": Infinity,
"floor": Infinity,
"exp": Infinity,
},
{
_id: 3,
"ceil": -Infinity,
"floor": -Infinity,
"exp": 0,
},
{
_id: 4,
"ceil": -2,
"floor": -3,
"exp": 1,
}
]);
// Testing $sqrt, $ln & $log10 success with NaN:
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: NaN}, {_id: 1, x: NumberDecimal("NaN")}]));
results = coll.aggregate([
{$project: {ln: {$ln: "$x"}, log10: {$log10: "$x"}, sqrt: {$sqrt: "$x"}}},
{$sort: {_id: 1}}
])
.toArray();
assert.eq(results, [
{_id: 0, "ln": NaN, "log10": NaN, "sqrt": NaN},
{_id: 1, "ln": NaN, "log10": NaN, "sqrt": NumberDecimal("NaN")}
]);
// Testing error codes.
// $ln, $log10 fail for 0 and negative numbers.
// $sqrt fails for negative numbers.
// Testing failures with 0:
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: 0}]));
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28766);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28761);
// Testing $sqrt success with 0:
results = coll.aggregate([{$project: {a: {$sqrt: "$x"}}}]).toArray();
assert.eq(results, [{_id: 0, a: 0}]);
// Testing failures with NumberLong(0):
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: NumberLong(0)}]));
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28766);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28761);
// Testing $sqrt success with NumberLong(0):
results = coll.aggregate([{$project: {a: {$sqrt: "$x"}}}]).toArray();
assert.eq(results, [{_id: 0, a: 0}]);
// Testing failures with NumberDecimal(0):
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: NumberDecimal("0")}]));
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28766);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28761);
// Testing $sqrt success with NumberDecimal(0):
results = coll.aggregate([{$project: {a: {$sqrt: "$x"}}}]).toArray();
assert.eq(results, [{_id: 0, a: NumberDecimal("0")}]);
// Testing failures with negative numbers (all types):
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: -1}]));
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28766);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28761);
assertErrorCode(coll, [{$project: {a: {$sqrt: "$x"}}}], 28714);
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: NumberLong(-1)}]));
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28766);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28761);
assertErrorCode(coll, [{$project: {a: {$sqrt: "$x"}}}], 28714);
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: NumberDecimal("-1")}]));
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28766);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28761);
assertErrorCode(coll, [{$project: {a: {$sqrt: "$x"}}}], 28714);
// All unary numeric agg operators fail for strings:
assert(coll.drop());
assert.commandWorked(coll.insert([{_id: 0, x: "string"}]));
assertErrorCode(coll, [{$project: {a: {$abs: "$x"}}}], 28765);
assertErrorCode(coll, [{$project: {a: {$ceil: "$x"}}}], 28765);
assertErrorCode(coll, [{$project: {a: {$floor: "$x"}}}], 28765);
assertErrorCode(coll, [{$project: {a: {$exp: "$x"}}}], 28765);
assertErrorCode(coll, [{$project: {a: {$log10: "$x"}}}], 28765);
assertErrorCode(coll, [{$project: {a: {$ln: "$x"}}}], 28765);
assertErrorCode(coll, [{$project: {a: {$sqrt: "$x"}}}], 28765);
}());