Gobsmacked by ksh93 floating point arithmetic.

Hi guys...
I am working on limited basic set of maths routines for ksh93 that can be sourced as . ./ksh_math.sh and I am gobsmacked by its capabilities.
Although some big guns may already know this, I didn't, and ksh93 is easily able to do floating point numbers to floating point powers and floating point roots.
WWOOWW!!
It is so simple to get the floating point NTH root of a floating point number, within limits of ksh93's floating point precision and rounding capabilities.
I started it earlier today and this is how far I have gotten:

#!/bin/ksh
# ksh_math.sh
# Basic Math extensions.

# CONSTANTS.
PI=3.14159265358979323
e=2.71828182845904523

# NTH ROOT of a positive floating point number.
# Called as:
# NthRoot NUMBER NTHROOT PRECISION
# ALL POSITIVE VALUES.
# NUMBER and NTHROOT can be floating point, PRECISION is an integer from 1 to 16.
# Returns NTHROOT as a variable.
NthRoot()
{
	NUMBER=$1
	NTHROOT=$2
	PRECISION=$3
	if [ "$PRECISION" = "" ] || [ $PRECISION -lt 1 ]
	then
		PRECISION=5
	fi
	NTHROOT=$( printf "%.${PRECISION}f" "$(( ${NUMBER}**(1.0/${NTHROOT}) ))" )
}

# SQUARE ROOT of a positive floating point number.
# Called as:
# Sqrt NUMBER PRECISION
# ALL POSITIVE VALUES.
# NUMBER can be floating point, PRECISION is an integer from 1 to 16.
# Returns SQRT as a variable.
Sqrt()
{
	NUMBER=$1
	NTHROOT=2.0
	PRECISION=$2
	NthRoot $NUMBER $NTHROOT $PRECISION
	SQRT=$NTHROOT
}

Sqrt 813.7173 10
echo ""
echo "Floating Point Square Root:"
echo "KSH93 FP maths routines = $SQRT."
echo "From Google calculator = 28.5257304902."
echo ""
echo "Floating Point Number And Nth Root:"
NthRoot 10.5 12.3 11
echo "KSH93 FP maths routines = $NTHROOT."
echo "From Google calculator = 1.21066369814."
echo ""

Results so far on OSX 10.13.6, default bash terminal calling ksh from the script:

Last login: Mon Oct 15 16:59:39 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./ksh_math.sh

Floating Point Square Root:
KSH93 FP maths routines = 28.5257304902.
From Google calculator = 28.5257304902.

Floating Point Number And Nth Root:
KSH93 FP maths routines = 1.21066369814.
From Google calculator = 1.21066369814.

AMIGA:amiga~/Desktop/Code/Shell> _

Will be working on basic TRIG functions next.
Enjoy...

A quiet reminder that ksh93 is very rare, and that shell isn't an efficient or readable language for mass computation. Carry on.

ksh93 also has date arithmetic.

1 Like

Notice that the Korn shell doesn't need that: There is a variable FPATH, which works quite like PATH, but for functions: You set it to a (list of) directory/ies and every function not yet defined will be searched there. It is possible to build "libraries" that way by collecting functions into a certain directory.

In fact i use this feature heavily in my scripts. Here is my "standard script template":

#! /bin/ksh
# ------------------------------------------------------------------------------
# script-template                                       template for scripts
# ------------------------------------------------------------------------------
# Author.....: 
# last update: 2000 00 00    by: 
# ------------------------------------------------------------------------------
# Revision Log:
# - 0.xx   2000 00 00   Original Creation
#                       - <keyword here>
#
# ------------------------------------------------------------------------------
# Usage:
#
#
#     Example:
#
# Prerequisites:
#
# ------------------------------------------------------------------------------
# Documentation:
#
#     Parameters:
#     returns:
# ------------------------------------------------------------------------------
# known bugs:
#
#     none
# ------------------------------------------------------------------------------
# ................................(C) 2000 ... .................................
# ------------------------------------------------------------------------------

if [ -z "$DEVELOP" ] ; then                      # set environment
     . /usr/local/lib/ksh/f_env
else
     . ~/lib/f_env
fi

                                                 # local variables
