A fixed point basic calculator for DASH.

This exercise has taught me a lot about POSIX, dash and their limits.

I decided to experiment with fixed point arithmetic using dash as the shell.
If you want to test it then change the shebang to your directory where 'dash' is or use 'bash' instead.

This was one of the hardest things I have done and it took me around 24 hours to just get addition and subtraction working with an accuracy of nine decimal places for calculation results inside + or - 1,000,000.000000000 and around 5 decimal places for the working limits of 64 bit integer maths of + or - 9223,372,036.85477[????].
I haven't done multiplication and division yet but have checked from the command line that these work and are easy enough now that I have this building block.
It will have bugs and limitations but for small scale quick fixed point calculations this seems to work fine at the moment.
As I once uploaded here, "Is there anything the UNIX shell can't do?" and the script is not that big.

This requires NO external command help to work, it is pure dash shell coding.
I will be finishing the multiplication and division whilst away next week.

#!/usr/local/bin/dash
#
#!/bin/bash
#
# 'calc' - A basic calculator for average sized numbers.
#
# OSX 10.13.6, default bash terminal.
# Another exercise in futility by B.Walker.
# Public Domain.
# Accurate enough to nine decimal places.
# USAGE: calc NUMBER1 +|-|*|/ NUMBER2
#
# In 64 bit mode.
# Total upper limit = +9223372036.85477; 64 bit signed integer = 9223372036854775807.
# Total lower limit = -9223372036.85477; 64 bit signed integer = 9223372036854775808.
# This allows nine places for the decimal component.

# Numerical variables.
NUM1="$1"
NUM2="$3"
# Equalise the decimal places.
FP1=$( printf "%.9f" "$NUM1" )
FP2=$( printf "%.9f" "$NUM2" )

# Calculation mode variable, limited error correction.
CALC="$2"
if [ "$NUM1" = "" ] || [ "$NUM2" = "" ] || [ "$CALC" = "" ]
then
	echo "USAGE: calc NUMBER1 +|-|*|/ NUMBER2"
	exit 1
fi

# Remove the decimal point(s).
INT1="${FP1%.*}${FP1#*.}"
INT2="${FP2%.*}${FP2#*.}"
# Remove all leading zeros.
INT1=$( printf "%.f" "$INT1" )
INT2=$( printf "%.f" "$INT2" )

