Чтобы предложить лучшее решение, я могу сказать, что обнаружил следующий метод:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
Позвольте мне объяснить, почему это лучшее решение. Как упоминалось выше в других ответах, рекомендуется использовать готовые для использования функции Javascript toFixed () для решения проблемы. Но, скорее всего, вы столкнетесь с некоторыми проблемами.
Представьте, что вы собираетесь добавить два числа с плавающей запятой, такие как 0.2
и 0.7
, вот оно: 0.2 + 0.7 = 0.8999999999999999
.
Ваш ожидаемый результат 0.9
означает, что в этом случае вам нужен результат с точностью до 1 цифры. Поэтому вы должны были использовать (0.2 + 0.7).tofixed(1)
, но вы не можете просто указать определенный параметр toFixed (), поскольку он зависит от заданного числа, например
`0.22 + 0.7 = 0.9199999999999999`
. В этом примере вам нужна точность в 2 цифры так что это должно быть toFixed(2)
, так что должно быть параметром для каждого заданного числа с плавающей запятой?
Вы могли бы сказать, что пусть это будет 10 в каждой ситуации:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
Черт! Что вы собираетесь делать с этими нежелательными нулями после 9? Пришло время преобразовать его в float, чтобы сделать его по вашему желанию:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Теперь, когда вы нашли решение, лучше предложить его как функцию:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Вы можете использовать его следующим образом:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
Чтобы ответить на ваш вопрос, да, это самый эффективный способ. Но я думаю, что нам нужно выяснить, почему это так.
Как было предложено в альтернативах, люди, на которые люди смотрят, «сортируют» ваши результаты, прежде чем перейти на стадию $group
и что они смотрят на значение «timestamp», поэтому вы хотите убедиться, что все находится в порядке «timestamp», поэтому отсюда:
db.temperature.aggregate([
{ "$sort": { "station": 1, "dt": -1 } },
{ "$group": {
"_id": "$station",
"result": { "$first":"$dt"}, "t": {"$first":"$t"}
}}
])
И, как указано, вы, конечно, захотите индекс, чтобы отразить это, чтобы сделать сортировку эффективной:
Однако, и это реальная точка. То, что, по-видимому, было упущено другими (если не так для себя), состоит в том, что все эти данные, вероятно, будут вставлены уже в порядке времени, поскольку каждое чтение записывается как добавленное.
Таким образом, красота этого это поле _id
(с по умолчанию ObjectId
) уже находится в порядке «timestamp», поскольку оно фактически содержит значение времени, и это делает возможным утверждение:
db.temperature.aggregate([
{ "$group": {
"_id": "$station",
"result": { "$last":"$dt"}, "t": {"$last":"$t"}
}}
])
И это быстрее. Зачем? Ну, вам не нужно выбирать индекс (дополнительный код для вызова), вам также не нужно «загружать» индекс в дополнение к документу.
Мы уже знаем, что документы в порядке ( на _id
), поэтому границы $last
совершенно верны. Вы все равно сканируете все, и вы также можете «задавать» запрос по значениям _id
одинаково допустимым для двух дат.
Единственное, что можно сказать здесь, это то, что в «реальном мире», использование, это может быть более практичным для вас $match
между диапазонами дат при выполнении такого рода накопления в отличие от получения значений «первый» и «последний» _id
для определения «диапазона» или чего-то подобного в ваше фактическое использование.
Итак, где доказательство этого? Ну, это довольно легко воспроизвести, поэтому я просто сделал это, создав некоторые примеры данных:
var stations = [
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "FL",
"GA", "HI", "ID", "IL", "IN", "IA", "KS", "KY", "LA",
"ME", "MD", "MA", "MI", "MN", "MS", "MO", "MT", "NE",
"NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK",
"OR", "PA", "RI", "SC", "SD", "TN", "TX", "UT", "VT",
"VA", "WA", "WV", "WI", "WY"
];
for ( i=0; i<200000; i++ ) {
var station = stations[Math.floor(Math.random()*stations.length)];
var t = Math.floor(Math.random() * ( 96 - 50 + 1 )) +50;
dt = new Date();
db.temperatures.insert({
station: station,
t: t,
dt: dt
});
}
На моем оборудовании (ноутбук 8 ГБ со спиннивным диском, который не является звездным, но, безусловно, адекватным) форма заявления четко показывает заметную паузу с версией, используя индекс и сортировку (те же ключи в индексе, что и оператор сортировки). Это лишь незначительная пауза, но разница достаточно значительна, чтобы заметить.
Даже глядя на вывод объяснения (версия 2.6 и выше, или на самом деле есть в 2.4.9, хотя и не задокументирована), вы можете видеть разница в этом, хотя $sort
оптимизирована из-за наличия индекса, время, которое, как представляется, относится к выбору индекса, а затем загружает индексированные записи. Включение всех полей для «охватываемых» запросов индекса не имеет значения.
Также для записи чисто индексация даты и только сортировка по значениям даты дают тот же результат. Возможно, немного быстрее, но все же медленнее, чем форма естественного индекса без сортировки.
Так что до тех пор, пока вы можете с радостью «диапазоном» на сначала и последним _id
, то верно, что использование естественного индекса в порядке вставки фактически является наиболее эффективным способом сделать это. Ваш реальный пробег в мире может варьироваться в зависимости от того, насколько это практично для вас или нет, и это может оказаться более удобным для реализации индекса и сортировки по дате.
Но если вы были довольны использованием _id
или больше, чем «последний» _id
в вашем запросе, а затем, возможно, одна настройка, чтобы получить значения вместе с вашими результатами, чтобы вы могли хранить и использовать эту информацию в следующих запросах:
db.temperature.aggregate([
// Get documents "greater than" the "highest" _id value found last time
{ "$match": {
"_id": { "$gt": ObjectId("536076603e70a99790b7845d") }
}},
// Do the grouping with addition of the returned field
{ "$group": {
"_id": "$station",
"result": { "$last":"$dt"},
"t": {"$last":"$t"},
"lastDoc": { "$last": "$_id" }
}}
])
И если вы на самом деле «следили» за такими результатами, вы можете определить максимальное значение ObjectId
из ваших результатов и использовать его в следующем запросе.
Во всяком случае, получайте удовольствие играя с этим, но опять же Да, в этом случае этот запрос является самым быстрым способом.
Индекс - это все, что вам действительно нужно:
db.temperature.ensureIndex({ 'station': 1, 'dt': 1 })
for s in db.temperature.distinct('station'):
db.temperature.find({ station: s }).sort({ dt : -1 }).limit(1)
, конечно, используя любой синтаксис, действительно действительный для вашего языка.
Edit: Вы правы, что такой цикл берет на себя поездку на одну поездку, и это отлично подходит для нескольких станций, и не очень хорошо для 1000. Однако вы все же хотите использовать составной индекс на станции + dt и использовать нисходящую сортировку:
db.temperature.aggregate([
{ $sort: { station: 1, dt: -1 } },
{ $group: { _id: "$station", result: {$first:"$dt"}, t: {$first:"$t"} } }
])
Что касается запроса агрегирования, который вы опубликовали, я бы удостоверился, что у вас есть индекс на dt:
db.temperature.ensureIndex({'dt': 1 })
. Это будет гарантировать, что сортировка $ в начале агрегатный трубопровод максимально эффективен.
Что касается того, является ли это наиболее эффективным способом получения этих данных, а также запросом в цикле, скорее всего, будет функция того, сколько у вас данных. В начале, с «тысячами станций» и, возможно, сотнями тысяч точек данных, я думаю, что метод агрегирования будет быстрее.
Однако, поскольку вы добавляете все больше и больше данных, проблема заключается в том, что запрос агрегации будет по-прежнему касаться всех документов. Это будет становиться все более дорогостоящим, поскольку вы масштабируетесь до миллионов или более документов. Одним из подходов для этого случая было бы добавить $ limit сразу после сортировки $, чтобы ограничить общее количество рассматриваемых документов. Это немного взломанно и неточно, но это поможет ограничить общее количество документов, к которым необходимо получить доступ.