typeset chUsageShort="usage: $0 [-(?|h)] | [-V] [-S]"

typeset achUsageLong[0]="$chUsageShort"
typeset achUsageLong[1]=" "
typeset achUsageLong[2]="Where         means"
typeset achUsageLong[3]="   -?|h[elp]  display this help"
typeset achUsageLong[4]="   -V         verbose, fulldebug mode"
typeset achUsageLong[5]="   -S         simulation mode, cmds only displayed"

typeset    chProgBase="${chProgName##*/}"        # ^= basename $0

typeset    chOpt=""


while getopts ":hVS" chOpt ; do                    # process commandline
     case "$chOpt" in
          h)                                       # display help
               f_usage full
               ;;

          "?")
               if [ "$chOpt" == "?" -a "$OPTARG" == "?" ] ; then
                    f_usage full
               else
                    f_die 1 "unknown option -${OPTARG}"
               fi
               ;;

          V)                                     # fulldebug mode
               export chFullDebug='set -xv'
               ;;

          S)                                     # simulation mode
               SIMULATE='print -'
               ;;

     esac
done

<your code here>
# -- EOF template.sh

where f_usage and f_env are functions in this library. Here is f_env :

# ------------------------------------------------------------------------------
# f_env()                                set the environment to a defined state
# ------------------------------------------------------------------------------
# Author.....: Wolf Machowitsch
# last update: 2001 08 08    by: Wolf Machowitsch
# ------------------------------------------------------------------------------
# Revision Log:
# - 0.99   1999 01 21   Original Creation
#                       -
#
# - 1.00   1999 03 12   Production release
#                       Reviewed version. chFullDebug can now be set via
#                       the commandline outside the script too. However,
#                       in this case the value of $chFullDebug is not
#                       checked for validity.
#                       -
#
# - 1.01   1999 04 18   Developers switch
#                       f_env now scans the environment for a variable
#                       $DEVELOP. If it is non-null, then FPATH is not
#                       set to "/usr/local/lib/ksh" but to "~/lib".
#                       This way it is possible to test new developments
#                       for the library before migrating them to the
#                       production environment.
#
# - 1.10   1999 05 01   bugfix version: system environment used
#                       /etc/environment is now parsed in in f_env().
#                       Not to do this would leave variables like ODMDIR
#                       unset. Some AIX processes rely on this.
#
# - 1.20   1999 05 10   Log- and Error-file
#                       support added for error- and logfile as used in
#                       the f_Cmd*-functions.
#
# - 1.30   2000 03 07   User- and Host-information
#                       to support the automated mailing facility some
#                       info about effective UID, hostname, etc. is
#                       retrieved.
#
#
# - 1.40   2001 05 23   Linux ready
#                       the 'uname' command is now used to find out the
#                       OS we're running. Accordingly either /etc/profile
#                       or /etc/environment is sourced in and a variable
#                       is set.
#
# - 1.41   2001 08 08   bugfix
#                       since in Linux '/bin' is not a link to '/usr/bin'
#                       (like in AIX) '/bin' is now included in the path.
#
# - 1.50   2015 09 24   HMC-List
#                       added a list of known HMCs to use for f_GetHostList()
#
# ------------------------------------------------------------------------------
# Usage:
#     f_env() is to be PARSED into the scripts environment. The following
#     piece of code shows how to use it.
#
#     Example:
#     #!/bin/ksh
#     # example script for using f_env()
#     . /usr/local/lib/ksh/f_env
#     # ---- here goes the rest of your code -----
#     exit
#
# Prerequisites:
# ------------------------------------------------------------------------------
# Documentation:
#     f_env() is intended to be used at the beginning of scripts to make
#     the environment always the same for every script instead of being
#     dependant of the environment the developer has set in his shell.
#     To use this script it has to be PARSED rather than called as a
#     function.
#
#     Parameters: void
#     returns:    void
# ------------------------------------------------------------------------------
# known bugs:
#
#     none
# ------------------------------------------------------------------------------
# ......................(C) 99 Wolf Machowitsch ................................
# ------------------------------------------------------------------------------