# Addition and subtraction.
if [ "$CALC" = "+" ] || [ "$CALC" = "-" ]
then
	if [ "$CALC" = "+" ]
	then
		SUM=$(( (INT1)+(INT2) ))
	fi
	if [ "$CALC" = "-" ]
	then
		SUM=$(( (INT1)-(INT2) ))
	fi
	# Internal Variables.
	LENGTH="${#SUM}"
	MINUS="$SUM"
	FLOAT=""
	COUNT="0"

	# Detect the minus sign, save and delete.
	while [ "$COUNT" -le "$LENGTH" ]
	do
		MINUS="${MINUS%?}"
		if [ "$MINUS" = "-" ]
		then
			SUM="${SUM#?}"
			MINUS="-"
			break
		fi
		COUNT=$(( COUNT+1 ))
	done

	# Give a positive sign on the final result if it is not negative.
	# This condition can be removed if required.
	if [ "$MINUS" = "" ]
	then
		MINUS="+"
	fi

	# Pad a float result of less that 1.0 with leading zeros if required.
	LENGTH=${#SUM}
	if [ "$LENGTH" -le "9" ]
	then
		FLOAT=$( printf "%09d" "$SUM" )
		SUM="$FLOAT"
	fi

	# Obtain the integer part.
	INT="${SUM%?????????}"
	if [ "$INT" = "" ]
	then
		INT="0"
	fi

	# Obtain the float part.
	if [ "$LENGTH" -ge "10" ]
	then
		COUNT="10"
		GETFLOAT=""
		while [ "$COUNT" -le "$LENGTH" ]
		do
			GETFLOAT="$GETFLOAT"'?'
			COUNT=$(( COUNT+1 ))
		done
		FLOAT="${SUM#${GETFLOAT}}"
	fi
	RESULT="${INT}.${FLOAT}"
	printf "%s%.9f\n" "$MINUS" "$RESULT"
fi

# Multiplication.
if [ "$CALC" = "*" ]
then
	:
fi

# Division.
if [ "$CALC" = "/" ]
then
	:
fi

A couple of 'additions' using python 2.7 as a check.
OSX 10.13.6, default bash terminal calling /usr/local/bin/dash

Last login: Mon Nov  5 08:06:19 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./calc 1234567.987654321 - -1234567.135790864
+2469135.123445185
AMIGA:amiga~/Desktop/Code/Shell> python
Python 2.7.10 (default, Oct  6 2017, 22:29:07) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 1234567.987654321 - -1234567.135790864
2469135.123445185
>>> exit()
AMIGA:amiga~/Desktop/Code/Shell> ./calc 0.00001234567890 - 1.012401299
-1.012388953
AMIGA:amiga~/Desktop/Code/Shell> python
Python 2.7.10 (default, Oct  6 2017, 22:29:07) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 0.00001234567890 - 1.012401299
-1.0123889533211
>>> exit()
AMIGA:amiga~/Desktop/Code/Shell> _

Have fun and enjoy...
This is my pet love, getting languages to do something they were not designed to do.

Bazza.

2 Likes

OSX 10.13.6, default bash terminal.
NOTE: These shells are of a vintage that Apple thinks is OK!

Well guys, have I found a bug?

Last login: Tue Nov  6 13:15:41 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> # 'calc' on diferent shells.
AMIGA:amiga~/Desktop/Code/Shell> # shebang set to 'ksh' in the script.
AMIGA:amiga~/Desktop/Code/Shell> ./calc 101010101.909090909 - -123456789.987654321
+224466891.896745230
AMIGA:amiga~/Desktop/Code/Shell> # shebang set to 'sh' in the script.
AMIGA:amiga~/Desktop/Code/Shell> ./calc 101010101.909090909 - -123456789.987654321
+224466891.896745235
AMIGA:amiga~/Desktop/Code/Shell> # shebang set to 'bash' in the script.
AMIGA:amiga~/Desktop/Code/Shell> ./calc 101010101.909090909 - -123456789.987654321
+224466891.896745235
AMIGA:amiga~/Desktop/Code/Shell> # shebang set to 'dash' in the script.
AMIGA:amiga~/Desktop/Code/Shell> ./calc 101010101.909090909 - -123456789.987654321
+224466891.896745235
AMIGA:amiga~/Desktop/Code/Shell> # shebang set to 'zsh' in the script.
AMIGA:amiga~/Desktop/Code/Shell> ./calc 101010101.909090909 - -123456789.987654321
+224466891.224466890
AMIGA:amiga~/Desktop/Code/Shell> # OH DEAR, WHAT WENT WRONG!
AMIGA:amiga~/Desktop/Code/Shell> python
Python 2.7.10 (default, Oct  6 2017, 22:29:07) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 101010101.909090909 - -123456789.987654321
224466891.89674523
>>> exit()
AMIGA:amiga~/Desktop/Code/Shell> zsh --version
zsh 5.3 (x86_64-apple-darwin17.0)
AMIGA:amiga~/Desktop/Code/Shell> _

'zsh' version, zsh 5.3 (x86_64-apple-darwin17.0)
It might be that whatever is in the script that is causing the 'zsh' error has been cured, so could someone with a current version check please.

Bazza.

Your use of printf "%f" is perhaps begging for trouble, that may convert to floating point and back. And even 64-bit floating point doesn't have the exactness of a 64-bit integer, some of those 64 bits get dedicated to mantissa et cetera.

dash gives +224466891.896745235 on my system, bash and ksh give +224466891.896745230, and zsh doesn't work at all, it gives "invalid floating point number" on printf "%s%.9f\n" "$MINUS" "$RESULT"

My suggestion would be to eliminate these floating point conversions, and just use integer math. For three decimal points you'd do

SIGN="+"
if [ "$NUM" -lt 0 ]
then
        NUM=$((-NUM))
        SIGN="-"
fi

printf "%s%d.%d" "$SIGN" "$((NUM/100))" "$((NUM%100))"

I wouldn't know where to start for floating point maths in pure 'dash' and I'll stick my neck out and say it cannot be done at all.

I don't use "%f" anywhere, I use "%.f" instead and 'shellcheck' shows no issues using 'dash' as the shell and changing it to 'sh' too.

I did quote that up 1,000,000.000000000, (1 Million), it is accurate to 9 decimal places and gets progressively worse to the upper/lower limits. Therefore IMO 8 places accuracy to at least 22 Million is acceptable for ordinary work

As for the code snippets I will try them out whilst working out multiply and divide; and I thought 'add' and 'subtract' were hard.
Doing it on the command line is not the same as working out the algorithms for limited language like 'dash'. All those 'bash'isms one takes for granted.

I have no idea why 'zsh' fails when the others work as per POSIX, I don't really intend to find out as dash is the important shell ATM...
(I have not got 'ash' so dunno if it works on that.)

EDIT:
Done manually...

Last login: Tue Nov  6 19:05:47 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./calc .000000277 - +.000000092
+0.000000185
AMIGA:amiga~/Desktop/Code/Shell> # Therefore SUM="000000185"
AMIGA:amiga~/Desktop/Code/Shell> FLOAT="000000185"
AMIGA:amiga~/Desktop/Code/Shell> INT=""
AMIGA:amiga~/Desktop/Code/Shell> NUMBER=$(( FLOAT/100 ))
-bash: 000000185: value too great for base (error token is "000000185")
AMIGA:amiga~/Desktop/Code/Shell> dash
AMIGA:\u\w> ./calc .000000277 - +.000000092
+0.000000185
AMIGA:\u\w> # Therefore SUM="000000185"
AMIGA:\u\w> FLOAT="000000185"
AMIGA:\u\w> INT=""
AMIGA:\u\w> NUMBER=$(( FLOAT/100 ))
dash: 6: Illegal number: 000000185
AMIGA:\u\w> exit
AMIGA:amiga~/Desktop/Code/Shell> _

Bazza...

I misread your post as dash misbehaving rather than zsh, but anyway - I don't mean the shell has floating point. I mean shell printf could use it as a temporary intermediate to implement %f.

potato, potahto. The simplest way to implement %f is the C function calls strtof and sprintf, which would use a floating point intermediate. It'd unprocess the number into a float then immediately process it back into a string using the "%.f" specifier. Less than a line.

You're taking the path of maximum resistance again. If you leave out the decimal point entirely and assume all numbers are multiplied by 100, addition and subtraction "just work", no zero padding required. Then you use division and modulus to extract the full and fractional parts later.

Here's a quick bash-only version for positive numbers. The trick is reading the values in. Once you do that the rest is straightforward.

DP=3
MOD=1000

function readnum {
        IFS="." read N FRAC <<<"$1"
        [ "$N" = "0" ] && N=""
        for((X=0; X<DP; X++))
        do
                D="${FRAC:$X:1}"
                [ -z "$D" ] && D="0"
                N="$N$D" # Prepend digits to N
        done
}

readnum "0.5"

echo "Integer $N is fixed point $((N/MOD)).$((N%MOD))"
1 Like

I can see what you are doing, but forgive my ignorance; apart from the obvious bashisms, how do I use 'read' to input values directly from command line arguments without disk thrashing in POSIX 'dash'?
Everything MUST centre around 'dash' or POSIX 'sh'.
As for 'zsh', I can't be responsible for a non POSIX compliant shell.
This is the whole point of this [futile] exercise to see what is possible and what better than Fixed Point Arithmetic.
And I already assume a "fractional" part of 1000000000 and use "%.9f" to add any zeros to the end.
Anyhow I will try out your "MINUS" modification tomorrow.
Multiplication and division are harder still in 'dash'.

Back tomorrow.
HTH.

Bazza.

I'm sure DASH can divide and multiply absolutely fine. It has * and /.

I noticed, but the way you're doing it is perhaps the hardest possible way. If you just keep your number as an integer, you can calculate it as an integer, because it is an integer. Addition and subtraction work completely straight. Multiplication and division will need correction before and afterwards respectively. And you will have to stay away from the integer limit because the result of multiplying 64-bit MAXINT by 64-bit MAXINT is a 128-bit number.

Anyway, you can use a here-document to feed values into read. There's another, older trick though, which works especially well in functions and should work anywhere.

DP=3
MOD=1000

function readnum {
        OLDIFS="$IFS"
        IFS="."
                set -- $1 # Set $1, $2, etc, splitting on "."
        IFS="$OLDIFS"

        N=$1
        FRAC=$2

        [ "$N" = "0" ] && N=""

        for((X=0; X<DP; X++))
        do
                D="${FRAC:$X:1}"
                [ -z "$D" ] && D="0"
                N="$N$D" # Prepend digits to N
        done
}

readnum "0.5"

echo "Integer $N is fixed point $((N/MOD)).$((N%MOD))"
1 Like

Hi Corona688...

#!/usr/local/bin/dash
# heredoc.sh
IFS="$IFS"'.'

read -r INT FLOAT <<EOF
$1
EOF
echo "$INT $FLOAT"

WOW! I didn't find anything about this on the mighty WWW. This earns you a big thank you and certainly does open up new avenues...
Results on my usual gear:

Last login: Wed Nov  7 19:10:03 on console
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> chmod 755 heredoc.sh
AMIGA:amiga~/Desktop/Code/Shell> ./heredoc.sh 123.456
123 456
AMIGA:amiga~/Desktop/Code/Shell> _

Now this I would be interested in, please post an example ASAP.
I will re-do my code accordingly and check against Python. Gimme a bit of time.

This is why I am an amateur and you are a professional, my knowledge is far less than yours and others in this sphere.

<thumb-up-smilie-here>
EDIT:
Done my own. ;o)

	        .--.
	        \   \
	         .   .
	         |   |
	         |   |
	         |   |
	    ____/     `-----------.
	   /                       )
	  |           +----------.'
	  |                       )
	  |           +---------.'
	  |                      )
	  |           +--------.'
	   \                    )
	    '------------------'
				

Bazza...

IFS="$IFS"'.'

has a permanent effect, so you often do

oldIFS=$IFS
IFS="$IFS"'.'
# shell code that needs the modified IFS
# restore the old IFS
IFS=$oldIFS
# normal shell code

Sometimes you can use a sub shell:

(
# this is a sub shell
IFS="$IFS"'.'
# shell code that needs the modified IFS
)
# in the main shell there is the old IFS

read is a command, and you can set the environment just for the command

IFS="$IFS"'.' read ...
# after the command there is the old IFS

Unfortunately set is not a command. (You can call it a pseudo command or special command. The shell does not fork/exec like with a normal command, therefore IFS must be set beforehand.)

1 Like

Hi MadeInGermany...

I was well aware of 'IFS' being permanent but the test code was proof of concept rather than a dedicated program.
If C688 had not mentioned the heredoc method then then I would not have known about it. My searching prowess must be poor using Google.
Having said that this is neat too, as it is also new to me:

(
# this is a sub shell
IFS="$IFS"'.'
# shell code that needs the modified IFS
)
# in the main shell there is the old IFS

So you get a thumbs up too:

	        .--.
	        \   \
	         .   .
	         |   |
	         |   |
	         |   |
	    ____/     `-----------.
	   /                       )
	  |           +----------.'
	  |                       )
	  |           +---------.'
	  |                      )
	  |           +--------.'
	   \                    )
	    '------------------'

Bazza.

I did? It is set -- arg1 arg2 arg3 ... which sets the $1 $2 $3 variables.

If you run it like set -- $variable it will split upon IFS, which is whatever you please, giving an easy way to split a string upon "." even in ancient shells which lack most modern string built-ins.

It works especially well in functions because otherwise you'd have to worry about overwriting the global $1 $2 etc, but inside a function you're only dealing with local ones, which will be gone soon anyway.

Beware that not only IFS is preserved -- every variable is preserved. Variable assignments inside the new shell don't copy into the old one. It's just like running commands behind a pipe.

Here is the fixed point arithmetic code for a fixed point basic calculator for DASH... Cheers.

#!/bin/bash
# Fixed point arithmetic
DP=3 # Decimal Places
MOD=1000 # 1 * 10^DP

function ftofix {       # ftofix OUTPUTNAME "3.14"
        _VAR="$1"
        _OLDIFS="$IFS"
        IFS="."
                set -- $2 # Set $1, $2, etc, splitting on "."
        IFS="$_OLDIFS"
        local FRAC=$2
        local _N=$1

        [[ "${_N}" =  "0" ]] && _N=""
        [[ "${_N}" = "-0" ]] && _N="-"

        for((X=0; X<DP; X++))
        do
                D="${FRAC:$X:1}"
                [[ -z "$D" ]] && D="0"
                _N="${_N}$D" # Prepend digits to N
        done
        read $_VAR <<EOF
${_N}
EOF

}

