About amounts and currencies in SAP screens and databases
After covering the oddity of quantity display (more precisely, the selective enforcement of CUNI settings), it’s time I told the story of an even stranger and more eccentric behaviour at SAP.
While having a glance at the following screenshot (left side: TM UI, right side: debugger)
you might be very well tempted to think “what the…??”.
The fact that an amount of “457,- EUR” is stored internally as 0,045700 is based on two rules:
- the position of radix point in data type CURR does not matter
- instead, the value is written in packed format, right aligned, using the number of decimals specified in table TCURX, if maintained for the affected currency, otherwise using two decimals (that is why a developer is always enforced to specify a reference field for currency – in data dictionary, as well as when using WRITE statements involving amount fields).
The Basis takes care of the display and it applies the currency customizing. You can see the expected value in SE16 too.
Like many other SAP bizarre thingies, this went unnoticed for so long, because R/3 data elements use a domain with two decimals, and the vast majority of the currencies worldwide have two decimals as well. Therefore, the values happened to be persisted in the very same way as they are seen on UI. Well, except for Japanese Yen (which famously has no decimals) and a couple of others. A value of 100 JPY in persisted in R/3 tables as 1,00 and this raised a few eyebrows in the past, as a quick SCN search should reveal.
But SAP TM uses a domain, /SCMTMS/AMOUNT, that has six decimals. Obvious, the one who designed and created this domain forgot about this behaviour. The six decimals are useless, do not add any “precision” and the data element could have been as well defined devoid of decimals, without any visible impact whatsoever.
The end user does not see any of this, and even experienced developers are sometimes confused, because the Basis takes care pretty well of hiding the internals.
However, there are three aspects that are worth mentioning:
1. Changing value of reference currency on UI
What’s happening when changing the currency above from EUR to JPY? If only the currency is changed (and the value of 0,045700 stays unchanged in backend), then after triggering the roundtrip the value in UI transforms suddenly to 45700,00 JPY!!
And that is exactly what happened in Web Dynpro screens prior to SAP Note 1538380, released some six years ago. The naive expectation of most users was that the value stay put, and as result of complaints, the correction was finally made. Afterwards, a change in currency may lead to a change in the persisted amount as well (to keep the display stable, the DB value must compensate for the difference of decimals – 0,045700 should become 0,000457).
The change in persisted value might trigger unwanted side effects, that’s why Web Dynpro introduced an application parameter, WDREFFIELDBEHAVIOUR, to allows any customer to influence what happens in this case. The default value is the “common sense” one – the values stays stable on the screen (and are modified on DB). Whoever prefers the original behaviour, must set explicitly the other parameter value.
This kind of situation occurs rarely, as there are only few currencies that deviate from the “two decimals” common behaviour.
2. Changing the currency definition
By now everyone should be aware of the fact that the number of decimals should never be changed in TCURX in a system where some amounts were already persisted with reference to the affected currency!!
Doing so will affect the interpretation of the persisted values. While one could write an XPRA-like adjustment report, there is no simple way (not unless logging everything) to detect which values were changed and which weren’t in case of a abnormal report termination.
3. Movement of values between variables
Now it becomes funny. SAP Basis takes care of displaying the correct value on UIs, but the kernel treats the value as a normal packed number. When moving data between two variables, the decimal point is respected.
Let’s assume a TM-specific variable LV_TM as amount, with six decimals if you paid attention, and another variable LV_ERP – an amount with two decimals like in the classic Business Suite.
LV_TM holds the value of 15.000,- EUR, therefore 1,500000 in internal format (you might have noticed that I consistently use European notation for numbers: comma as radix character, full stop as digit group separator). When executing a statement
MOVE lv_tm TO lv_erp.
lv_erp = lv_tm.
the outcome will see LV_ERP having an internal value of 1,50 which represents 1,50 EUR. Similary, 3,14 EUR from LV_ERP will become 3,140000 in LV_TM, which of course stands for 31.400,- EUR.
This kind of mistake can occur quite often in code that is situated at the boundary between an SAP system and “outside” world – B2B, A2A interfaces are affected. Even the integration between SAP TM and SAP ERP is affected. In such cases, one must not use the MOVE statement, but suitable central routines like function modules from function group ISOC (e.g. CURRENCY_AMOUNT_BAPI_TO_SAP) or the amount-related methods from class CL_GDT_CONVERSION.
Well, some programmers simply multiply or divide the amounts (with/by ten at the difference of decimals) instead of using the said functions. You might encounter – e.g. in TM code related to settlement document transfer to ERP – statements like
lv_erp = lv_tm * 10000.
This works of course, and quite securely (none in right mind will ever change the definition of the amount domains in TM and ERP), but I personally prefer to use the central approach, as it makes the situation more transparent. A beginner looking at the statement above has little chance to figure out why the value must be multiplied with exactly ten thousand. While the technical explanation isn’t straightforward in the other case either, the central method gives at least a hint that some sort of special handling is required.
PS: you can see in the source code of CL_GDT_CONVERSION=>AMOUNT_INBOUND, how the currencies not maintained in TCURX are defaulted to two decimals:
CLEAR s_tcurx. SELECT SINGLE * FROM tcurx INTO s_tcurx WHERE currkey EQ sapcode. IF sy-subrc NE 0. s_tcurx-currdec = 2. CLEAR sy-subrc. ENDIF