Ich habe ein Problem, dass ich denke, kann keine Antwort haben andere dann völlig überdenken die Anwendung, aber hoffentlich können Sie mir beweisen, falsch!Fehler bei der Währungsumrechnung bei mehrzeiligen Transaktionen
Ich habe eine Anwendung, die einen Wechselkurs verwendet, um einen Basiswert von einem Währungswert auf vier Dezimalstellen zurück zu berechnen. Die Berechnung ist recht einfach und übergibt alle relevanten Unit-Tests korrekte Ergebnisse jedes Mal geben:
public static decimal GetBaseValue(decimal currencyAmount, RateOperator rateOperator,
double exchangeRate)
{
if (exchangeRate <= 0)
{
throw new ArgumentException(ErrorType.
ExchangeRateLessThanOrEqualToZero.ErrorInfo().Description);
}
decimal baseValue = 0;
if (currencyAmount != 0)
{
switch (rateOperator)
{
case RateOperator.Divide:
baseValue = Math.Round(currencyAmount * Convert.ToDecimal(exchangeRate),
4, MidpointRounding.AwayFromZero);
break;
case RateOperator.Multiply:
baseValue = Math.Round(currencyAmount/Convert.ToDecimal(exchangeRate),
4, MidpointRounding.AwayFromZero);
break;
default:
throw new ArgumentOutOfRangeException(nameof(rateOperator));
}
}
return baseValue;
}
Ich habe auch die entsprechende Währung von der Basis zu berechnen, nicht um das Problem zu vermeiden gezeigt verwirrend und in jedem Fall so ziemlich identischer Code abgesehen von Parameternamen und der Umkehrung der math-Operatoren in der switch-Anweisung.
Mein Problem kommt, wenn ich diesen Prozess auf eine Transaktion anwenden muss, die mehrere Zeilen hat. Die Regel ist, dass die Summe der Belastungen gleich der Summe der Kredite sein muss, aber einige Zahlen sind um einen Bruchteil eines Pennys hinaus. Dies liegt daran, dass wir das Ergebnis von zwei oder mehr individuell konvertierten Zahlen gegen eine einzige Umwandlung zusammenrechnen, und ich glaube, dass die akkumulierte Rundung den Fehler verursacht.
Nehmen wir an, den folgenden:
- Artikel Wert von 1,00 $
- MwSt Wert von $ 0,20 (20%)
- Wechselkurs von 1,4540 Dollar für das Pfund
Nun lassen wir überprüfen eine Beispieltransaktion:
Line # Debit Credit
1 $1.20
2 $1.00
3 $0.20
Dieser Test besteht den Test, da die Gesamtguthaben den Gesamtbelastungen entsprechen.
Wenn wir (teilen jeden Dollar-Wert von 1,454) umwandeln, sehen wir das Problem:
Line # Debit Credit
1 £0.8253
2 £0.6878
3 £0.1376
========================
Total £0.8254 £0.8253
========================
Dies scheitert und bricht die Regel, glaube ich, als Folge der beiden Sätze in der Belastungs Spalte Rundungs und nur ein Satz in der Kreditspalte. Aber ich muss in der Lage sein, mit Genauigkeit genau hin und her zu rechnen, und ich muss sicherstellen, dass die Transaktion in allen Linien in Währung und in Basis ausgeglichen ist.
Also zu meiner Frage: Wie kann man dieses Problem am besten angehen? Hat jemand etwas Ähnliches erlebt, und wenn es Ihnen etwas ausmacht, zu teilen, wie Sie es gelöst haben?
EDIT - Die Lösung I
GebrauchteIm Anschluss an die Antwort von Charles, die mich absolut in die richtige Richtung wies ich meine Arbeitsweise zugunsten von jemand anders bin Entsendung, die ein ähnliches Problem konfrontiert sein könnten. Während des Verständnis, dass dieser spezifische Code nicht für eine direkte Kopieren und Einfügen Lösung geeignet sein kann, hoffe ich, dass die Kommentare und das angewandte Verfahren wird eine Hilfe sein:
private static void SetBaseValues(Transaction transaction)
{
// Get the initial currency totals
decimal currencyDebitRunningTotal = transaction.CurrencyDebitTotal;
decimal currencyCreditRunningTotal = transaction.CurrencyCreditTotal;
// Only one conversion, but we do one per column
// Note that the values should be the same anyway
// or the transaction would be invalid
decimal baseDebitRunningTotal =
Functions.GetBaseValue(currencyDebitRunningTotal,
transaction.MasterLine.RateOperator,
transaction.MasterLine.ExchangeRate);
decimal baseCreditRunningTotal =
Functions.GetBaseValue(currencyCreditRunningTotal,
transaction.MasterLine.RateOperator,
transaction.MasterLine.ExchangeRate);
// Create a list of transaction lines that belong to this transaction
List<TransactionLineBase> list = new List<TransactionLineBase>
{ transaction.MasterLine };
list.AddRange(transaction.TransactionLines);
// If there is no tax line, don't add a null entry
// as that would cause conversion failure
if (transaction.TaxLine != null)
{
list.Add(transaction.TaxLine);
}
// Sort the list ascending by value
var workingList = list.OrderBy(
x => x.CurrencyCreditAmount ?? 0 + x.CurrencyDebitAmount ?? 0).ToList();
// Iterate the lines excluding any entries where Credit and Debit
// values are both null (this is possible on some rows on
// some transactions types e.g. Reconciliations
foreach (var line in workingList.Where(
line => line.CurrencyCreditAmount != null ||
line.CurrencyDebitAmount != null))
{
if (transaction.CanConvertCurrency)
{
SetBaseValues(line);
}
else
{
var isDebitLine = line.CurrencyCreditAmount == null;
if (isDebitLine)
{
if (line.CurrencyDebitAmount != 0)
{
line.BaseDebitAmount =
line.CurrencyDebitAmount ?? 0/
currencyDebitRunningTotal * baseDebitRunningTotal;
currencyDebitRunningTotal -=
line.CurrencyDebitAmount ?? 0;
baseDebitRunningTotal -= line.BaseDebitAmount ?? 0;
}
}
else
{
if (line.CurrencyCreditAmount != 0)
{
line.BaseCreditAmount =
line.CurrencyCreditAmount ?? 0/
currencyCreditRunningTotal*baseCreditRunningTotal;
currencyCreditRunningTotal -= line.CurrencyCreditAmount ?? 0;
baseCreditRunningTotal -= line.BaseCreditAmount ?? 0;
}
}
}
}
}
Hallo Charles, Vielen Dank für Ihren interessanten Beitrag. Die Theorie macht Sinn, wäre aber nicht genau reversibel und würde nicht in die aktuelle Verarbeitungsmethode passen, die im Grunde die Zeilen in der Reihenfolge bearbeitet. Das heißt nicht, dass es nicht erreicht werden könnte, aber es würde eine vollständige Änderung des Systems bedeuten. Ich werde darüber noch einmal nachdenken und mit den Mächtigen darüber diskutieren, ob dies ein möglicher Weg nach vorne ist. Ich werde posten, sobald ich irgendeine Form der Entscheidung habe. Nochmals vielen Dank dafür, dass Sie sich die Zeit und Mühe genommen haben, zu antworten. – oldcoder
@oldcoder kein Problem.Wie Sie herausgefunden haben (und wie Peter darauf hingewiesen hat), besteht die einzige Möglichkeit darin, nur eine Zahl zu konvertieren und die anderen basierend darauf zu berechnen - entweder unter Verwendung seines spezifischen Domänenwissens wie der Mehrwertsteuerrate oder durch Verwendung diese allgemeinere Methode. Wenn Sie jede ohne Kontext konvertieren, verursachen die kumulativen Rundungsfehler kleine Diskrepanzen. –
Hallo Charles, Ihre Antwort führte mich zur richtigen Lösung. Es erforderte ein wenig Reengineering, um mit vorhandenem Code arbeiten zu können, und um alle Möglichkeiten abzudecken, war es ziemlich mühsam und fehlerbehaftet, aber das Ergebnis funktioniert einwandfrei und alle zugehörigen Unit- und Integrationstests passieren 100%. Eine besondere Sache war, dass ich Credit und Debit laufen lassen musste, indem ich die Summen für die Währungs- und Basiswerte laufen ließ, um sicherzustellen, dass alle notwendigen Werte verfügbar waren. Vielen Dank für Ihre Hilfe, die sehr geschätzt wird. – oldcoder