somme problème de valeurs divisées (portant erreur d'arrondi)
-
24-10-2019 - |
Question
J'ai un produit qui coûte 4 € et je dois diviser cet argent pour 3 départements. Sur la deuxième colonne, j'ai besoin pour obtenir le nombre de lignes pour ce produit et diviser le nombre de départements.
Ma requête:
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
Mais quand je résume les valeurs, je reçois 3,999999. Bien sûr, avec des centaines de lignes que je reçois de grandes différences ... Est-il possible de définir 2 nombres décimaux et dernière valeur ronde? (Mes résultats seraient 1.33 1.33 1.34) Je veux dire, d'une certaine façon de régler la dernière ligne?
La solution
Avec six décimales de précision, vous auriez besoin d'environ 5 000 transactions à remarquer une différence d'un cent, si vous arrondissez le nombre final à deux décimales. L'augmentation du nombre de décimales à un niveau acceptable éliminerait la plupart des problèmes, à savoir l'utilisation 9 décimales dont vous auriez besoin d'environ 5.000.000 transactions pour constater une différence d'un cent.
Autres conseils
Pour gérer cela, pour chaque ligne que vous auriez à faire ce qui suit:
- Effectuer la division
- Arrondir le résultat au nombre approprié de cents
- Somme la différence entre le montant arrondi et le résultat de l'opération de division
- Lorsque la somme des différences dépasse le plus bas décimale (dans ce cas, 0,01), ajouter ce montant aux résultats de la prochaine opération de division (après arrondi).
distribuera des quantités fractionnaires uniformément sur les lignes. Malheureusement, il n'y a pas moyen facile de le faire dans SQL avec des requêtes simples; il est probablement préférable d'effectuer cette procédure dans le code.
En ce qui concerne l'importance, en ce qui concerne les applications et les institutions financières, les choses de ce genre sont très importantes, même si elle est seulement un sou, et même si elle ne peut se produire chaque X nombre de dossiers; En règle générale, les utilisateurs veulent voir les valeurs cravate au centime (ou quel que soit votre unité monétaire est) exactement.
Plus important encore, vous ne voulez pas permettre un exploit comme "Superman III" ou "office Space" pour se produire.
Peut-être que vous pouvez faire une quatrième ligne qui sera totale - somme (A, B, C). Mais cela dépend de ce que vous voulez faire, si vous avez besoin valeur exacte, vous pouvez garder les fractions, sinon, troncature et ne se soucient pas de la perte virtuelle
peut également se faire en ajoutant simplement la différence d'arrondi d'une valeur particulière au numéro suivant à arrondir (avant arrondi). De cette façon, la pile reste toujours la même taille.
Voici une implémentation TSQL (Microsoft SQL Server) de l'algorithme fourni par Martin :
-- 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
;
Sortie:
Vous pouvez brancher ce que les numéros que vous voulez en haut. Je ne sais pas s'il y a une preuve mathématique de cet algorithme.