if [ -z "$NEVER_USE_THIS_VAR" ] ; then           # are we called recursively ?

     unset ENV                                   # clear the environment

     #---------------------------------------------------- set basic environment

     typeset -x OS=$(uname -a | cut -d' ' -f1)   # find out the OS
                                                 # read in standard environment
     case "$OS" in
          AIX)
               . /etc/environment
               ;;

          Linux)
               . /etc/profile
               ;;

          *)
               . /etc/environment
               ;;
     esac

                                                 # set default TERM variable
     case "$OS" in
          AIX)
               TERMDEF="$(termdef)"
               ;;

          Linux)
               TERMDEF="$TERM"
               ;;

          *)
               TERMDEF="$TERM"
               ;;

     esac
     typeset -x TERM=${TERMDEF:-'wyse60'}        # set default TERM variable

     typeset -x LANG=C                           # default language environment
     typeset -x EDITOR=vi                        # what else ? ;-))
     typeset -x VISUAL=$EDITOR

     typeset -x PATH="/usr/bin"                  # set the path
                PATH="$PATH:/bin"
                PATH="$PATH:/etc"
                PATH="$PATH:/usr/sbin"
                PATH="$PATH:/usr/ucb"
                PATH="$PATH:/sbin"
                PATH="$PATH:/usr/bin/X11"
                PATH="$PATH:/usr/local/bin"      # tools, home for scripts
                PATH="$PATH:/usr/local/sbin"     # -"-

     typeset -x chTmpDir=""                      # path for temporary files

     #---------------------------------------------------- debugging stuff
     if [ -z "$chFullDebug" ] ; then             # debug switch
          typeset -x chFullDebug=""
     else
          typeset -x chFullDebug                 # export if already set
     fi
     typeset -x SIMULATE=''                      # set to 'print' for
                                                 # simulation mode
     typeset -x TRACE=''                         # set to 'on' for trace mode

     if [ -z "$DEVELOP" ] ; then
          typeset -x FPATH="/usr/local/lib/ksh"  # set fnc path for fnc lib
          FPATH="$FPATH:/usr/local/bin"
          FPATH="$FPATH:/usr/local/sbin"
     else
          typeset -x FPATH=~/lib        # for lib development
     fi

     #---------------------------------------------------- global constants
     typeset -x chProgName="$0"                  # const. for main program name

     typeset -x chUsageShort=""                  # short usage message
     typeset -x achUsageLong[0]=""               # long usage message (line)

     if [ -z "$fLogFile" ] ; then                # log file
          if [ $(f_ImRoot ; print $?) -eq 0 ] ; then
               typeset -x fLogFile='/usr/local/log/system.log'
          else
               typeset -x fLogFile=~/system.log
          fi
     else
          typeset -x fLogFile
     fi
     if [ -z "$fErrFile" ] ; then                # error file
          if [ $(f_ImRoot ; print $?) -eq 0 ] ; then
               typeset -x fErrFile='/usr/local/log/system.err'
          else
               typeset -x fErrFile=~/system.err
          fi
     else
          typeset -x fErrFile
     fi

     #---------------------------------------------------- automated mail
     typeset -x chUser=$(id -nur)

     #-------------------------------------------------- site dependent includes

                                                 # HP Open View at xxx
     typeset -x AlertSW='OV'                     # alerting software
     typeset -x AlertCmd='/usr/lpp/OV/bin/opcmsg' # for f_Alert()
     typeset -x AlertApp=''                      # are defined in the script
     typeset -x AlertObj=''

                                                 # HMC list for xxx
     typeset -x CachHMC[1]="xxx-f-hmc1"
     typeset -x CachHMC[2]="xxx-f-hmc2"
     typeset -x CachHMC[3]="xxx-f-hmc3"
     typeset -x CachHMC[4]="xxx-f-hmc4"

     #-------------------------------------------------- reentrancy protection
     typeset -x NEVER_USE_THIS_VAR="KILROY_WAS_HERE"
fi

# --- EOF f_env

Notice that there is a variable "DEVELOP": set it to any value and the script will use not the library in /usr/local/lib/ksh but the one in ~/lib . This way you can have a personal copy of the lib and tinker with it without affecting other scripts using the same functions. Also notice that i have some logging functions which all write to a continuous log defined here ( /usr/local/log/system.log and /usr/local/log/system.err ).

If you are interested i can eventually publish more functions from my library.

@Corona: ksh93 is a widely used shell. In every AIX version since 5L it is included (as ksh93) and in most Linux distributions it is part of the installable base. AIX uses a ksh88 (as ksh and as sh) as the default shell since AIX 4 (about 1997).

I hope this helps.

bakunin

1 Like

Bakunin, why not using logger to log ?
On first glance, looks like the script(s) should stay portable, perhaps a bit shorter.

Just wondering is it by design or some limitation or feature lack of logger.

Regards
Peasant.

First, i tried hard to make the whole thing as independent from it surroundings as possible. Up to now, the library has worked on Linux (SLES, RHEL, centOS, Fedora, OpenVZ), Solaris, HP-Ux, and AIX while being developed mainly on AIX. It also works on both ksh88 and ksh93 and the most part of it even on bash. Introducing logger as a prerequisite would hurt that goal.

Second: i am terminally lazy! When i started the library aobunt 20 years ago logging was one of the first things i implemented. Since it worked (and worked well, for my purposes) i never got around to change it.

bakunin

Well this exercise in futility is coming along.

@bakunin
I like the template idea and will use it when I am happy with the results.

@all the viewers

In the code are two attempts at creating SINE(X).
The first uses ANGLE in degrees and this is my choice at the moment because:
1: The results are consistent to 8 decimal places to the Google calculator values. (NOTE: printf rounds at the 8th decimal place.)
2: Number 1: gives the correct result irrespective of ANGLEs greater than 360 degrees.
However...
3: The RADIAN method is good enough for the first quadrant but creeping errors start to begin beyond that due to PRECISION and rounding.
4: For RADIAN values greater than 2PI these creeping errors become large and when greater than 6PI even 5 decimal places is not good enough.
The RADIAN version is commented out along with test code so experimenters can see these anomalies for themselves.

# SINE(angle) in degrees.
# Called as:
# Sin ANGLE
# Where ANGLE is any positive floating point value.
# Returns SIN as a variable to 8 decimal places.
# Google sin(degrees) values for first quadrant.
#
# 0	0.00000000000
# 15	0.25881904510
# 30	0.50000000000
# 45	0.70710678118
# 60	0.86602540378
# 75	0.96592582628
# 90	1.00000000000
Sin()
{
	# SIN fixed to 8 decimal places.
	ANGLE=$1
	while [ $ANGLE -gt 360.0 ]
	do
		ANGLE=$(( $ANGLE-360.0 ))
	done
	if [ $ANGLE -ge 360.0 ]
	then
		ANGLE=0.0
	fi
	if [ $ANGLE -gt 270.0 ]
	then
		ANGLE=$(( $ANGLE-360.0 ))
	fi
	if [ $ANGLE -gt 180.0 ]
	then
		ANGLE=$(( -($ANGLE-180.0) ))
	fi
	if [ $ANGLE -gt 90.0 ]
	then
		ANGLE=$(( 180.0-$ANGLE ))
	fi
	Radian $ANGLE
	SIN=$(( $RADIAN-(($RADIAN**3)/6.0)+(($RADIAN**5)/120.0)-(($RADIAN**7)/5040.0)+(($RADIAN**9)/362880.0)-(($RADIAN**11)/39916800.0)+(($RADIAN**13)/6227020800.0) ))
	SIN=$( printf "%.8f" "$SIN" )
}

#Sin(radians).
#{
#	RADIAN=$1
#	while [ $RADIAN -gt $(( 2*$PI )) ]
#	do
#		RADIAN=$(( $RADIAN-2*$PI ))
#	done
#	if [ $RADIAN -ge $(( 2*$PI )) ]
#	then
#		RADIAN=0.0
#	fi
#	if [ $RADIAN -gt $(( (3*PI)/4 )) ]
#	then
#		RADIAN=$(( $RADIAN-2*PI ))
#	fi
#	if [ $RADIAN -gt $PI ]
#	then
#		RADIAN=$(( -($RADIAN-$PI) ))
#	fi
#	if [ $RADIAN -gt $(( $PI/2 )) ]
#	then
#		RADIAN=$(( $PI-$RADIAN ))
#	fi
#	SIN=$(( $RADIAN-(($RADIAN**3)/6.0)+(($RADIAN**5)/120.0)-(($RADIAN**7)/5040.0)+(($RADIAN**9)/362880.0)-(($RADIAN**11)/39916800.0)+(($RADIAN**13)/6227020800.0) ))
#}


