Pages

Wednesday, October 25, 2023

Calculating the sine, cosine, and tangent

I was asked what is the best way to calculate the sine for a number within a RPG program? I know my answer frustrated the person who asked as I replied with "It depends".

I can think of two scenarios that I would use different approaches for:

  1. Calculate the sine for a value in a file or table's field or column when "reading" the file or table
  2. Calculate the sine for a value calculated within a program

In my examples I am going to also show how to calculate the cosine and tangent too.

 

When "reading" the file

In this scenario I have a value in a column, NUMBER, in a SQL DDL Table, TESTABLE, and I want to calculate the cosine, sine, and tangent for every row in the table.

I am going to use SQL to fetch data from the file for two reasons. Firstly fetching data from a table, or file, can be done much faster when I use the multi row fetch.

Fortunately there are three SQL Scalar functions that will do the calculations for me. These being:

  • COS:  Calculate cosine
  • SIN:  Calculate sine
  • TAN:  Calculate tangent

I can show these at work with the following Select statement:

01  SELECT NUMBER,
02         TAN(NUMBER) AS "Tangent",
03         COS(NUMBER) AS "Cosine",
04         SIN(NUMBER) AS "Sine"
05    FROM TESTTABLE

In my results I want to show the original value, then the tangent, cosine, and sine for that number.

The results look like:

Number  Tangent               Cosine                 Sine
------  -------------------   -------------------    ------------------
   2.5  -0.7470222972386603   -0.8011436155469337    0.5984721441039565

I try not to read files or tables any more with RPG's native I/O operation codes. Instead I would define a SQL cursor and then perform a multi row fetch. The definition for my cursor would be:

01  dcl-ds Data qualified dim(*auto : 9999) ;
02    Number packed(5:2) ;
03    Tangent packed(20:15) ;
04    Cosine packed(20:15) ;
05    Sine packed(20:15) ;
06  end-ds ;

07  exec sql DECLARE C0 CURSOR FOR
08             SELECT NUMBER,
09                    TAN(NUMBER),COS(NUMBER),SIN(NUMBER)
10               FROM TESTTABLE
11           FOR READ ONLY ;

Lines 1 – 6: I know this is not the cursor. This is the data structure array the results of the Fetch goes into.

Line 1: This is defined as an "auto expanding" array, the number of elements it has will be the same as the number of the rows fetched from the cursor.

Lines 3 – 5: With trial and error I have found the best size of the data structure array subfields for the cosine, sine, and tangent is a packed 20,15.

Lines 7 - 11: The cursor definition. The Select statement is the same as the one I used above. I have the "FOR READ ONLY" so that others know I will not being updating this cursor, and I am sure it is faster.

 

Calculate within the program

By program I mean a RPG program. I am going to use C external procedures to perform the calculations for me. The three C external procedures I am going to use are:

  • cos():  Cosine
  • sin():  Sine
  • cos():  Tangent

Without further ado, let me start by showing the definition part of my program:

01  **free
02  ctl-opt dftactgrp(*no) ;

03  dcl-pr CalcSine float(8) extproc('sin') ;
04    *n float(8) value ;
05  end-pr ;

06  dcl-pr CalcCosine float(8) extproc('cos') ;
07    *n float(8) value;
08  end-pr ;

09  dcl-pr CalcTangent float(8) extproc('tan') ;
10    *n float(8) value ;
11  end-pr ;

12  dcl-s Result float(8) ;
13  dcl-s Value like(result) ;

14  Value = 2.5 ;

Line 1: My code is always totally free format RPG.

Line 2: As I am calling procedures I cannot execute this program in the default activation group. Therefcre, I need to use the control option to say that I will not.

Lines 3 - 5: This is the procedure prototype for the sine external function. I have given this procedure a name CalcSine which I will use in place of its original name, which is placed in the EXTPROC keyword. The procedure uses floating point numbers. The passed parameter is on line 4, as it has the VALUE keyword its contents are passed as a value. The returned parameter is on line 3, define before the EXTPROC.

Lines 6 – 8: Procedure prototype of the external prototype to calculate the cosine. I have given the procedure my own name. The parameters, passed and returned, are the same as the previous procedure prototype.

Lines 9 – 11: Procedure prototype for the tangent external prototype. Like the other two I have given this one a new name. Parameters are the same as the other procedures.

Lines 12 and 13: I have defined two variables as floating point.

Line 14: I move the value of 2.5 to the variable Value.

Now for the call to calculate the cosine:

15  Result = CalcCosine(Value) ;
16  dsply ('Cosine: float = ' + %char(Result)) ;
17  dsply ('Cosine: number = ' + %char(%dec(Result:20:15))) ;

Line 15: I call the external procedure to calculate the cosine, passing it the value in the variable Value, and the result is placed in the variable Result.

Line 16: I am using the Display operation code, DSPLY to show the value in the result variable, which will be a floating point value.

Line 17: I have placed a Decimal built in function, %DEC, within the %CHAR, to convert the floating point value to a decimal one.

This shows:

DSPLY  Cosine: float = -8.011436155469337E-001
DSPLY  Cosine: number = -.801143615546933

The first line is the floating point version of the result. The second is the decimal equivalent.

Next are the lines that call the sine calculation program, and show its result.

18  Result = CalcSine(Value) ;
19  dsply ('Sine: float = ' + %char(Result)) ;
20  dsply ('Sine: number = ' + %char(%dec(Result:20:15))) ;

Line 18: Call the sine external procedure, passing to it the value in Value, and placing the returned result into Result.

Lines 19 and 20: Will display the result as floating point and decimal:

DSPLY  Sine: float = +5.984721441039565E-001
DSPLY  Sine: number = .598472144103956

Lastly the code to calculate the tangent:

21  Result = CalcTangent(Value) ;
22  dsply ('Tangent: float = ' + %char(Result)) ;
23  dsply ('Tangent: number = ' + %char(%dec(Result:20:15))) ;

Do I really need to describe this as it is the same as the last two snippets?

The results are:

DSPLY  Tangent: float = -7.470222972386603E-001
DSPLY  Tangent: number = -.747022297238660

 

As I have shown "it depends" was the correct answer as it does depend on when I want to calculate the sine. In English English we have the phrase "horses for courses", this means that I pick the method to get the best result.

 

 

This article was written for IBM i 7.5, and should work for some earlier releases too.

No comments:

Post a Comment

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.