Сумма разделенных значений Проблема (дело с ошибкой округления)

StackOverflow https://stackoverflow.com/questions/4784864

Вопрос

У меня продукт, который стоит 4 €, и мне нужно разделить эти деньги на 3 отдела. Во втором столбце мне нужно получить количество строк для этого продукта и разделиться на количество отделов.

Мой запрос:

select
   department, totalvalue,
   (totalvalue / (select count(*) from departments d2 where d2.department = p.product))
       dividedvalue
from products p, departments d
where d.department = p.department

Department  Total Value  Divided Value
----------  -----------  -------------
A           4            1.3333333
B           4            1.3333333
C           4            1.3333333

Но когда я суммирую значения, я получаю 3999999. Конечно, с сотнями строк я получаю большие различия ... есть ли шанс определить 2 десятичных числа и круглые последнее значение? (Мои результаты будут 1,33 1,33 1.34) Я имею в виду, какой -нибудь способ отрегулировать последний ряд?

Это было полезно?

Решение

С шестью десятилями точности, вам понадобится около 5000 транзакций, чтобы заметить разницу в одном центре, если вы округлите окончательный номер до двух десятичных десятиц. Увеличение количества десятичных декораций до приемлемого уровня устранит большинство проблем, т.е. использование 9 десятичных декораций, вам понадобится около 5 000 000 транзакций, чтобы заметить разницу в центре.

Другие советы

Чтобы справиться с этим, для каждой строки вам придется сделать следующее:

  • Выполнить разделение
  • Округлый результат до соответствующего количества центов
  • Суммируйте разницу между округлой суммой и результатом операции деления
  • Когда сумма различий превышает наименьшее десятичное место (в данном случае 0,01), добавьте эту сумму к результатам операции следующего деления (после округления).

Это будет распределять дробные суммы равномерно по рядам. К сожалению, нет простого способа сделать это в SQL с простыми запросами; Вероятно, лучше выполнить это в процедурном коде.

Что касается того, насколько важно, когда дело доходит до финансовых приложений и учреждений, такие вещи очень важны, даже если это только копейки, и даже если это может произойти только каждое x числа записей; Как правило, пользователи хотят, чтобы значения связывались с копейкой (или какой -либо из них, что такое единица валюты).

Самое главное, вы не хотите допускать такого эксплойта "Супермен III" или же "Офисное помещение" происходить.

Может быть, вы сможете сделать прямое ряд, которая будет полной - сумма (A, B, C). Но это зависит от того, что вы хотите сделать, если вам нужна точная ценность, вы можете сохранить фракции, иначе усечь и не заботиться о виртуальных потерях

Также можно сделать просто, просто добавив разницу в округлении определенного значения к следующему числу, которое будет округлено (до округления). Таким образом, куча остается всегда одинаковым размером.

Вот реализация TSQL (Microsoft SQL Server) алгоритма, предоставленного Мартин:

-- Set parameters.
DECLARE @departments INTEGER = 3;
DECLARE @totalvalue DECIMAL(19, 7) = 4.0;

WITH
CTE1 AS
(
    -- Create the data upon which to perform the calculation.
    SELECT

        1 AS Department
        , @totalvalue AS [Total Value]
        , CAST(@totalvalue / @departments AS DECIMAL(19, 7)) AS [Divided Value]
        , CAST(ROUND(@totalvalue / @departments, 2) AS DECIMAL(19, 7)) AS [Rounded Value]

    UNION ALL

    SELECT

        CTE1.Department + 1
        , CTE1.[Total Value]
        , CTE1.[Divided Value]
        , CTE1.[Rounded Value]

    FROM

        CTE1

    WHERE

        Department < @departments
),

CTE2 AS
(
    -- Perform the calculation for each row.
    SELECT

        Department
        , [Total Value]
        , [Divided Value]
        , [Rounded Value]
        , CAST([Divided Value] - [Rounded Value] AS DECIMAL(19, 7)) AS [Rounding Difference]
        , [Rounded Value] AS [Calculated Value]

    FROM

        CTE1

    WHERE

        Department = 1

    UNION ALL

    SELECT

        CTE1.Department
        , CTE1.[Total Value]
        , CTE1.[Divided Value]
        , CTE1.[Rounded Value]
        , CAST(CTE1.[Divided Value] + CTE2.[Rounding Difference] - ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))
        , CAST(ROUND(CTE1.[Divided Value] + CTE2.[Rounding Difference], 2) AS DECIMAL(19, 7))

    FROM

        CTE2

            INNER JOIN CTE1
                ON CTE1.Department = CTE2.Department + 1

)

-- Display the results with totals.
SELECT 

    Department
    , [Total Value]
    , [Divided Value]
    , [Rounded Value]
    , [Rounding Difference]
    , [Calculated Value]

FROM 

    CTE2

UNION ALL

SELECT

    NULL
    , NULL
    , SUM([Divided Value])
    , SUM([Rounded Value])
    , NULL
    , SUM([Calculated Value])

FROM

    CTE2

;

Выход:

enter image description here

Вы можете подключить любые номера, которые вы хотите вверху. Я не уверен, есть ли математическое доказательство этого алгоритма.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top