# COSINE(x)
# Called as:
# Cos RADIANS
# Returns COS as a variable.
Cos()
{
	:
}

# ANGLE to RADIAN.
# Called as:
# Radian ANGLE
# Returns RADIAN as a variable.
Radian()
{
	ANGLE=$1
	RADIAN=$(( ($ANGLE*$PI)/180.0 ))
}

# RADIAN to ANGLE.
# Called as:
# Angle RADIAN
# Returns ANGLE as a variable.
Angle()
{
	RADIAN=$1
	ANGLE=$(( ($RADIAN*180.0)/$PI ))
}

# **********************************
# Test radians.
#for x in $( seq 0 $(( $PI/12 )) $(( 6*PI )) )
#do
#	Sin x
#	printf "%u -> %.8f\n" "$x" "$SIN"
#done

# Test degrees.
for x in $( seq 0.0 15.0 360.0 )
do
	Sin x
	echo "$x -> $SIN"
done
# **********************************

Results, OSX 10.13.6, default bask terminal calling ksh.

Last login: Thu Oct 18 15:52:22 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./ksh_math.sh
0 -> 0.00000000
15 -> 0.25881905
30 -> 0.50000000
45 -> 0.70710678
60 -> 0.86602540
75 -> 0.96592583
90 -> 1.00000000
105 -> 0.96592583
120 -> 0.86602540
135 -> 0.70710678
150 -> 0.50000000
165 -> 0.25881905
180 -> 0.00000000
195 -> -0.25881905
210 -> -0.50000000
225 -> -0.70710678
240 -> -0.86602540
255 -> -0.96592583
270 -> -1.00000000
285 -> -0.96592583
300 -> -0.86602540
315 -> -0.70710678
330 -> -0.50000000
345 -> -0.25881905
360 -> 0.00000000
AMIGA:amiga~/Desktop/Code/Shell> _

Compare the angles with those from Google inside the code.

Interesting, a few obvious improvements.

  • You don't need $ on variable names inside (( ))
  • (( )) replaces [ ] for integer mathematics, especially float
  • The && construct will tremendously reduce redundancy here
  • You should use typeset to declare a local variable, otherwise you're unintentionally stomping on each other's ANGLE all the time
  • You should not be using global variables for all communication
  • You can make that giant equation one short loop
  • Not everything needs to be a function, many things are shorter and more readable as the single value they are
  • A bit more sanitization for negative input values
#!/bin/ksh

e=2.71828182845904523
PI=3.14159265358979323

TORADIANS=$(( PI/180.0 )) # Correction for degrees to radians
TODEGREES=$(( 180.0/PI )) # Correction for radians to degrees

# SIN fixed to 8 decimal places.
Sin()
{
        typeset ANGLE var # Tell KSH ANGLE is local
        typeset SIN var # Also local

        ANGLE="$1"
        SIN=0.0

        while (( ANGLE < 0.0 ))
        do
                (( ANGLE += 360 ))
        done

        while (( ANGLE >= 360.0 )) # Too bad KSH doesn't have fmod
        do
                (( ANGLE -= 360.0 ))
        done

        (( ANGLE > 270.0 )) && (( ANGLE -= 360 ))
        (( ANGLE > 180.0 )) && ANGLE=$((-(ANGLE-180)))
        (( ANGLE > 90.0  )) && ANGLE=$(( 180 - ANGLE ))

        (( ANGLE *= TORADIANS ))  # Convert to radians

        # Series coefficients, for the sum of A to the $1 divided by $2 for each pair
        # Sets $1=1, $2=1.0, $3=3, $4=-6.0, etc
        set --  1       1.0             \
                3       -6.0            \
                5       120.0           \
                7       -5040.0         \
                9       362880.0        \
                11      -39916800       \
                13      6227020800.0

        while [ "$#" -gt 0 ]
        do
                # We DO need $ here since $1, $2, etc are special
                (( SIN += (ANGLE**$1)/$2 ))
                shift 2 # Discard the first two arguments
        done

        printf "%.8f\n" "$SIN"
}

