Friday, November 10, 2017

INTQUANT and REALQUANT

Almost every programming language has at least two kinds of numbers: integers and reals. In the old 16 bit days, an unsigned integer could have a value between 0 and 65535 (that's 2 to the power of 16, less 1), whereas a signed integer could have a value between -32768 and 32767 (215 − 1). In these days of 32 bits, an integer can have a value between −2,147,483,648 (−231) and 2,147,483,647 (231 − 1). An integer does not have a fractional part; for this, one uses real numbers, which have a range of −238 to 238. Whilst real numbers have a much larger range than integers, they are also less accurate: there are certain fractions which are very difficult to store in binary. This lack of accuracy causes problems when real numbers are multiplied, and so some languages store real numbers as 'decimal shifted' integers (i.e. 3.333 is stored as 3333). This is very important for applications which deal with money as no accuracy is sacrificed.

As Priority deals with both monies and stock quantities, it provides integers, reals and decimal shifted integers. As usual, the documentation is not too clear how to use the different types, so this blog is intended to clarify matters, especially with regard to decimal shifted integers. The TRANSORDER (inventory movement) table - as well has other tables - contains a few fields whose name is like QUANT (quant, tquant, cquant); in the table directory screen, these fields are defined as integers with a width of 17.3. This means that they are stored as integers but their values should be shifted three columns to the left in order to obtain their real value; as written above, a quantity of 3.333 would be stored as 3333.

The screen manager knows automatically how to display such fields (3000 would be displayed as 3.00) so the beginning programmer doesn't have to convert anything. The problems begin when one wants to calculate something based on such a field: for example, the value of inventory is calculated by multiplying the amount in inventory by the part's cost. The unaware programmer will write this statement without change: if the amount is 3, then it will be stored as 3000, and multiplying by the cost (let's say 20 shekels/unit) would result in the value of the inventory being 60,000 shekels! Obviously the quantity has to be converted to a real number before multiplying, so one would actually write REALQUANT (TRANSORDER.QUANT) * PART.COST. This will give the desired result, 60 shekels.

The result of such a calculation should be stored in a variable which has been defined as a real. The easiest way to do this in a procedure is to initialise the variable in the following manner:
:VALUE = 0.0; /* this tells the parser that 'value' is a real */ SELECT REALQUANT (TRANSORDER.QUANT) * PART.COST INTO :VALUE FROM TRANSORDER, PART WHERE TRANSORDER.PART = PART.PART AND ...
Sometimes it is necessary to store the result of such a calculation back into a decimal shifted integer: for this one uses the INTQUANT function, which as its name suggests, turns a quant into an int. I've had to use this function recently as I was writing code which creates bills of materials; a quantity field in the interface requires a decimal shifted number which is calculated on the basis of a real number and another decimal shifted number. INTQUANT requires its operand to be a real number, so the easiest way of ensuring this is to include 0.0+ in the expression, as follows:
:NEWQUANT = INTQUANT (0.0 + :COEF * REALQUANT (:QUANT));
The variable :COEF has already been defined as a real (for example, this would hold the value of PARTARC.COEF, which defines the number of 'son parts' to its 'father part') whereas :QUANT would be a decimal shifted integer, such as ORDERITEMS.QUANT.

The parser in Priority procedures can be a bit stupid, so one should give it all the help one can by predefining variables. Otherwise one is liable to receive the error message that :VARIABLE has been defined having two different types.

I tend to use REALQUANT when I am writing cursors, so that I know that I am working with a real number; I find this makes life a bit easier. For example,
DECLARE CUR CURSOR FOR SELECT REALQUANT (TRANSORDER.QUANT), PART.COST FROM TRANSORDER, PART WHERE TRANSORDER.PART = PART.PART AND ...
Basic rule: never write an arithmetical expression using decimal shifted numbers without REALQUANT! That's about all there is to say about INTQUANT and REALQUANT.

No comments: