RISC World

Programming for non programmers

Part 11 - Real VAT calculations

David Holden continues his series.

I had intended to continue with some more 'real' Wimp programming in this session and show how you put an icon on the iconbar and create a Menu. However, someone has pointed out that the actual results the program produces are not what is really required to be of real use, and so I have decided to concentrate on that aspect. This is something I was aware of, but had ignored, but now it's been pointed out I had better correct the errors.

Although the VAT program works, and it's been a useful tool for teaching, as a practical way of calculating VAT it's not very effective. This is because it performs an exact calculation (or nearly, as you will see later), but in reality VAT isn't calculated that way.

For example, if the price of an item is 1 + VAT then, with VAT at 17.5% the VAT should be 17.5p, but in fact it isn't. This is because VAT must always be an exact number of pence, so in this case it would be 'rounded down' and you would pay 1 + 17p or 1.17.

There's a further complication. If the price was 1.02 + VAT then the VAT would be 17.5% of 1.02, which is 17.85p. So would the VAT be rounded up to 18p or rounded down to 17p? Unfortunately the answer is 'either'. Believe it or not there is no definite rule as to whether VAT should be rounded to the nearest whole penny or just rounded down. A supplier can use either method as long as he always does it the same way. So if you purchase something at 1.02 + VAT you could be charged 1.19 or 1.20, either is perfectly legitimate by VAT rules.

For the present we shall use the 'rounding down' method. So, in theory, all we have to do is 'throw away' all the fractional part of the pence. This sounds easy, but how is it done in practice?

There are two ways of doing this. It is possible to define the way that BASIC converts numbers to strings. There is a special variable @% which sets how numbers are converted and PRINTed. @% is a normal integer variable, and its main use is in non-Wimp programs where numbers have to be PRINTed on screen in tables or columns. Because @% operates when numbers are converted to strings using STR$ it would be possible to use it for our present purpose as it can be set to convert to a fixed 2 decimal place format. However, there's another way, and one which is slightly better in this case.

You have seen how when converting a floating point number to an integer the fractional part gets chopped off, leaving just the whole number. In the VAT calculator we can carry out all the calculations in whole numbers by multiplying the pounds and pence by 100 so that we are working in pence only. We will then have to divide the results by 100 to turn them back into pounds and pence again. Not only will this get rid of all those fractions of pence but by writing special routines to produce the results we can ensure that they are always neatly formatted with two decimal places in the conventional manner.

To illustrate this look at the following simple program.

    INPUT "Enter cash - " in$
    cash_in = VAL(in$)
    cash% = cash_in * 100
    PRINT cash%/100

This is very straightforward and can be found on the CD in the archive BEGINPRG in the SOFTWARE directory as Prog_11_1. The floating point variable cash_in is multiplied by 100 and placed in the variable cash%. To print this out as pounds and pence we just have to divide it by 100 again.

To go one step further and print out the plus VAT amount we just have to multiply by 1.175.

    INPUT "Enter cash - " in$
    cash_in = VAL(in$)
    cash_in = cash_in * 1.175
    cash% = cash_in * 100
    PRINT cash%/100

This is program Prog_11_2 on the CD. If you Run this and enter a value of '1' you will get the answer '1.17', or one pound and seventeen pence, which is correct.. But what happens if you enter '2' ? The answer we want is '2.35' because this time there will be no fractional part (2 x 17.5p = 35p), but instead you will get '2.34'.

What's going wrong?

The problem is not with the program but with BASIC. The routine which converts between real and integer variables and, more importantly, between real variables and strings, that is, STR$, is not 100% accurate. In fact, the results are often fractionally less than they should be. The inaccuracy is extremely small, around 0.00000001, but it's enough to cause us problems.

Look at the this illustration

This is just simple BASIC typed directly into a Task Window. The floating point variable 'A' is set to 47.1, and then STR$ is used to convert it to a string and PRINT it. Instead of '47.1' what STR$ produces is '47.09999999'. This is a well known problem with BBC BASIC, not only BASIC V, which is built into RISC OS, but also BASIC VI as well. BASIC V will return a number that is too small, and BASIC VI will return one that is too big. Although this can be serious under some circumstances luckily it's easily fixed when you are aware of it with a program which does not require absolute accuracy.

Since we only require accuracy to two decimal places and the error is only something in the order of 0.0000001 we can eliminate it by just adding a small amount, say 0.0001.

    INPUT "Enter cash - " in$
    cash_in = VAL(in$)
    cash_in = (cash_in * 1.175) + 0.0001
    cash% = cash_in * 100
    PRINT cash%/100

With this version (Prog_11_3) if you enter '2' the result will be correct, that is '2.35'. The addition of 0.0001 in line 3 to the floating point variable will ensure that when it is converted to an integer on the next line it is slightly larger than the whole number.

It should now be possible to do the same thing with a routine to subtract VAT.

    INPUT "Enter cash - " in$
    cash_in = VAL(in$)
    cash_in = (cash_in / 1.175) + 0.0001
    cash% = cash_in * 100
    PRINT cash%/100

The only change with this version is in line 3 where instead of multiplying by the VAT amount (1.175) we divide.

This program can be found on the CD as Prog_11_4. It actually works as you'd expect, but there is a problem, and the problem is not with the program but illustrates an anomaly with VAT when the 'rounding down' method is used. If you enter a value of '2' then you will find that it returns a result of '1.7', or one pound 70 pence. If you now run Prog_11_3 and enter a value of '1.7' the result will be not '2' but '1.99'. So, if you subtract VAT from a VAT inclusive price of two pounds you get 1.70, but of you add VAT to 1.70 you get not two pounds but 1.99. How?