# Test degrees.
for x in $( seq 0.0 15.0 360.0 )
do
        printf "%d\t" $x
        Sin $x
done
1 Like

A version that doesn't need the table:

        F=1     # N factorial
        for ((N=1; N<=13; ))
        do
                (( SIN += (ANGLE**N)/F ))
                ((F *= ++N))
                ((F *= - ++N))
        done
1 Like

Apologies for any typos...

1: True but we had a discussion about it on this site WRT shellcheck and I decided I was going to keep '$' due to discrepancies of some combinations.

Here:

2: The ksh version is of 2007 vintage and I assumed double parentheses (in the method you have used them) to be unavailable, however...
3: I never even thought about the && construct as I had no idea my incarnation would even remotely give reasonably accurate results.
4: 'typeset' is duly noted and will be used in COSINE soon. Good point about stomping on its own variable. As this was a prototype that point hadn't crossed my mind.
5: I tend to use global variables a great deal, even in Python, my mind tends to work globally. <wink>
6: Using the 'set' statement like you have done is completely new to me and is cool. Logged in the old grey matter for future use.
7: True and AudioScope has the mixture.
8: Negative values; love it, nice-n-simple. I was more interested in my code snippet working and I never expected anyone to strip it and better it so quickly. I will now put your code in the script and take it from there.

Not sure whether to use a modified version of the sin() section of the code as a whole for COS(X) or use the SIN(X) script and call it as SIN(X+90) for the same result.

Will experiment and find out over the next day or so.

------ Post updated at 08:51 PM ------

Neat, but...
I don't quite understand how this works.
Surely ((F *= - ++N)) would overwrite ((F *= ++N)) , if NOT then I don't understand why?
Please explain...

May I jump in here: That's one of the "assignment operators"; A += B is to be read like A = A + B , so the "former" value of A is not lost / overwritten. Corona688 is using those abundantly in his above sin(x) script.

That rare ambiguity does not apply to any of these combinations. You are safe.

I can think of plenty of situations where leaving the $ in will cause problems, however!

You were already using them. They might even be mandatory.

It's a short form of F = F * N, so it doesn't overwrite it as much as advance it.

Since the factorials jump by two every loop, it needs to multiply twice.

One of them is inverted, to make the sign alternate every loop.

1 Like

Hi RudiC...
Longhand OSX 10.13.6, default bash terminal running ksh:

Last login: Thu Oct 18 22:04:50 on ttys000
AMIGA:amiga~> ksh
AMIGA:uw> F=1
AMIGA:uw> N=3
AMIGA:uw> (( F *= ++N ))
AMIGA:uw> echo "$F"
4
AMIGA:uw> (( F *= - ++N ))
AMIGA:uw> echo "$F"
-20
AMIGA:uw> _

You see my point...
'F' has been overwritten the 'echo' command shows it...
Something else is happening that I don't quite understand.
It must be something to do with 'for' statement for ((N=1; N<=13; )) where the increment of "STEP 2" is done inside the loop itself, ..... ++N effectively twice.
I didn't think that was possible in most languages as I have not seen it before.
If this is the case then I understand perfectly what is going on.

It did exactly what it was supposed to: Incremented N by 1, then F = F * N. By doing this repeatedly, you can calculate factorials like 1*2*3*4*5*6, which are the large numbers in the series of your sin equation.

Though with F=1 and N=3, you're calculating something like 5!/3!

1 Like

Hi C688...
This will probably attach itself to my previous...

Thanks, much like my reply above...

I can see it now.

The for-loop comes from C, where it follows the form

for(statement1;  statement2;  statement3) {
}

which does effectively this:

statement1;

while(statement2) {
      statement3;
}

It's usually used for(var=0; var<10; var++) but any valid expression will do. Most languages with for() loops allow them to be flexible like that, though few languages can cram as many things into one expression as C :wink:

In the BASH/KSH adaptation, you can put anything inside each statement that you might put inside a (( )) block, including nothing.

