I have a compute statement that uses fields like so:
WS-COMPUTE PIC 9(14).
WS-NUM-1 PIC 9(09).
WS-NUM-2 PIC 9(09).
WS-NUM-3 PIC S9(11) COMP-3.
WS-DENOM PIC 9(09).
And then there is logic to make a computation
COMPUTE WS-COMPUTE =
((WS-NUM-1 - WS-NUM-2 + WS-NUM-3)
/ WS-DENOM) * 100
The * 100 is in there because a number < 1 is expected from the division, but 0 is what was always stored in WS-COMPUTE.
We got a workaround by declaring another field that did have implied decimals, and then moving that to value to WS-COMPUTE, but I was lost on why the original would always populate WS-COMPUTE with 0?
The number of decimal places for the results of intermediate calculations are directly related to the number of decimal places in your the final result field (you can consult the manual in the case where you have multiple result fields) when there are no decimal places in the individual operands. COBOL is not going to use a predetermined number of decimal places for intermediate results. If neither actual operands in question nor final result contain decimal places, the intermediate result will not contain decimal places.
The relationship is: number of decimal places in intermediate results = number of decimal places in final result field. The only thing which can modify this is the specification of ROUNDED. If ROUNDED is specified, one extra decimal place is kept for the intermediate result fields, and that will be used to perform the rounding of the final result.
You have no decimal places on your final result, and no ROUNDED. So the intermediate results will have no decimal places. If you get a value of less than zero, then it is gone before anything can happen to it. It is stored as zero, because there is no decimal part available to store it in.
You need to understand COMPUTE before you use it. Nowhere near enough people do. There is absolutely no need to specify excessive lengths of fields or decimal places where none are needed. These a common ways to "deal with" a problem, but are unnecessary, as the actual problem is a poorly-formed COMPUTE.
If your COMPUTE contains multiplication, do that first. If it contains division, do that last. This may require re-arranging a formula, but this will give you the correct result. Subject to truncation, which comes in two parts, as Bruce Martin has indicated. There is the one you are getting, decimal truncation through not specifying enough (any) decimal places when you expect a decimal-only value for an intermediate result, and high-order truncation if your source fields are not big enough. Always remember that the result field controls the size (decimal and integer) of the intermediate results. If you do those things, your COMPUTEs will always work.
And consider whether you want the final result rounded. If so, use ROUNDED. If you want intermediate results to be rounded, you need to do that yourself with separate COMPUTEs or DIVIDEs or MULTIPLYs.
If you don't take these things into account, your COMPUTEs will work by accident, or sometimes, or not at all, or when you specify excessive size or decimal places. Always remember that the result field controls the size (decimal) of the intermediate results where operands contain no decimal places.
If you don't need any decimal places in the final result, use Bruce Martin's first COMPUTE:
COMPUTE WS-COMPUTE = (((WS-NUM-1 - WS-NUM-2 + WS-NUM-3) * 100) / WS-DENOM
If you do need decimal places, use Bruce Martin's first COMPUTE (yes, the same one) with the decimals defined on the final result (WS-COMPUTE).
If you need the result to be rounded (0-4 down, 5-9 up) use ROUNDED. If you need some other rounding, specify the final result with an extra decimal place beyond what you need, and do your own rounding to your specification.
If you look at the column to the right of your question, under Related, you'll find existing questions here which would/should have answered this one for you.
You do not need to add spurious digits or spurious decimal places to everything in sight. Ensure your final result is big enough, has enough decimal places, and pay attention to the order of things. Read your manual which should document intermediate results. If your manual does not cover this, the IBM Enterprise COBOL manuals are an excellent general reference, as well as specific ones. The Programming Guide devotes an entire Appendix to intermediate results.
It sounds like you are using the TRUNC(STD) option, the compiler takes the picture clause to decide what precision to use for intermediate results. You can either add implied decimals to all your intermediate fields or try something like TRUNC(BIN) or TRUNC(OPT), though in this case, I don't think they will help.
Truncates final intermediate results. OS/VS COBOL has the TRUNC and NOTRUNC options (NOTRUNC is the default). VS COBOL II , IBM COBOL, and Enterprise COBOL have the TRUNC(STD|OPT|BIN) option.
TRUNC(STD)
Truncates numeric fields according to PICTURE specification of the binary receiving field
TRUNC(OPT)
Truncates numeric fields in the most optimal way
TRUNC(BIN)
Truncates binary fields based on the storage they occupy
TRUNC(STD) is the default.
For a complete description, see the Enterprise COBOL Programming Guide.
The default for Cobol is normally to truncate !!. This includes intermediate results.
So the decimal places will be truncated in your calculation
You could try:
COMPUTE WS-COMPUTE = (((WS-NUM-1 - WS-NUM-2 + WS-NUM-3) * 100) / WS-DENOM
This could result in loosing top order digits.
Alternatively you could
Use 2 computes
Add decimals to the input declaration
Use floating point fields (comp-1, comp-2). As they are rarely used in Cobol, I do not advise it.l
03 WS-Temp Pic 9(11)V9999 comp-3.
Compute WS-Temp = WS-NUM-1 - WS-NUM-2 + WS-NUM-3.
Compute WS-Temp = (WS-Temp / WS-DENOM) * 100.
Compute WS-COMPUTE = WS-Temp.
Change the field definition:
WS-COMPUTE PIC 9(14).
WS-NUM-1 PIC 9(09)V999.
WS-NUM-2 PIC 9(09)V999.
WS-NUM-3 PIC S9(11)V999 COMP-3.
WS-DENOM PIC 9(09).
Related
Why such code is used in some applications instead of a MOVE?
add 16 to ZERO giving SOME-RESULT
I spotted this in professionally written code at several spots.
Sorce is on this page
Why such code is used in some applications instead of a MOVE?
add 16 to ZERO giving SOME-RESULT
Without seeing more of the code, it appears that it could be a translation of IBM Assembler to COBOL. In particular, the ZAP (Zero and Add Packed) instruction may be literally translated to the above instruction, particularly if SOME-RESULT is COMP-3. Thus, someone checking the translation could see that the ZAP instruction was faithfully translated.
Or, it could be an assembler programmer's idea of a joke.
Having seen the code, I also note the use of
subtract some-data-item from some-data-item
which is used instead of
move zero to some-data-item
This is consistent with operations used with packed decimal fields in IBM Assembly, where there are no other instructions to accomplish "flexible" moves. By flexible, I mean that the packed decimal instructions contain a length field so that specific size MVC instructions need not be used.
This particular style, being unusual, may be related to catching copyright violations.
From my experience, I'm pretty sure I know the reason why the programmer would have done this. It has something to do with the binary representation of the number.
I bet SOME-RESULT is a packed-decimal (or COMP-3) format number. Let's assume the field is defined like this
05 SOME-RESULT PIC S9(5) COMP-3.
This results in a 3-byte field with a hex representation like this
x'00016C'
The decimal number is encoded as a binary encoded decimal (BCD, one decimal digit per half-byte), and the last half-byte holds the sign.
Let's take a look at how the sign is defined:
if it is one of x'C', x'A', x'F', x'E' (café), then the number is positive
if it is one of x'B', x'D', then the number is negative
any of x'0'..'x'9' are not valid signs, so we can distinguish signed packed-decimals from unsigned.
However, a zoned number (PIC 9(5) DISPLAY) - as in the source code - looks like this:
x'F0F0F0F1F6'
As you can see, each decimal digit is an EBCDIC character with the 'zone' part (the first half-byte) always being x'F'.
Now we get closer to your question!
What happens when we use
MOVE 16 TO SOME-RESULT
If you just MOVE a number to such a field, this results in being compiled into a PACK instruction on the machine code level.
PACK SOME-RESULT,=C'16'
A pack instruction takes a zoned number and packs it by picking only the second half-byte of each byte and storing it in the half-bytes of the packed number - with one exception! When it comes to the last byte, it simply flips the two half-bytes and stores them in the last half-byte of the decimal.
This means that the zone of the last byte of the zoned decimal becomes the sign in the packed decimal:
x'00016F'
So now we have an x'F' as the sign – which is a valid positive sign.
However, what happens if use this Cobol instruction instead
ADD 16 TO ZERO GIVING SOME-RESULT
This compiles into multiple machine level instructions
PACK SOME_RESULT,=C'0'
PACK TEMP,=C'16'
AP SOME_RESULT,TEMP
(or similar - the key point is that is needs an AP somewhere)
This makes a slight difference in the result, because the AP (add packed) instruction always sets the resulting sign to either x'C' for a positive or x'D' for a negative result.
So the difference lies in the sign
x'00016C'
Finally, the question is why would one make this difference? After all, both x'F' and x'C' are valid positive signs. So why care?
There is one situation when this slight difference can cause big problems: When the packed decimal is part of an index key, then we would not get a match, even though the numbers are semantically identical!
Because this situation occurred quite often in older databases like VSAM and DL/I (later: IMS/DB), it became good practice to "normalize" packed decimals if they were part of an index key.
However, some programmers adopted the practice without knowing why, so you may come across code that uses this "normalization" even though the data are not used for index keys.
You might also wonder why a compiler does not optimize out the ADD 16 TO ZERO. I'm pretty sure it once did, but that broke a lot of applications, so this specific optimization was removed again or at least made a non-default option with warnings.
Additional useful info
Note that at least the Enterprise Cobol for z/OS compiler allows you to see exactly the machine code that is produced from your source code if use the LIST compile option (see this example output). I recommend to always compile with options LIST, MAP, OFFSET, XREF because these options enable you find the exact problem in your Cobol source even when you only have a program dump from an abend.
Anyway, good programming practice is not to care about the compiler or the machine code, but about the other programmers who will have to maintain, and thus read and understand the code. Good practice would be to always prefer simple and readable instructions, and to document the reasons (right in the code) when deviating from this rule.
Some programmers like to do things "just because they can". I have a feeling that is what you are seeing here. It makes about as much sense as doing
a := 0 + b
would in go.
A simple question that turned out to be quite complex:
How do I turn a float to a String in GForth? The desired behavior would look something like this:
1.2345e fToString \ takes 1.2345e from the float stack and pushes (addr n) onto the data stack
After a lot of digging, one of my colleagues found it:
f>str-rdp ( rf +nr +nd +np -- c-addr nr )
https://www.complang.tuwien.ac.at/forth/gforth/Docs-html-history/0.6.2/Formatted-numeric-output.html
Convert rf into a string at c-addr nr. The conversion rules and the
meanings of nr +nd np are the same as for f.rdp.
And from f.rdp:
f.rdp ( rf +nr +nd +np – )
https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Simple-numeric-output.html
Print float rf formatted. The total width of the output is nr. For
fixed-point notation, the number of digits after the decimal point is
+nd and the minimum number of significant digits is np. Set-precision has no effect on f.rdp. Fixed-point notation is used if the number of
siginicant digits would be at least np and if the number of digits
before the decimal point would fit. If fixed-point notation is not
used, exponential notation is used, and if that does not fit,
asterisks are printed. We recommend using nr>=7 to avoid the risk of
numbers not fitting at all. We recommend nr>=np+5 to avoid cases where
f.rdp switches to exponential notation because fixed-point notation
would have too few significant digits, yet exponential notation offers
fewer significant digits. We recommend nr>=nd+2, if you want to have
fixed-point notation for some numbers. We recommend np>nr, if you want
to have exponential notation for all numbers.
In humanly readable terms, these functions require a number on the float-stack and three numbers on the data stack.
The first number-parameter tells it how long the string should be, the second one how many decimals you would like and the third tells it the minimum number of decimals (which roughly translates to precision). A lot of implicit math is performed to determine the final String format that is produced, so some tinkering is almost required to make it behave the way you want.
Testing it out (we don't want to rebuild f., but to produce a format that will be accepted as floating-point number by forth to EVALUATE it again, so the 1.2345E0 notation is on purpose):
PI 18 17 17 f>str-rdp type \ 3.14159265358979E0 ok
PI 18 17 17 f.rdp \ 3.14159265358979E0 ok
PI f. \ 3.14159265358979 ok
I couldn't find the exact word for this, so I looked into Gforth sources.
Apparently, you could go with represent word that prints the most significant numbers into supplied buffer, but that's not exactly the final output. represent returns validity and sign flags, as well as the position of decimal point. That word then is used in all variants of floating point printing words (f., fp. fe.).
Probably the easiest way would be to substitute emit with your word (emit is a deferred word), saving data where you need it, use one of available floating pint printing words, and then restoring emit back to original value.
I'd like to hear the preferred solution too...
I am reading the following statement and I am not sure why we must have the packed-decimal in odd digits? Is the following statement true, that you can only have odd number of digits in hardware? Can you give me a example to show why it says that?
RULES(NOEVENPACK) This compiler option will tell you if you
accidentally define a Pack Decimal data item within even number of
digits. You can only have odd number of digits in hardware. If you
have one byte you have one digit , 2 byte you have 3 digit, 3 byte
-->5 digit.
In a packed-decimal field, the right-most half-byte (nybble) is the sign position. Each other half-byte in the field is a digit, 0-9.
This means that the storage occupied by a packed-decimal field represents an odd number of digits. You have no choice over that.
If you define PACKED-DECIMAL PIC 9(4) you get
?NNNNS
Where N is a digit (see, there are four of them) and S is the sign (since the field is defined as unsigned, it will have a sign of F, which is always treated as positive).
What about that ?. It can't not be there. Since it can't not be there, the compiler has to generate code so that it can only contain a zero, which won't affect the value of the field.
If you define PACKED-DECIMAL PIC 9(5) you get
NNNNNS
Five digits, sign, and nothing else for the compiler to worry about. No code generated beyond what is otherwise required for the field.
So your code runs faster.
You may wonder "how much does that matter?". If you consider how many packed-decimal fields you may see in a program, if each of those, every time it was referenced, had code to make the first digit zero, you've got quite a lot of code, for every pass through the program.
On the Mainframe you, generally, pay for resource-usage. If you avoid that in 5,000 programs which are processing 10,000,000 transactions a day, 365 days a year, then it adds up.
I want to convert a double to a string and only display needed decimals.
So I cannot use
d := 123.4
s := Format('%.2f', [d]);
As it display as the result is 123.40 when I want 123.4.
Here is a table of samples and expected result
|Double|Result as string|
-------------------------
|5 |5 |
|5.1 |5.1 |
|5.12 |5.12 |
|5.123 |5.123 |
You can use the %g format string:
General: The argument must be a floating-point value. The value is converted to the shortest possible decimal string using fixed or
scientific format. The number of significant digits in the resulting
string is given by the precision specifier in the format string; a
default precision of 15 is assumed if no precision specifier is
present. Trailing zeros are removed from the resulting string, and a
decimal point appears only if necessary. The resulting string uses the
fixed-point format if the number of digits to the left of the decimal
point in the value is less than or equal to the specified precision,
and if the value is greater than or equal to 0.00001. Otherwise the
resulting string uses scientific format.
This is not as simple as you think. It all boils down to representability.
Let's consider a simple example of 0.1. That value is not exactly representable in double. This is because double is a binary representation rather than a decimal representation.
A double value is stored in the form s*2^e, where s and e are the significand and exponent respectively, both integers.
Back to 0.1. That value cannot be exactly represented as a binary floating point value. No combination of significand and exponent exist that represent it. Instead the closest representable value will be used:
0.10000 00000 00000 00555 11151 23125 78270 21181 58340 45410 15625
If this comes as a shock I suggest the following references:
Is floating point math broken?
http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html
http://floating-point-gui.de/
So, what to do? An obvious option is to switch to a decimal rather than binary representation. In Delphi that typically means using the Currency type. Depending on your application that might be a good choice, or it might be a terrible choice. If you wish to perform scientific or engineering calculations efficiently, for instance, then a decimal type is not appropriate.
Another option would be to look at how Python handles this. The repr function is meant, where possible, to yield a string with the property that eval(repr(x)) == x. In older versions of Python repr produced very long strings of the form 1.1000000000000001 when in fact 1.1 would suffice. Python adopted an algorithm that finds the shortest decimal expression that represents the floating point value. You could adopt the same approach. The snag is that the algorithm is very complex.
Studying for a test right now and can't seem to wrap my head around when to use "V" for a decimal instead of an actual decimal in PIC clauses. I've done some research but can't find anything I understand. Only been learning cobol for about a week, so is there like a rule of thumb here? Thanks for your time.
You use an actual decimal-point when you want to "output" a value which has decimal places, like a report line, a position on a screen, an item in an output file which is going to a "different" system which doesn't understand the format with an implied decimal pace.
That's what the V is, it is an implied decimal place. It tells the compiler where to align results from calculations, MOVEs, whatever. Computer chips, and the machine instructions they support, don't know about actual decimal points for their internal processing.
COBOL is a language with fixed-length fields. The machine instructions don't need to know where the decimal point is (effectively it can deal with everything as integer values) but the compiler does, and the compiler has to do the correct scaling and alignment of results.
Storing on your own files, use V, the implied decimal place.
For data which is to be "human readable" or read by a system which cannot understand your character set, cannot scale what looks like an integer, use an actual decimal-point, . (for computer-readable stuff, you can sometimes use a separate scaling factor, if that is more convenient for the receiving system).
Basically, V for internal, . for external, should be a rule of thumb to get you there.
Which COBOL are you using? I'm surprised it is not covered in your documentation.