Ссылка Мацери на окончательный ресурс по арифметике с плавающей запятой действительно является окончательным ответом на этот вопрос. Однако для завершения:
octave:34> fprintf("%.80f\n%.80f\n", 0.95, 1 - 0.05)
0.94999999999999995559107901499373838305473327636718750000000000000000000000000000
0.94999999999999995559107901499373838305473327636718750000000000000000000000000000
octave:35> fprintf("%.80f\n%.80f\n", 0.05, 1 - 0.95)
0.05000000000000000277555756156289135105907917022705078125000000000000000000000000
0.05000000000000004440892098500626161694526672363281250000000000000000000000000000
Другими словами, 0,95 труднее точно представить в плавающей запятой, поэтому любой расчет на первом шаге, который включает 0,95 (либо как вход, либо как выход), обязательно менее точный, чем тот, который использует только 0,05.
Поэтому:
1 - 0.05 = 0.95 (imprecise, due to intrinsic floating-point representation)
(1 - 0.05) - 0.95 = exactly 0 (since both are represented identically imprecisely)
vs
1 - 0.95 = imprecise 0.05 (due to involvement of 0.95 in calculation)
(imprecise 0.05) - (precise 0.05) = not exactly 0 (due to difference in precisions)
ОДНАКО . Следует отметить, что эта разница в точности намного ниже допуска машины (как указано в eps
- 2.2204e-16 на моей машине). Следовательно, для всех практических применений 4.1633e-17 равно 0. Если практическим моментом здесь является проверка, является ли результат вычисления эффективным 0, то с практической точки зрения всегда нужно учитывать точность машины при работе с вычислениями с плавающей запятой или, предпочтительно, найти способ переформулировать вашу проблему так, чтобы она полностью исключала необходимость проверки на равенство.
Я настроил бы представление и связанную основанную на таблице функцию на основе CTE. Мое обоснование для этого состоит в том, что, в то время как Вы могли реализовать логику на стороне приложения, это включит отправку промежуточных данных по проводу для вычисления в приложении. Используя разработчика DBML, представление переводит в объект Таблицы. Можно затем связать функцию с объектом Таблицы и вызвать метод, созданный на DataContext для получения объектов типа, определенного представлением. Используя основанную на таблице функцию позволяет механизму запроса принимать Ваши параметры во внимание при построении набора результатов вместо того, чтобы применить условие на набор результатов, определенный представлением после факта.
CREATE TABLE [dbo].[hierarchical_table](
[id] [int] IDENTITY(1,1) NOT NULL,
[parent_id] [int] NULL,
[data] [varchar](255) NOT NULL,
CONSTRAINT [PK_hierarchical_table] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE VIEW [dbo].[vw_recursive_view]
AS
WITH hierarchy_cte(id, parent_id, data, lvl) AS
(SELECT id, parent_id, data, 0 AS lvl
FROM dbo.hierarchical_table
WHERE (parent_id IS NULL)
UNION ALL
SELECT t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
FROM dbo.hierarchical_table AS t1 INNER JOIN
hierarchy_cte AS h ON t1.parent_id = h.id)
SELECT id, parent_id, data, lvl
FROM hierarchy_cte AS result
CREATE FUNCTION [dbo].[fn_tree_for_parent]
(
@parent int
)
RETURNS
@result TABLE
(
id int not null,
parent_id int,
data varchar(255) not null,
lvl int not null
)
AS
BEGIN
WITH hierarchy_cte(id, parent_id, data, lvl) AS
(SELECT id, parent_id, data, 0 AS lvl
FROM dbo.hierarchical_table
WHERE (id = @parent OR (parent_id IS NULL AND @parent IS NULL))
UNION ALL
SELECT t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
FROM dbo.hierarchical_table AS t1 INNER JOIN
hierarchy_cte AS h ON t1.parent_id = h.id)
INSERT INTO @result
SELECT id, parent_id, data, lvl
FROM hierarchy_cte AS result
RETURN
END
ALTER TABLE [dbo].[hierarchical_table] WITH CHECK ADD CONSTRAINT [FK_hierarchical_table_hierarchical_table] FOREIGN KEY([parent_id])
REFERENCES [dbo].[hierarchical_table] ([id])
ALTER TABLE [dbo].[hierarchical_table] CHECK CONSTRAINT [FK_hierarchical_table_hierarchical_table]
Для использования его, Вы сделали бы что-то как - принимающий некоторую разумную схему именования:
using (DataContext dc = new HierarchicalDataContext())
{
HierarchicalTableEntity h = (from e in dc.HierarchicalTableEntities
select e).First();
var query = dc.FnTreeForParent( h.ID );
foreach (HierarchicalTableViewEntity entity in query) {
...process the tree node...
}
}
Я получил этот подход из блога Rob Conery (проверка вокруг Pt. 6 для этого кода, также на codeplex), и я люблю использовать его. Это могло быть повторно сформировано для поддержки нескольких "sub" уровней.
var categories = from c in db.Categories
select new Category
{
CategoryID = c.CategoryID,
ParentCategoryID = c.ParentCategoryID,
SubCategories = new List<Category>(
from sc in db.Categories
where sc.ParentCategoryID == c.CategoryID
select new Category {
CategoryID = sc.CategoryID,
ParentProductID = sc.ParentProductID
}
)
};
Этот дополнительный метод мог потенциально быть изменен для использования IQueryable. Я использовал его успешно в прошлом на наборе объектов. Это может работать на Ваш сценарий.
public static IEnumerable<T> ByHierarchy<T>(
this IEnumerable<T> source, Func<T, bool> startWith, Func<T, T, bool> connectBy)
{
if (source == null)
throw new ArgumentNullException("source");
if (startWith == null)
throw new ArgumentNullException("startWith");
if (connectBy == null)
throw new ArgumentNullException("connectBy");
foreach (T root in source.Where(startWith))
{
yield return root;
foreach (T child in source.ByHierarchy(c => connectBy(root, c), connectBy))
{
yield return child;
}
}
}
Вот то, как я назвал его:
comments.ByHierarchy(comment => comment.ParentNum == parentNum,
(parent, child) => child.ParentNum == parent.CommentNum && includeChildren)
Этот код является улучшенной, исправленной версией ошибки кода, найденного здесь.
Проблема выбрать данные из стороны клиента состоит в том, что Вы никогда не можете быть уверены, как глубоко необходимо пойти. Этот метод сделает одно распространение в прямом и обратном направлениях на глубину, и это мог быть union'd, чтобы сделать от 0 до указанной глубины в одном распространении в прямом и обратном направлениях.
public IQueryable<Node> GetChildrenAtDepth(int NodeID, int depth)
{
IQueryable<Node> query = db.Nodes.Where(n => n.NodeID == NodeID);
for(int i = 0; i < depth; i++)
query = query.SelectMany(n => n.Children);
//use this if the Children association has not been defined
//query = query.SelectMany(n => db.Nodes.Where(c => c.ParentID == n.NodeID));
return query;
}
Это не может, однако, сделать произвольной глубины. При реальном требовании произвольной глубины необходимо сделать это в базе данных - таким образом, можно принять корректное решение остановиться.
Я сделал это два пути:
В MS SQL 2008 Вы могли использовать HierarchyID непосредственно, в sql2005 Вам, вероятно, придется реализовать их вручную. ParentID не то, что производителен на больших наборах данных. Также проверьте эту статью на большее количество обсуждения темы.
This option might also prove useful:
LINQ AsHierarchy() extension method
http://www.scip.be/index.php?Page=ArticlesNET18
Я удивлен, что никто не упомянул альтернативный дизайн базы данных - когда иерархию нужно выровнять с нескольких уровней и извлекать с высокой производительностью (не так, учитывая пространство для хранения) Лучше использовать другую таблицу entity-2-entity для отслеживания иерархии вместо подхода parent_id.
Это позволит не только отношения с одним родителем, но и отношения с несколькими родителями, указание уровня и различные типы отношений:
CREATE TABLE Person (
Id INTEGER,
Name TEXT
);
CREATE TABLE PersonInPerson (
PersonId INTEGER NOT NULL,
InPersonId INTEGER NOT NULL,
Level INTEGER,
RelationKind VARCHAR(1)
);
Прочтите следующую ссылку.
http://support.microsoft.com/default.aspx?scid=kb;en-us;q248915