The answer, as you might guess, is not due to any inaccuracies in the programs or calculations but with the rounding method used to calculate VAT. If you use a pocket calculator to do the same calculation then -

    2 divided by 1.175 = 1.7021276

Now, even retaining the full accuracy of this result if we calculate the VAT we get -

    1.7021276 x 17.5% = 0.2978723

If we now round down the VAT to the nearest penny that's 29p, and 29 + 170 = 1.99.

To make the 'subtract VAT' routine accurate it is going to have to compensate for this. However, I'm going to leave the problem for the present. I shall come back to this point in the next session, but meanwhile think of it as a test and see if you can find a way to solve the problem yourself.

Tidying the output

We can now calculate the results and produce a result to 2 decimal places, but the result will often not appear quite as we would like it. Cash is usually shown and 'pounds-dot-pence' so one pound and twenty pence would be '1.20' whereas using STR$ would produce '1.2'. As I said earlier, we could use @% to modify this, but here is an opportunity to learn some more BASIC keywords and employ a method which can be used for a variety of other purposes as well.

What we need to do is create a string which consists of the number of pounds, then a decimal point, then the number of pence. We also want the number of pence to always consist of two figures. The first stage is to turn out result, which is in pence, into pounds and pence. Luckily BASIC has two functions, DIV and MOD, which are ideal for this purpose.

DIV performs an integer divide, that is, it produces a result which is always a whole number. Using the floating point divide -

    10 / 3 = 3.3333333
    5 / 2 = 2.5

but if we use DIV -

    10 DIV 3 = 3
    5 DIV 2 = 2

The fractional part (or 'remainder') of the answer is always discarded.

MOD is used to extract only the 'remainder' part after an integer division, so -

    5 MOD 2 = 1
    5 MOD 3 = 2
    9 MOD 3 = 0
    13 MOD 5 = 3

Applying DIV and MOD we can extract the pounds and pence since -

    cash DIV 100 = pounds
    cash MOD 100 = pence

The modified add VAT routine therefore becomes -

    INPUT "Enter cash - " in$
    cash_in = VAL(in$)
    cash_in = (cash_in * 1.175) + 0.0001
    cash% = cash_in * 100
    PRINT STR$(cash% DIV 100) + "." + STR$(cash% MOD 100)

You will see that the work is all done in the final line. The string to be PRINTed consists of three parts, all joined together. The first part is the pounds, cash% DIV 100, then the decimal point ".", then the pence, cash% MOD 100. But once again there is a problem.

Consider what happens if the answer is 1.20. The two parts of the string would be '1' and '20' and the resulting string would be '1.20', so all is well. But what if the result was 1.02? The two parts of the string would now be '1' and '2', so the resulting string would be '1.2', not at all what we want.

What is required is a method of including leading zeros in a string, so if the number consists of just a single digit it is always preceded by '0'.

One way of doing this would be to check if the number of pence was less that 10 and add a leading zero if it was -

  pence% = cash% MOD 100
  IF pence% <10 THEN pence$ = "0"+STR$(pence%) ELSE pence$ = STR$(pence%)

This would work in the present circumstances, but it's rather specialised. It would also only produce a single zero if there were no pence whereas we are used to seeing two. We could add yet another comparison to check if pence% was equal to zero, but this starts to get a bit clumsy, and it would be nice to have a system that could be made to work in all circumstances and which could be easily modified if we wanted a different number of digits.

The easiest way to do this is to create a string of zeros at least as long as the number of digits we require and then add the result of the MOD function to the end of this string. If we then 'cut out' the number of digits required from the right-hand end of the string we will always get the required number of digits with leading zeros.

For example, assume we want three decimal places and the fractional part of the division is 12. If we take a string consisting of 3 zeros, then add the string '12' to it we get -


Now if we can 'chop off' the three right hand characters we end up with -


This is exactly what is required. You can substitute any decimal numbers you like, from '0' to '999', you will always get a 3 digit string padded with leading zeros.

Using RIGHT$ and LEFT$

Luckily BASIC has a function to do this job for us. The function is RIGHT$, and it (normally) takes two parameters, the string to be manipulated, and the number of characters we want returned from its right hand end. So, in the example above -

   results$ = RIGHT$("00012",3)

would make result$ equal to '012'.

As usual the parameters passed to the function can be expressions or variables. So, to get the result shown in exactly the way we want it we could modify the last lines of the program thus.

   pence$ = "00" + STR$(cash% MOD 100)
   PRINT STR$(cash% DIV 100) + "." + RIGHT$(pence$,2)

Or if you wanted to make it more compact you could condense it into a single line.

   PRINT STR$(cash% DIV 100) + "." + RIGHT$("00" + STR$(cash% MOD 100),2)

You will find this in the program Prog_11_6, and you will see that it produces exactly the results we require. If you enter '1.71' then it will show the result as '2.00', and if you enter '0.87' it will show '1.02'.

As you might expect there is another BASIC function, LEFT$, which works in exactly the same way as RIGHT$ but returns characters from the left hand end of a string. There is also yet another, MID$, which will return characters from any other place in a string, but we will deal with that later.

Although RIGHT$ and LEFT$ normally take two parameters, the string and the number of characters you want returned, if the second parameter is omitted it will return just a single character, so -

   a$ = LEFT$("this is a string")

would make a$ hold just the letter 't', whereas

   a$ = LEFT$("this is a string",3)

would make a$ 'thi'.

You should now have all the information you require to enable you to modify the main Wimp VAT calculator to incorporate the things I've introduced in this session so that the results are properly formatted.

David Holden