OK, using Corona688's code for SINE we now have Sin(), Cos(), NthRoot() and Sqrt() functions.
The 'e' and 'PI' constants are to 17 decimal places, NthRoot() and Sqrt() to 16 decimal places and Sin() and Cos() to 8 decimal places.
These precisions are good enough for basic mathematical tasks, after all KSH93 was not designed to send a spacecraft to Neptune. <wink>

#!/bin/ksh
# ksh_math.sh
# Basic Math extensions.

# CONSTANTS.
PI=3.14159265358979323
e=2.71828182845904523

# Degrees to radians.
TORADIANS=$(( PI/180.0 ))
# Radians to degrees.
TODEGREES=$(( 180.0/PI ))

# NTH ROOT of a positive floating point number.
# Called as:
# NthRoot NUMBER NTHROOT [PRECISION]
# ALL POSITIVE VALUES.
# NUMBER and NTHROOT can be floating point, optional PRECISION is an integer from 1 to 16.
NthRoot()
{
	# Make these local.
	typeset NUMBER var
	typeset NTHROOT var
	typeset PRECISION var

	NUMBER=$1
	NTHROOT=$2
	PRECISION=$3

	if [ "$PRECISION" = "" ] || [ $PRECISION -lt 1 ]
	then
		# My school log table(s) accuracy. ;o)
		PRECISION=4
	fi

	printf "%.${PRECISION}f\n" "$(( NUMBER**(1.0/NTHROOT) ))"
}

# SQUARE ROOT of a positive floating point number.
# Called as:
# Sqrt NUMBER [PRECISION]
# ALL POSITIVE VALUES.
# NUMBER can be floating point, optional PRECISION is an integer from 1 to 16.
Sqrt()
{
	# Make these local.
	typeset NUMBER var
	typeset NTHROOT var
	typeset PRECISION var

	NUMBER=$1
	NTHROOT=2.0
	PRECISION=$2

	NthRoot $NUMBER $NTHROOT $PRECISION
}

# SINE(angle) in degrees.
# Called as:
# Sin some_angle
# Google 'sin X degrees' values for first quadrant.
#
# 0	0.00000000000
# 15	0.25881904510
# 30	0.50000000000
# 45	0.70710678118
# 60	0.86602540378
# 75	0.96592582628
# 90	1.00000000000
#
# Courtesy of Corona688 translated and improved considerably from shell code I wrote.
Sin()
{
	# Make these local.
	typeset ANGLE var
	typeset SIN var

	# ANGLE used instead of RADIANS for consistency in accuracy.
	ANGLE="$1"
	SIN=0.0

	# Compensate for angles less the 0.0 degrees.
	while (( ANGLE<0.0 ))
	do
		(( ANGLE+=360.0 ))
	done

	# Ditto for angles greater than 360.0 degrees.
	while (( ANGLE>=360.0 ))
	do
		(( ANGLE-=360.0 ))
	done

	# Correct for the four quadrants.
	(( ANGLE>270.0 )) && (( ANGLE-=360 ))
	(( ANGLE>180.0 )) && ANGLE=$(( -(ANGLE-180) ))
	(( ANGLE>90.0 )) && ANGLE=$(( 180-ANGLE ))

	# Now convert to RADIANS.
	(( ANGLE*=TORADIANS ))

	# This loop is to calculate this Taylor series:
	# RADIAN-((RADIAN**3)/6.0)+((RADIAN**5)/120.0)-((RADIAN**7)/5040.0)+((RADIAN**9)/362880.0)-((RADIAN**11)/39916800.0)+((RADIAN**13)/6227020800.0) ... etc.
	# Factorial part 'F'.
	F=1
	for (( N=1; N<=13; ))
	do
		(( SIN+=(ANGLE**N)/F ))
		(( F*=++N ))
		(( F*=-(++N) ))
	done

	# Precision returned to 8 Decimal places.
	printf "%.8f\n" "$SIN"
}

# COSINE(angle) in degrees.
# Called as:
# Cos some_angle
Cos()
{
	# Make this local.
	typeset COS var

	COS=$(( $1 + 90.0 ))
	# Call the 'Sin()' function.
	Sin $COS
}