function fixtof {
        _VAR="$1"
        _N="$2"
        _SIGN=""
        ((_N<0)) && _SIGN="-" && ((_N=-_N))
        _S=`printf "%d.%0${DP}d" $((_N/MOD)) $((_N%MOD))`
        read $_VAR <<EOF
$_S
EOF
}

AA="3.14159"
BB="2.0"

ftofix A $AA
ftofix B $BB

# Multiplcation
fixtof CC $(((A*B)/MOD))
echo "$AA * $BB = $CC"

# Division
fixtof DD $(( (A*MOD)/B ))
echo "$AA / $BB = $DD"

# Addition and Subtraction
fixtof EE $(( B - A ))
echo "$BB - $AA = $EE"
fixtof FF $(( A - B ))
echo "$AA - $BB = $FF"
fixtof GG $(( A + B ))
echo "$AA + $BB = $GG"

Apologies for the delay, I am a carer or babysitter presently for my mother or grandchild and dogs.
However @ Corona688...

Well tested your 'BASH' version with just one edge case and there will be quite a few...
Note with your original, 2.0 - 3.14159 = -1.141 , not what is shown.
Then I just did one edge case, I changed '2.0' to '0.073' and...
NOTE: Not related to either your or my versions; I have manually found many edgecases. Python was a godsend for this task.

