OK. I think I'm caught up on my sleep. And, I think I have a new script that meets all of the requirements ( bash
script using only integer arithmetic and only using shell built-ins running on Mac OS X on a MacBook Pro (assembled in mid 2014)). I am, however, using OS X version 10.10.5 instead of version 10.7.5 that wisecracker was using.
The bash
script wisecracker provided in post #1 in this thread mostly works fine for relatively small values, but takes a LONG time for relatively large values. I was surprised that it said that 0 is not a perfect square instead of saying that the minimum value it would process is 1. I wanted to see what it would do when invoked as:
perfect_square 1000000002000000001
I wasn't surprised that it came up with the correct answer:
1000000002000000001 is the perfect square of 1000000001...
but a ps
run shortly before it completed showed that it used more than 595 cpu minutes to compute that result.
While that was consuming one core of one cpu, I played around with Corona688's bash
script in post #16 in this thread. It correctly reported that 0 is a perfect square (which surprised me since it always uses 1 as the low end of its binary search range). And, due to the way it calculates the stopping point for the binary search and the way it determines the value it is processing, it says:
9223372030926249001 is not a perfect square
even though the correct response would be:
Square root of 9223372030926249001 is 3037000499
When given invalid numbers and when given valid numbers that do not fit in a signed long integer, bash
prints a diagnostic message when processing that value with Corona688's script and sometimes continues processing and sometimes terminates processing at that point.
I made a slight modification to Corona688's script to read values from a file instead of just processing one command line argument.
And, I produced the following bash
script which performs a more traditional binary search. It uses a little bit of number theory to reduce the range of the binary search. (An x-1 or x digit integer, where x is even, will have a square root that is x/2 digits if it has an integer square root.) It performs lots of tests to verify that the input is a valid number and is in range for a signed long int.
It reads its input from a file named file
and looks for two values on each line. The 1st value is the number to be processed. The 2nd value (if present) indicates the expected results for the 1st value (-1 if it is not a perfect square or the square root if it is a perfect square). If the expected value does not match the computed value, it prints a diagnostic. If the 2nd value is not present, the result is printed, but not verified.
#!/bin/bash
# Usage: perfect_square
# Numbers to be processed are read from an input file named file.
# There is no prompting for input.
# The input file format is:
# number_to_evaluate [expected_result]
# where number_to_evaluate is assumed to be a string of decimal digits with
# an optional leading minus sign, but no leading zeros. This string will be
# checked to verify that is a positive decimal value <= LONG_MAX and, if it
# is, determine wheter or not it has an integer square root. If expected
# result is given and is a positive number, it is the square root that is
# expected to be determined from the given number_to_evaluate. If it is -1,
# number_to_evaluate is not a perfect square. If expected_result is not
# given, the code will not verify the results computed against the
# expected_value.
# Note that the extensive checks used to verify valid numbers are used
# because the normal -lt, -le, eq, -ne, -ge, and -gt operators are not
# reliable with numbers that overflow a signed long integer.
# The following constants assume a 64-bit signed long int. If your system
# has a differrent number of bits in a signed long int, these values can be
# determined for your system, respectively, with the command:
# printf '0k%dpdvp1-d*pq\n' $(getconf LONG_MAX) | dc
# Maximum signed long int
LONG_MAX=9223372036854775807
# Maximum value that can be squared and produce a valid signed long int
MAX_SQUARE_ROOT=3037000499
# (MAX_SQUARE_ROOT - 1) ** 2
MAX_RANGE=9223372024852248004
while read num root
do # Verify non-empty string, no leading zeros, all digits (other than
# one allowed leading minus sign)...
if [[ "$num" = "" ]] || [[ "$num" != "${num##*[![:digit:]-]}" ]] || \
[[ "$num" != "${num#[0-]0}" ]] || [[ "$num" != "${num#[0-9-]*-}" ]]
then printf '%s %s\n%s: "%s"\n' \
'Invalid number (no leading zeros, no non-digits (other' \
'than an optional' 'leading -), no empty strings' "$num"
continue
fi
# Verify that 0 <= num <= LONG_MAX...
if [[ ${#num} -gt ${#LONG_MAX} ]] || \
{ [[ ${#num} -eq ${#LONG_MAX} ]] && [[ "$num" > "$LONG_MAX" ]];} || \
[[ num -lt 1 ]]
then printf "Out of range: 0 < x <= $LONG_MAX: x=$num\n"
continue
fi
# Set binary search range...
if [[ num -gt MAX_RANGE ]]
then low=$MAX_SQUARE_ROOT
high=$MAX_SQUARE_ROOT
else # Note that a number containing x or x+1 digits will have a
# square root that contains x / 2 digits...
low=$((10 ** ((${#num} - 1) / 2)))
if [[ ${#num} -eq ${#LONG_MAX} ]]
then high=$((MAX_SQUARE_ROOT - 1))
[[ num -lt high ]] && high=$num
else high=$((10 ** ((${#num} - 1) / 2 + 1) - 1))
fi
fi
# printf 'num=%d(%d), root=%s(%d), low=%d(%d), high=%d(%d)\n' \
# $num ${#num} "$root" ${#root} $low ${#low} $high ${#high}
# Search the established range...
while [[ low -le high ]]
do mid=$(((low + high) / 2))
square=$((mid * mid))
# printf '\tlow=%d, mid=%d, high=%d, square=%d\n' $low $mid \
# $high $square
if [[ square -eq num ]]
then echo "Square root of $num is $mid"
[[ "$root" != "" ]] && [[ mid -ne root ]] && \
echo "*** Expected \"$root\""
continue 2
fi
[[ square -lt num ]] && low=$((mid + 1)) || high=$((mid - 1))
done
echo "$num is not a perfect square"
[[ "$root" != "" ]] && [[ root -ne -1 ]] && echo "*** Expected \"$root\""
done < file
With the following input in file
:
-10000000000000000000000000000 -1
-1 -1
0 0
1 1
2 -1
3 -1
4 2
5 -1
8 -1
9 3
10 -1
1517823 -1
1517824 1232
1517825 -1
1520288 -1
1520289 1233
1520290 -1
1522755 -1
1522756 1234
1522757 -1
1524153208355 -1
1524153208356 1234566
1524153208357 -1
1524155677488 -1
1524155677489 1234567
1524155677490 -1
1524158146623 -1
1524158146624 1234568
1524158146625 -1
9754610360615760 -1
9754610360615761 98765431
9754610360615762 -1
9754610558146623 -1
9754610558146624 98765432
9754610558146625 -1
9754610755677488 -1
9754610755677489 98765433
9754610755677490 -1
1000000002000000000 -1
1000000002000000001 1000000001
1000000002000000002 -1
1000000004000000003 -1
1000000004000000004 1000000002
1000000004000000005 -1
1000000006000000008 -1
1000000006000000009 1000000003
1000000006000000010 -1
9223372024852248003 -1
9223372024852248004 3037000498
9223372024852248005 -1
9223372030926249000 -1
9223372030926249001 3037000499
9223372030926249002 -1
9223372036854775806 -1
9223372036854775807 -1
9223372036854775808 -1
9223372037000249999 -1
9223372037000250000 3037000500
9223372037000250001 -1
9999999999999999999 -1
10000000000000000000000000000 100000000000000
All of the following shold be rejected as invalid numbers (not out of range)...
abcd
001
-01
1-2
-abc
-0
123a
abc-
0-
it produces the output:
Out of range: 0 < x <= 9223372036854775807: x=-10000000000000000000000000000
Out of range: 0 < x <= 9223372036854775807: x=-1
Out of range: 0 < x <= 9223372036854775807: x=0
Square root of 1 is 1
2 is not a perfect square
3 is not a perfect square
Square root of 4 is 2
5 is not a perfect square
8 is not a perfect square
Square root of 9 is 3
10 is not a perfect square
1517823 is not a perfect square
Square root of 1517824 is 1232
1517825 is not a perfect square
1520288 is not a perfect square
Square root of 1520289 is 1233
1520290 is not a perfect square
1522755 is not a perfect square
Square root of 1522756 is 1234
1522757 is not a perfect square
1524153208355 is not a perfect square
Square root of 1524153208356 is 1234566
1524153208357 is not a perfect square
1524155677488 is not a perfect square
Square root of 1524155677489 is 1234567
1524155677490 is not a perfect square
1524158146623 is not a perfect square
Square root of 1524158146624 is 1234568
1524158146625 is not a perfect square
9754610360615760 is not a perfect square
Square root of 9754610360615761 is 98765431
9754610360615762 is not a perfect square
9754610558146623 is not a perfect square
Square root of 9754610558146624 is 98765432
9754610558146625 is not a perfect square
9754610755677488 is not a perfect square
Square root of 9754610755677489 is 98765433
9754610755677490 is not a perfect square
1000000002000000000 is not a perfect square
Square root of 1000000002000000001 is 1000000001
1000000002000000002 is not a perfect square
1000000004000000003 is not a perfect square
Square root of 1000000004000000004 is 1000000002
1000000004000000005 is not a perfect square
1000000006000000008 is not a perfect square
Square root of 1000000006000000009 is 1000000003
1000000006000000010 is not a perfect square
9223372024852248003 is not a perfect square
Square root of 9223372024852248004 is 3037000498
9223372024852248005 is not a perfect square
9223372030926249000 is not a perfect square
Square root of 9223372030926249001 is 3037000499
9223372030926249002 is not a perfect square
9223372036854775806 is not a perfect square
9223372036854775807 is not a perfect square
Out of range: 0 < x <= 9223372036854775807: x=9223372036854775808
Out of range: 0 < x <= 9223372036854775807: x=9223372037000249999
Out of range: 0 < x <= 9223372036854775807: x=9223372037000250000
Out of range: 0 < x <= 9223372036854775807: x=9223372037000250001
Out of range: 0 < x <= 9223372036854775807: x=9999999999999999999
Out of range: 0 < x <= 9223372036854775807: x=10000000000000000000000000000
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "All"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "abcd"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "001"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "-01"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "1-2"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "-abc"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "-0"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: ""
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "123a"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "abc-"
Invalid number (no leading zeros, no non-digits (other than an optional
leading -), no empty strings: "0-"
with average times:
real 0m0.042s
user 0m0.037s
sys 0m0.003s
for 10 runs. With the same input file, the modified version of Corona688's script produces the output:
./Corona: line 14: [: -10000000000000000000000000000: integer expression expected
-10000000000000000000000000000 is not a perfect square
Negative numbers not allowed
Square root of 0 is 0
Square root of 1 is 1
2 is not a perfect square
3 is not a perfect square
Square root of 4 is 2
5 is not a perfect square
8 is not a perfect square
Square root of 9 is 3
10 is not a perfect square
1517823 is not a perfect square
Square root of 1517824 is 1232
1517825 is not a perfect square
1520288 is not a perfect square
Square root of 1520289 is 1233
1520290 is not a perfect square
1522755 is not a perfect square
Square root of 1522756 is 1234
1522757 is not a perfect square
1524153208355 is not a perfect square
Square root of 1524153208356 is 1234566
1524153208357 is not a perfect square
1524155677488 is not a perfect square
Square root of 1524155677489 is 1234567
1524155677490 is not a perfect square
1524158146623 is not a perfect square
Square root of 1524158146624 is 1234568
1524158146625 is not a perfect square
9754610360615760 is not a perfect square
Square root of 9754610360615761 is 98765431
9754610360615762 is not a perfect square
9754610558146623 is not a perfect square
Square root of 9754610558146624 is 98765432
9754610558146625 is not a perfect square
9754610755677488 is not a perfect square
Square root of 9754610755677489 is 98765433
9754610755677490 is not a perfect square
1000000002000000000 is not a perfect square
Square root of 1000000002000000001 is 1000000001
1000000002000000002 is not a perfect square
1000000004000000003 is not a perfect square
Square root of 1000000004000000004 is 1000000002
1000000004000000005 is not a perfect square
1000000006000000008 is not a perfect square
Square root of 1000000006000000009 is 1000000003
1000000006000000010 is not a perfect square
9223372024852248003 is not a perfect square
Square root of 9223372024852248004 is 3037000498
9223372024852248005 is not a perfect square
9223372030926249000 is not a perfect square
9223372030926249001 is not a perfect square
9223372030926249002 is not a perfect square
9223372036854775806 is not a perfect square
9223372036854775807 is not a perfect square
./Corona: line 14: [: 9223372036854775808: integer expression expected
9223372036854775808 is not a perfect square
./Corona: line 14: [: 9223372037000249999: integer expression expected
9223372037000249999 is not a perfect square
./Corona: line 14: [: 9223372037000250000: integer expression expected
9223372037000250000 is not a perfect square
./Corona: line 14: [: 9223372037000250001: integer expression expected
9223372037000250001 is not a perfect square
./Corona: line 14: [: 9999999999999999999: integer expression expected
9999999999999999999 is not a perfect square
./Corona: line 14: [: 10000000000000000000000000000: integer expression expected
10000000000000000000000000000 is not a perfect square
./Corona: line 14: [: abcd: integer expression expected
Square root of abcd is 0
Square root of 001 is 1
Negative numbers not allowed
./Corona: line 14: [: 1-2: integer expression expected
1-2 is not a perfect square
./Corona: line 14: [: -abc: integer expression expected
Square root of -abc is 0
Square root of -0 is 0
./Corona: line 14: [: : integer expression expected
Square root of is 0
./Corona: line 11: 123a: value too great for base (error token is "123a"
with average times:
real 0m0.041s
user 0m0.036s
sys 0m0.003s
for 10 runs. Note that it is expected to run faster than the above script because it doesn't perform any error checking and it doesn't process the last two input lines because it dies on any of the last three input lines. I was surprised that the times were so close. (Apparently reducing the binary search range made up for the extra data verification tests.)
Note also that I am not saying that Corona688's script should perform any error checking; that was not a requirement for this project. I machine-generated most of the input file (after I found the anomalies in the results from the ksh
script I presented earlier) and added code to verify that the results computed matched known results as a debugging aid.