# ****************************************************
#
# All of below will be removed when finished.
# Test degrees.
echo ""
echo "SINE and COSINE of an ANGLE:"
echo ""
for x in $( seq 0.0 15.0 360.0 )
do
	MYSIN=$( Sin x )
	printf "%s" "SIN $x -> $MYSIN"
	MYCOS=$( Cos x )
	printf "%s\n" "		COS $x -> $MYCOS"
done

# Tests nth roots...
SQRT=$( Sqrt 813.7173 10 )
echo ""
echo "Floating Point Square Root:"
echo "KSH93 FP maths routines = $SQRT."
echo "From Google calculator = 28.5257304902."
echo ""
echo "Floating Point Number And Nth Root:"
NTHROOT=$( NthRoot 10.5 12.3 11 )
echo "KSH93 FP maths routines = $NTHROOT."
echo "From Google calculator = 1.21066369814."
echo ""
#
# ****************************************************

Results on OSX 10.13.6, default bash terminal calling ksh.
Note the code is sourced into current ksh shell...

Last login: Sat Oct 27 12:49:46 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ksh
AMIGA:uw> . ./ksh_math.sh

SINE and COSINE of an ANGLE:

SIN 0 -> 0.00000000		COS 0 -> 1.00000000
SIN 15 -> 0.25881905		COS 15 -> 0.96592583
SIN 30 -> 0.50000000		COS 30 -> 0.86602540
SIN 45 -> 0.70710678		COS 45 -> 0.70710678
SIN 60 -> 0.86602540		COS 60 -> 0.50000000
SIN 75 -> 0.96592583		COS 75 -> 0.25881905
SIN 90 -> 1.00000000		COS 90 -> 0.00000000
SIN 105 -> 0.96592583		COS 105 -> -0.25881905
SIN 120 -> 0.86602540		COS 120 -> -0.50000000
SIN 135 -> 0.70710678		COS 135 -> -0.70710678
SIN 150 -> 0.50000000		COS 150 -> -0.86602540
SIN 165 -> 0.25881905		COS 165 -> -0.96592583
SIN 180 -> 0.00000000		COS 180 -> -1.00000000
SIN 195 -> -0.25881905		COS 195 -> -0.96592583
SIN 210 -> -0.50000000		COS 210 -> -0.86602540
SIN 225 -> -0.70710678		COS 225 -> -0.70710678
SIN 240 -> -0.86602540		COS 240 -> -0.50000000
SIN 255 -> -0.96592583		COS 255 -> -0.25881905
SIN 270 -> -1.00000000		COS 270 -> 0.00000000
SIN 285 -> -0.96592583		COS 285 -> 0.25881905
SIN 300 -> -0.86602540		COS 300 -> 0.50000000
SIN 315 -> -0.70710678		COS 315 -> 0.70710678
SIN 330 -> -0.50000000		COS 330 -> 0.86602540
SIN 345 -> -0.25881905		COS 345 -> 0.96592583
SIN 360 -> 0.00000000		COS 360 -> 1.00000000

Floating Point Square Root:
KSH93 FP maths routines = 28.5257304902.
From Google calculator = 28.5257304902.

Floating Point Number And Nth Root:
KSH93 FP maths routines = 1.21066369814.
From Google calculator = 1.21066369814.

AMIGA:uw> Sin 45.1
0.70833984
AMIGA:uw> Sin 45.0
0.70710678
AMIGA:uw> Sin 44.9
0.70587157
AMIGA:uw> Cos 45.1
0.70587157
AMIGA:uw> Cos 45.0
0.70710678
AMIGA:uw> Cos 44.9
0.70833984
AMIGA:uw> Sqrt 10
3.1623
AMIGA:uw> Sqrt 10 10
3.1622776602
AMIGA:uw> NthRoot 9113.762 3.12   
18.5838
AMIGA:uw> NthRoot 9113.762 3.12 11
18.58377218033
AMIGA:uw> echo $(( $(Sqrt 10 16)*$(Sqrt 10 16) ))
10.0000000000000011
AMIGA:uw> x=$( NthRoot 9138052765.123 6.711 16 )
AMIGA:uw> echo $x
30.4970432230686335
AMIGA:uw> echo $(( x**6.711 ))
9138052765.12300491
AMIGA:uw> _

Hope you are all enjoying this exercise in futility... ;oD

1 Like