Last login: Fri Nov 23 21:29:04 on ttys000
AMIGA:amiga~> cd Desktop/Code/Shell
AMIGA:amiga~/Desktop/Code/Shell> ./FPMATH_C688.sh
3.14159 * 2.0 = 6.282
3.14159 / 2.0 = 1.570
2.0 - 3.14159 = 1.141		# WRONG! -1.141
3.14159 - 2.0 = 1.141
3.14159 + 2.0 = 5.141
AMIGA:amiga~/Desktop/Code/Shell> # Just one edge case, change BB to 0.073.
AMIGA:amiga~/Desktop/Code/Shell> ./FPMATH_C688.sh
3.14159 * 0.073 = 0.185
3.14159 / 0.073 = 53.237
0.073 - 3.14159 = 3.082
3.14159 - 0.073 = 3.082
3.14159 + 0.073 = 3.200
AMIGA:amiga~/Desktop/Code/Shell> # Oops, they are all wrong.
AMIGA:amiga~/Desktop/Code/Shell> _
Google calculator:
3.14159 * 0.073 = 0.229
3.14159 / 0.073 = 43.035
0.073 - 3.14159 = -3.068
3.14159 - 0.073 = 3.068
3.14159 + 0.073 = 3.214

As I said I have learnt a lot about 'dash' and POSIX. Your 'heredoc' code is wonderful.
As above I have found many edge cases.
Also the strange effects closing the gaps to the + or - limits I have set.
I will be back ASAP but family comes first.

There was a bug in my version above. Arithmetic involving negative numbers worked, the edge case was the display of them. One 'edge' was that I completely forgot to display the sign, at all, ever. Still trying to fix the 0.073 problem.

DP=3
MOD=1000

function ftofix {       # ftofix OUTPUTNAME "3.14"
        _VAR="$1"
        _OLDIFS="$IFS"
        IFS="."
                set -- $2 # Set $1, $2, etc, splitting on "."
        IFS="$_OLDIFS"
        local FRAC=$2
        local _N=$1

        [[ "${_N}" =  "0" ]] && _N=""
        [[ "${_N}" = "-0" ]] && _N="-"

        for((X=0; X<DP; X++))
        do
                D="${FRAC:$X:1}"
                [[ -z "$D" ]] && D="0"
                _N="${_N}$D" # Prepend digits to N
        done
        read $_VAR <<EOF
${_N}
EOF

}

function fixtof {
        _VAR="$1"
        _N="$2"
        _SIGN=""
        ((_N<0)) && _SIGN="-" && ((_N=-_N))
        _S=`printf "%s%d.%0${DP}d" "${_SIGN}" $((_N/MOD)) $((_N%MOD))`
        read $_VAR <<EOF
$_S
EOF
}

AA="3.14159"
BB="-2.0"

ftofix A $AA
ftofix B $BB

# Multiplcation
fixtof CC $(((A*B)/MOD))
echo "$AA * $BB = $CC"

# Division
fixtof DD $(( (A*MOD)/B ))
echo "$AA / $BB = $DD"

# Addition and Subtraction
fixtof EE $(( B - A ))
echo "$BB - $AA = $EE"
fixtof FF $(( A - B ))
echo "$AA - $BB = $FF"
fixtof GG $(( A + B ))
echo "$AA + $BB = $GG"
3.14159 * -2.0 = -6.282
3.14159 / -2.0 = -1.570
-2.0 - 3.14159 = -5.141
3.14159 - -2.0 = 5.141
3.14159 + -2.0 = 1.141

I wonder why in a thread with title "DASH" you use function, a keyword that is not known in dash. dash needs

funcname(){ ... }
DP=3
MOD=1000

function ftofix {       # ftofix OUTPUTNAME "3.14"
        local _N="0"
        local D=0
        local X
        local Y

        # Convert "VAR" "-.3" into "VAR" "-" "0.3", etc
        case "$2" in
        [.]*)           set -- "$1" "" "0$2"            ;;
        [0-9]*)         set -- "$1" "" "$2"             ;;
        [+-][0-9.]*)    set -- "$1" "${2:0:1}" "${2:1}" ;;
        *)              return 1                        ;;
        esac

        for((X=0; X<${#3}; X++))
        do
                if [[ "${3:$X:1}" == "." ]]
                then
                        (( X++ )) ; break
                fi

                (( _N = (10*_N) + ${3:$X:1} ))
        done

        while [[ "$D" -lt "$DP" ]]
        do
                Y="${3:$((X+D)):1}"
                [ -z "$Y" ] && Y=0

                (( _N = (10*_N) + Y ))
                (( D++ ))
        done

        read "$1" <<EOF
${2}${_N}
EOF
}

function fixtof {
        _VAR="$1"
        _N="$2"
        _SIGN=""
        ((_N<0)) && _SIGN="-" && ((_N=-_N))
        _S=`printf "%s%d.%0${DP}d" "${_SIGN}" $((_N/MOD)) $((_N%MOD))`
        read $_VAR <<EOF
$_S
EOF
}

AA="3.14159"
BB="-0.073"

ftofix B $BB
ftofix A $AA

# Multiplcation
fixtof CC $(((A*B)/MOD))
echo "$AA * $BB = $CC"

# Division
fixtof DD $(( (A*MOD)/B ))
echo "$AA / $BB = $DD"

# Addition and Subtraction
fixtof EE $(( B - A ))
echo "$BB - $AA = $EE"
fixtof FF $(( A - B ))
echo "$AA - $BB = $FF"
fixtof GG $(( A + B ))
echo "$AA + $BB = $GG"
3.14159 * -0.073 = -0.229
3.14159 / -0.073 = -43.027
-0.073 - 3.14159 = -3.214
3.14159 - -0.073 = 3.214
3.14159 + -0.073 = 3.068

BASH mandelbrot set:

DP=8
MOD=1
while [[ "${#MOD}" -le "${DP}" ]]
do
        MOD="${MOD}0"
done

ESCAPED=0

function ftofix {       # ftofix OUTPUTNAME "3.14"
        local _N="0"
        local D=0
        local X
        local Y

        # Convert "VAR" "-.3" into "VAR" "-" "0.3", etc
        case "$2" in
        [.]*)           set -- "$1" "" "0$2"            ;;
        [0-9]*)         set -- "$1" "" "$2"             ;;
        [+-][0-9.]*)    set -- "$1" "${2:0:1}" "${2:1}" ;;
        *)              return 1                        ;;
        esac

        for((X=0; X<${#3}; X++))
        do
                if [[ "${3:$X:1}" == "." ]]
                then
                        (( X++ )) ; break
                fi

                (( _N = (10*_N) + ${3:$X:1} ))
        done

        while [[ "$D" -lt "$DP" ]]
        do
                Y="${3:$((X+D)):1}"
                [ -z "$Y" ] && Y=0

                (( _N = (10*_N) + Y ))
                (( D++ ))
        done

        read "$1" <<EOF
${2}${_N}
EOF
}

function fixtof {
        _VAR="$1"
        _N="$2"
        _SIGN=""
        ((_N<0)) && _SIGN="-" && ((_N=-_N))
        _S=`printf "%s%d.%0${DP}d" "${_SIGN}" $((_N/MOD)) $((_N%MOD))`
        read $_VAR <<EOF
$_S
EOF
}

function escape { # R I
        # Z Z C C R
        set -- 0 0 $1 $2 $(( (($1*$1)+($2*$2))/MOD ))
        local N=0

        while [[ "$N" -lt "$EMAX" ]] && [[ "$5" -lt $((4*MOD)) ]]
        do
                set -- $(( $3 + (($1*$1) - ($2*$2))/MOD ))      \
                        $(( $4 + (2*$1*$2)/MOD )) $3 $4         \
                        $(( (($1*$1)+($2*$2))/MOD ))
                (( N++ ))
        done

        (( N >= EMAX )) && (( ESCAPED ++ ))

        return $N
}

Y=0

STUFF=" .oOo. -=%#%=-"

ftofix CY       ${1-0.0}
ftofix CX       ${2--0.5}
ftofix W        ${3-1.0}
EMAX=${4-100}

XMIN=$((CX-W))
XMAX=$((CX+W))
YMIN=$((CY-W))
YMAX=$((CY+W))

XSTEP=$(( (XMAX-XMIN) / 70 ))
YSTEP=$(( (YMAX-YMIN) / 20 ))

for ((Y=YMIN; Y < YMAX; Y += YSTEP ))
do
        S=""
        for ((X=XMIN; X < XMAX; X += XSTEP ))
        do
                escape "$X" "$Y"
                Z="$?"
                [ "$Z" -eq "$EMAX" ] && Z=0 || (( Z %= ${#STUFF} ))

                S="$S${STUFF:$Z:1}"
        done
        echo "|$S|"
done

echo "Escaped $ESCAPED times" >&2

exit 0
./mandel2.sh

|Ooooooooooooooooooooooooooooo...........    -=%%.#%=-- ........oooooooO|
|oooooooooooooooooooooooo.............     -=%#%o%%o%%=-    .......ooooo|
|oooooooooooooooooooo.............   ----==%%O      .# #=--      .....oo|
|oooooooooooooooo...........   -==%o%%%%%##%=.       O=%##%==----==- ...|
|oooooooooooo......        ---==% .  Ooo                   o-=-=oo%==  .|
|oooooo....             ----=%# =.                               = %--  |
|....  -=%%%==============%%#=.o                                  .%#== |
|     --=%% o-o-- %   =%##%%.%                                     o-%--|
|   -===%-=.=            -oO%                                      -.#- |
|==%%- O.o                 O                                       %=-  |
|                                                              --%%=--  |
|==%%- O.o                 O                                       %=-  |
|   -===%-=.=            -oO%                                      -.#- |
|     --=%% o-o-- %   =%##%%.%                                     o-%--|
|....  -=%%%==============%%#=.o                                  .%#== |
|oooooo....             ----=%# =.                               = %--  |
|oooooooooooo......        ---==% .  Ooo                   o-=-=oo%==  .|
|oooooooooooooooo...........   -==%o%%%%%##%=.       O=%##%==----==- ...|
|oooooooooooooooooooo.............   ----==%%O      .# #=--      .....oo|
|oooooooooooooooooooooooo.............     -=%#%o%%o%%=-    .......ooooo|

$

I don't expect this to run on the amiga with any reasonable speed.

1 Like