: # Hypercalc, Copyright (C) 1998-2024 Robert P. Munafo *-*-perl-*-*
eval 'exec perl -S $0 ${1+"$@"}'
if 0; # if running under some shell
# Copyright (c) 1997-2024 Robert Munafo
# License: Creative Commons Attribution-NonCommercial 4.0 International
# Source: http://mrob.com/pub/perl/hypercalc.txt
#
# This is the 2024 Aug 21 version of Hypercalc.
my $bapropos = q@
hypercalc(1r) - interpreted calculator with special provisions
to avoid overflow
@;
# RHTF NOTE
#
# If this file is in rhtf/src, it is read-only.
#
# The file rhtf/.../hypercalc.txt is derived from .../bin/hypercalc
# and copied automatically by misc.per
#
use IO::Handle;
use IO::File;
use IPC::Open2;
$format_debug = 0;
$g_min_mant_digits = 3;
$HC_BANNER = q@
Hypercalc, Copyright (C) 1998-2024 Robert P. Munafo.
This is the 2024 Aug 21 version of Hypercalc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License (immediately following this text) for
more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
For more perl goodness, go to mrob.com/pub/perl
@; $HC_MANPAGE = q`
NAME
hypercalc - interpreted calculator with special provisions
to avoid overflow
DESCRIPTION
#hypercalc# is an interactive calculator program with command history,
variable substitution, and a programming language similar to BASIC.
Its most distinctive feature is that it uses an internal representation
of numbers that avoids overflow by supporting arbitrarily high "towers"
of exponents.
For more information and complete descriptions of #hypercalc# expression
syntax, variables, programming, etc. start hypercalc with no argument
and then type "help" at the prompt.
`; $GPL_VERSION_3 = q`
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom
to share and change all versions of a program--to make sure it remains
free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies
also to any other work released this way by its authors. You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you
have certain responsibilities if you distribute copies of the
software, or if you modify it: responsibilities to respect the freedom
of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the
manufacturer can do so. This is fundamentally incompatible with the
aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for
individuals to use, which is precisely where it is most unacceptable.
Therefore, we have designed this version of the GPL to prohibit the
practice for those products. If such problems arise substantially in
other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the
freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish
to avoid the special danger that patents applied to a free program
could make it effectively proprietary. To prevent this, the GPL
assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released
under this License and any conditions added under section 7. This
requirement modifies the requirement in section 4 to "keep intact
all notices".
c) You must license the entire work, as a whole, under this License
to anyone who comes into possession of a copy. This License will
therefore apply, along with any applicable section 7 additional
terms, to the whole of the work, and all its parts, regardless of
how they are packaged. This License gives no permission to
license the work in any other way, but it does not invalidate
such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has
interactive interfaces that do not display Appropriate Legal
Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that
product model, to give anyone who possesses the object code
either (1) a copy of the Corresponding Source for all the
software in the product that is covered by this License, on a
durable physical medium customarily used for software
interchange, for a price no more than your reasonable cost of
physically performing this conveying of source, or (2) access to
copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in
accord with subsection 6b.
d) Convey the object code by offering access from a designated place
(gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding
Source may be on a different server (operated by you or a third
party) that supports equivalent copying facilities, provided you
maintain clear directions next to the object code saying where to
find the Corresponding Source. Regardless of what server hosts
the Corresponding Source, you remain obligated to ensure that it
is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material
by anyone who conveys the material (or modified versions of it)
with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU General Public
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that numbered version or
of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public
License, you may choose any version ever published by the Free
Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU General Public License can be used, that proxy's public
statement of acceptance of a version permanently authorizes you to
choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
`;
$GPLNOTICE = "
Hypercalc is distributed under the terms and conditions of the
GNU General Public License, version 3, June 2007. Type 'help gpl'
at the Hypercalc prompt for details.
";
sub date1
{
my($offset) = @_;
my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
my($cn_date);
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
= localtime(time + $offset);
$year += 1900;
$cn_date = sprintf("%04d%02d%02d", $year, $mon+1, $mday);
return $cn_date;
}
sub fdate2
{
my($dt1) = @_;
my($yyyy, $mm, $dd);
my @months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
if ($dt1 =~ m/^([0-9]{4})([0-9]{2})([0-9]{2})$/) {
$yyyy = $1; $mm = $2; $dd = $3;
$dt1 = "$yyyy " . $months[$mm-1] . " $dd";
}
return $dt1;
}
sub subst_global_tags
{
my($l) = @_;
if ($g_dth eq '') {
$g_dth = &fdate2(&date1(0));
}
$l =~ s|BWEBD[_]SUBST_AUTODATE_TAG|$g_dth|g;
return $l;
}
#
# hypercalc -- interpreted calculator with special provisions to avoid
# overflow
#
$tagline = &subst_global_tags("
Hypercalc by Robert Munafo, 2024 Aug 21
Go ahead -- just TRY to make me overflow!
_ _
|_| . . ._ _ ._ _ ._ | _
| | | | | ) (-` | ( ,-| | (
~ ~ 7 |~ ~' ~ ~ `~` ~ ~
-' mrob.com/hypercalc
Enter expressions, or type 'help' for help.
"); # )) balance parens in ASCII banner
#
# Which is bigger: 27 ^ (86 !) or (27 ^ 86) !
# Most calculators can't even give the value of 27^86 or of 86!.
#
# With HyperCalc you can see that 27^86 is 1.251076x10^123,
# and 86! is 2.422709x10^130. Some calculators can handle that --
# the current record-holder is AlCalc for the Pilot, which goes
# as high as 10^32767 and can handle 9274! (9274 factorial)
#
# But no other calculator can tell you that
#
# (27 ^ 86) ! = 10 ^ (1.534607.. x 10 ^ 125)
#
# or that
#
# 27 ^ (86!) = 10 ^ (3.467778.. x 10 ^ 130)
#
# (in other words, the first has over 10^125 digits and the second,
# with over 10^130 digits is "just a little bit" larger.)
# Project notes follow
$ignored_block_comment = qq@
Hypercalc is an open-source interpreted calculator program designed to
calculate extremely large numbers (such as your phone number raised to
the power of the factorial of the Federal budget deficit) without
overflowing.
It does this by using a modified form of the level-index number system
with a radix of 1.0e300
Performance stats for other calculators
Year Make and model Overflow Digits sin/cos
(pi, e) radians
1973 TI SR-50 1.0e100 589,459 ?
1980 Sharp EL-5100 1.0e100 12? ?
1989 Casio fx-7500G 1.0e100 59,459 1.57e8
? Casio fx-115D 1.0e100 6,45 1.57e8 = 1/2 * 1e8 * pi
1995 Casio CFX-9800G 1.0e100 5898,45904 1.57e8 = 1/2 * 1e8 * pi
1997 Pilot AlCalc 1.0e32768 32? n/a
1998 Casio fx-260 1.0e100 6,45 1.57e8 = 1/2 * 1e8 * pi
1998 Casio 9850 1.0e100 5898,45904 1.57e8 = 1/2 * 1e8 * pi
1998 Casio 7400 1.0e100 5898,45904 1.57e8 = 1/2 * 1e8 * pi
1998 Sharp EL-531L 1.0e100 58,44 1.7453e8 = 5/9 * 1e8 * pi
1998 TI-85 1.0e1000 5898.459 1.0e12
1998 TI-92 1.0e1000 5898,459 1.0e13
1999 TI-89 1.0e1000 5898,459
? Mathematica 1.44e323228010 n/a ?
1998 Hypercalc 32768pt300 58979,45905 1.35e10 = 2^32 * pi
(for Palm)
1999 Hypercalc (10^10)pt300 300 1.35e10 = 2^32 * pi
(for UNIX)
The overflow value for Hypercalc is so large it can't be represented
in the standard way. If we use Hypercalc's internal "PT" format it's
easy.
Hypercalc handles numbers with absolute value greater than the range
supported by the floating point library by storing the numbers in
many different formats. When the numbers are within normal
floating-point range (less than 10^300) they are stored in the normal
floating-point format. Between 10^300 and 10^(10^300) they are stored
as logarithms, and Logarithmic Number System (LNS) algorithms are
used. When the logarithm gets too big to store as a floating point
number, the log is taken again, and so on. An integer field is used
to keep track of how many times the log has been taken. Here are some
examples:
pt-notation pt val represents
0pt1.0 0 1.0 1.0
0pt3.45e10 0 3.45e10 3.45 x 10^10
0pt1.0e299 0 1.0e299 1.0 x 10^299
0pt9.9e299 0 9.9e299 9.9 x 10^299
1pt300.0 1 300.0 1.0 x 10^300
1pt300.301 1 300.301 2.0 x 10^300
1pt301.0 1 301.0 1.0 x 10^301
1pt834.173 1 834.173 1.489 x 10^834
2pt79.0 2 79.0 10^(10^79)
3pt34.0 3 34.0 10^(10^(10^34))
254pt1.0e10 254 1.0e10 10^(10^(10^(10^ ... ^(10^10) ... ))), where
there are 256 10's
32767pt1.0e300 32767 1.0e300 10^(10^(... ^(10^300) ...)), with 32768
10's; the highest number Hypercalc can
handle (on the Palm platform; UNIX
Hypercalc can go to about (10^10)PT1.0e300)
(To read about even larger numbers, go to www.mrob.com and click
on "Large Numbers".)
Each time we transition from the top of one pt range to the bottom of the
next, about 2 1/2 digits of precision are lost as the information formerly
stored in the exponent has to be absorbed by the mantissa. Then, as we
proceed up the range digits are gradually gained back until we reach the
top of the range and we once again have a 2.5 digit exponent. So, for
example at the top of the pt=0 range the values are things like
1.23456789012345 x 10^299, and there are 53 binary digits of precision in
the mantissa, or almost 16 decimal digits. Then we cross over into the pt1
range and store the log instead, which becomes a value like
301.456789012345 -- we still have 15+ digits to work with, but the first
three correspond to the exponent of the number and there are only 12+ or
13 digits left for expressing the mantissa. Of course as we keep going up
we get to values like 123456.789012345 (which represents 6.15 x 10^123456)
we lose even more mantissa digits to exponent, but eventually we'll get to
values like 123456789012345000000 = 1.2345... x 10^20, which represents
10^(1.2345... x 10^20) and as we go on up to even bigger numbers we see
that since the exponent needs to be printed it once again holds information
equivalent to 2 1/2 digits.
This entire issue of variable numer of digits and the associated
problems it causes with nonintuitive roundoff performance would be
avoided if one used a "natural" PT storage format, where e is the
base and the representation is such that the floating point value is
always in the range [1.0, e]. So, for example, the number 143 would
be represented as {2, 1.601979...} because e^(e^1.601979) is 143.
Such a format would be unwieldy for normal calculations, however,
because you'd have to keep doing e^x and ln(x) all over the place
when doing simple calculations like 25 + 2.
@;
$hypercalc_revision_history = qq|
REVISION HISTORY
Hypercalc is one of my oldest projects; it began in 1998 as a Palm
Pilot program in C. To creat the Perl version, I hand-converted most
of the actual calculation functions to Perl and added a command-line
interpreter.
Revision history for PalmPilot version of Hypercalc:
1998101x Start project from "SampleCalc" example.
19981018 Fairly complete scientific calculator, except trig functions
19981021 Start implementing PT functions, get pt.exp and pt.mul working.
19981022 Implement add/subtract, pow, pow10/log10, and gamma.
19981024 Pretty much complete on the PT functions; they even handle
infinities properly. Also, add a "tiny" font to print exponents when
using the stdFont.
19981025 Refine the formatting code for PT1's and higher so it
computes exactly how many digits of mantissa can be shown. Add some
more buttons, but most not implemented yet. Implement rounding
(incredibly complex!). Add arc, hyp, sto, rcl, 2nd, and 1/x keys (but
only implement 1/x)
19981026 Add the same formatting refinements to PT0's, so it can
print contents of memories (which have fewer pixels available).
Implement Sto and Rcl keys.
19981028 Add hyperbolic functions and inverse trig (but not inverse
hyperbolic trig)
19981030 Add inverse hyperbolic functions.
19981031 Put f_xxx and pt_xxx routines in their own files. Implement
floating-point square root based on the grammar school algorithm
(greatly increases speed of inverse trig functions).
Revision history for Perl implementation of HyperCalc:
19990610 Start writing a simple Perl calculator program using a new
concept: expression evaluation via regular expressions (I got the idea
while writing the #top100# movie statistics program). Right now it
just does + and @ (kind oflike *).
19990701 Break '+' operator out into a separate subroutine add1
(eventually all operators will be done this way)
19990720? Add all the code from the Pilot Hypercalc, to eventually
merge and translate into Perl.
19970721 Parsing routine is fairly complete and now includes nested
loop to handle parentheses. Subroutines for all four operators +, -, *
and /. "e" and "%" in an expression represent 2.71828... and previous
result, respectively.
19990725 Add splt() and start writing first operator that handles PT
types: p_add, pt.add, pt.addpos.
19990727.2125 Do lots of porting work: put all routines in "proper"
(Pascal) order; lots of global replaces to change things like "x.pt"
to "$x_pt"; replace Taylor and Newton algorithms with builtin
functions where available; minimum work to get pt.addpos working. It
now properly adds 1e300 + 1e300 (and gets 1pt300.3010299...)
19990727.2154 pt.add fully works; pt.div works.
19990727.2235 pt.sub and pt.mul work now. Output formatting handles
some of the special cases to print values like 1p2345.6789 as 4.77 *
10^2345 rather than as "1 PT 2345.6789"
19990727.2722 pt.ln works; parser handles ln() and log().
19990728.1333 It now handles exp() and pow(), so I can compute really
big values without lots of repetitious keystrokes.
19990728.25xx eval2() now stores all operator results into an array,
and stores the array index into the expression string. This is to
avoid numbers getting converted from strings into floating point and
back again, and that dramatically reduces roundoff error.
19990729 Start editing all the f_xxx routines so the primitive
floating-point type can be changed easily later. This involves
implementing a minimal set of "primitives" like f_int, f_le, f_neg,
f.mul, etc. and making all the other f_xxx routines do all their
operations by calling these primitives. Also, inline constants like
"10" are replaced with globals.
19990801 &pt.root and &pt.log_n work. All of the &f_xxx routines are
'primitivized', but &pt_xxx routines still need some work.
19990801.2529 Add "debug" command. Put most of &f_xxx primitives
inside $f64_prim so they can be defined and redefined via &exec.
Create $g_pt_inf to distinguish uses of infinity in PT field from its
uses in VAL field. A few other changes to support switching VAL
primitive precision. Make it auto-promote inlines like "23e456".
19990802 Pretty much finished making the &pt_xxx routines call &f_xxx
routines explicitly.
1999080x Use open2() to launch #bc#. Write bce.
19990805 Write &fbc_fix2sci, &fbc_split, &f.cmp, comparison primitives,
&f_neg, &me_magcompare, &m_truncround, &me_addpos, &f_add, &f.sub.
&fbc_encode renamed to &fbc.sci2fix. Redirect stderr when launching
#bc#.
1999080x Write &me_subpos
19990811 Add HC_LOG debug log, lots of calls to dbg1. Fix lots of
bugs. Write bc version of &f.mul and &f_div.
19991015 Fix bug that caused small PT1's to be printed as e.g. 10 ^
301.30103. Make dbg1flag a bitmask to allow debugging functions,
expression parsing, or both.
19991017 Add variables (currently limited to all-alphabetic starting
with "v")
19991018 Change single-letter function abbreviations and special
letters like 'e', 'p' etc. to uppercase, to clear the lowercase
namespace for use by user variables.
19991019 Fix some bugs relating to infinity handling and conversion
in fbc routines. Four basic functions almost work (subtraction still
seems to have problems)
19991117 Variables no longer need to start with 'v'. Add 'sqrt'
function.
19991124 Combine parsing of e/pi/phi with the variable and function
parsing; add error-check for undefined variables.
20000120 Write fbc versions of f_ln and f.exp; fix bugs in fix2sci
and sci2fix; it now correctly computes 2^100 in scale 30. Fix bugs in
switching back and forth between f64 and fbc.
20000206 Fix bug that prevented "sqrt(1+2)" from working
20000304 sqrt now goes through f_sqrt. Fix bugs that made bc hi.init
not compute g_pi properly.
20000728 Remove dependency on "rpmlib.pl"
20010102 Add ERASE_BS test.
20010103 Clean up internals of eval.2. Fix "right-to-left precedence
bug": "4-3-2" used to give "3", and "4/3/2" used to give "2.66667". I
am deliberately leaving exponents that way: 4^3^2 still gives 262144.
20010107 Fix bugs: 2+2/(1+1) gave 2; 7^-1 didn't parse; scale
50,27^27 printed in scientific notation. Write &pt.roundup. Fix &prnt1
handling of high PT1's. fbc-based PT calculation is actually usable
now!
20010108 Add history array and define_hist. Conversion across scale
changes works, at least in the cases I checked. Fix bug in eval.2:
sqrt() and other functions had become broken as a result of
yesterday's fixes. Clean up fbc version of f.gamma a little, but it
still suffers from a fundamental limit of the Stirling formula method,
which basically requires that the number being factorialed must be at
least as big as the 15th root of 10^curscale. Combined with the current
limit of 1.0e300 for the fbc float data type, that means we can't get
more than 33 digits of accuracy out of the f.gamma function. Increasing
the exponent limit would fix it, but that poses another problem with
the scaling loop -- for 50 digits of accuracy, the scaling loop has to
loop 2154 times (because 2154 = 10^(50/15)).
2001010x Finish implementing #format# command.
20010109 Fix bug that made history list usable only for first 9 items.
20010115 Write init_pi_2, which calculates g_pi much more quickly.
Decrease gammalim.
20010116 Add input history.
20010117 Change letters I/H for input and output history to C/R
(commands and results).
20010210 Fix "c2" in case where c2 is a variable assignment, and add
';' symbol to separate commands.
20010216 Add ability to take "1e9" as input (used to require "1.e9")
20010521 Make 'x' a synonym for '*'. This works pretty well, in fact
you can even define a variable 'x', and the expressions '2 x 4', '2 x
x' and 'x x x' all do the right thing! But, that's not recommended.
Also, change default output format to format 1, and make it print
multiplication as 'x' because it looks better.
20010521 Map [] in input to (). This almost solves the problem of
having output and input formats match -- the one missing piece is
allowing the user to type 'PT', such as '3 PT 1.2 x 10^45'.
20010530 Almost fix the ambiguity of '!!': You can now type "4!!" and
it will give you (4!)!, rather than '4' followed by the previous typed
line.
20010601 When ';' is present in input, print each of the commands
with its 'C# =' label as they're being added to input_history.
20010610 Detect presence of UNIX and doesn't try to run #bc# if not
on UNIX.
20010613 Fix some of the bugs in handling of '-'. Add &pt_negate
20011026 Fix some bugs in command history expansion.
20011104 Add autodetect of ^H and call stty erase if they type it
(Unix only)
20020129 Move automatic stty erase fix to subroutine fixerase.
20020301 Read first expression from command line.
20020305 Fix some bugs in rounding and pr.nt2 -- but it still has the
problem that "scale=15" prints the same number of digits as the
default "scale=14"
20020306 Now can put multiple commands including 'scale=' and 'quit'
on command line
20020711 Convert tabs to spaces in input
20051228 Begin to implement BASIC interpreter
20051229 Add OLD, SAVE and indenting
20050102 Disambiguate input like "10 x = 2" and "10 x 2" (the first
is a BASIC line, the second is a calculation)
20060716 Add &format4 and &format6.
20060719 Add &format5.
20061112 In canon_sci, remove trailing zeros from mantissa. (This
affects format5, and might not be what we want in all cases, but it
fixes cases like 10^27)
20061126 FOR..NEXT works enough to compute, e.g. the value of Mega.
Also add CAT and rudimentary version of PRINT.
20061129 Try to fix broken "x = x + 1"; this might cause other bugs.
Make "old" and "save" conscious of current file name.
20070513 Add #help debug#; slight changes aimed at an eventual merge
of f64_prim and fbc_prim
20070621 Fix a flaw in format5
20070624 Add mantissa precision to range 1e5..1e7 in format5
20070629 Add BASIC help
20070809 Make format5's output a little more useful
20071224 Add ability to input time in HH:MM:SS.frac format, and
format7 to print out in that format.
20080204 Fix bug that prevented nested loops from working. Make BASIC
statements not print if they are followed by a semicolon.
20080215 Accept abbreviated PT notation (e.g. "2P100" for Googolplex)
in input.
20080508 A couple fixes intended to make negative exponents in
scientific notation (like "1.23e-4") work again.
20081001 Slight improvements to #help var#
20100113 Add #help format#. Add a missing pair of {} in prnt1 case p2_e.
Remove extra () and [] from most formats.
20100204 Add a check for Cygwin and treat it as UNIX (for now; we
really need to also check for #bc# on all "Unix" systems)
20100221 Fix a couple bugs related to creating and using directories.
Eliminate reference to my personal environment variable FROMWORK.
20100318 Upgrade PRINT statement to support literal strings and
multiple arguments separated either by comma or semicolon (the latter
suppresses the trailing newline). Also, a trailing semicolon on
ordinary statements, LET and FOR suppresses printing of the value that
has been computed.
20100329 Add $pt_str; handle $apt greater than 9 in &for.mat5.
20100601 &do.cmd3: Don't allow user to "set" the constants pi, e or phi
20100603 &do.cmd3: setvar errors now stop BASIC; detect setting r1, etc.
20100611 Add $format.debug and several changes to &splt, &pr.nt2,
&canon_sci, &for.mat5, and &prnt1 which collectively make for.mat5
labels more accurate, correctly handle denormalised scientific
notation input like "13.72e9", and properly deal with infinity (e.g.
from inputting "1/0"). Also, &BASIC_old now allows whitespace blank
lines anywhere in its input.
20100612 Allow '_' in variable names and program names.
20100614 Turn most commands to lowercase (except lines inserted into
BASIC programs, and quoted strings in PRINT statements).
20100705 Bug fix in mantissa rounding in &canon_sci.
20100916 Add sine, cosine and tangent
20101108 HC_LOG is now $g_logfile; use IO::Handle->flush operator to
flush log output before each input prompt.
Several bug fixes: fbc_split now handles '+inf' and '-inf'; compound
statements in BASIC work again; detect wrong variable name on NEXT
statement.
20110821 BASIC 'old' and 'new' now print a message if/when they do
nothing because the user did not give a name.
20110822 Allow bare REM statement; print line number on "unrecognised
variable/function" error.
A couple bug fixes in parsing '!!' and 'x' for multiplication; some
preparations for uncertainty (error) component. Bug fixes in handling
statement separators ':' and ';' (which had already been broken and
which I broke a little more in today's changes).
20110823 More groundwork for uncertainty, mostly in &normalise (which
is now used for all number literals, even when not specified in "1.23e5"
format). Fix bugs in fbc.sci2fix (parsing mising sign), fbc version of
f.exp (negative exponents)
20110828 A little cleanup in &eval.1; add &init_ir
20110829 Begin work on handling uncertainty: changes to pt.add,
pt.addpos, pt.subpos; add pt.addlog.
20110830 Add and subtract now handle uncertainty (for PT0 results
only). Rename pr.nt2 to "prnt3"; &pr.nt2 is now a wrapper for prnt3 that
inserts the uncertainty digits into the output.
&pt.mul and &pt.div now handle uncertainty.
&pt.log10, &pt.pow10, &pt.exp, &pt.pow and pt.sqrt now handle uncertainty.
Some rewriting and expansion of built-in help text
Add &cv.60_10 and make HH:MM:SS (sexagesimal) input work again.
20110831 Get uncertainty calculations working in #bc# mode; also use
native floating point for most uncertainty calculations, since their
precision never needs to be more than that. A couple changes to ensure
uncertainty is always positive.
Add &insert_uncertainty to encapsulate most of the new code in &pr.nt2,
mainly so I can use it elsewhere e.g. in format7.
20110901 Get format7 to display uncertainty; fix a bug with
uncertainty exponent in normalise case 0.D
20110902 Add uncertainty calculation to the remaining functions
(pt.gamma, pt_log_n, pt_root, and the trig functions). Fix a couple
bugs with infinity handling in prnt1 and prnt3. Consolidate TTD notes
on Gamma precision issues.
20110908 Fix a couple bugs related to uncertainty and base-60 with
fractions.
20120102 Fix handling of "1.23e-4" when using #bc# primitives (labels
are still broken in format 5)
20120109 Rework the Gamma function code, adding more terms (suitable for
31-digit precision) and computing the cutoff values and precision levels
for a more informative "factorial will give only..." message. Also,
fix broken handling of "1.23e45" in #bc# primitives (a bug produced
by the "1.23e-4" fix)
20120111 BASIC changes: Loop ranges can be a simple variable (e.g.
"for x=a to b"); single-quote "'" is now a synonym for "rem" to mark
comments.
Use f.mul instead of explicit multiplication in division's
uncertainty calculation.
20120204 Fix formatting of "39.37" when format=5
20120422 Fix a number of Perl warnings (as suggested by Michael Samos)
(add "use warnings;" at top of file to see any remaining warnings).
20120426 Stop trying to use f.gamma for large values when scale>14.
20120610 Show more digits in format.5 for values in the range (1e5..1e10)
20120612 Expand range of base-60 input and output.
20120613 Fix a bug with parentheses in expand_C_hist and define.Chist.
20121023 Return "nan" (not a number, i.e. undefined) for 1/0, 0^-1,
and several other similar cases.
20121028 Add automatic multi-page help (like in #mira#)
20121101 Fix bugs in fbc f_div that caused 1/inf to give 1einf, which
was interpreted as 1e0, rather than giving 0. This caused Gamma
accuracy problems for huge numbers of digits. For example,
"scale=200", "(10^100)!" gave an answer starting with 2.20515, but the
correct answer starts with 1.629404332. The Gamma value is too small
by a factor of e^2. Taking the factorial of 10^50 gives an answer too
small by a factor of e.
20121102 Add &set.var and predefined variables "vigintillion",
"googol", etc. for some commonly-known big numbers. These should work
at all precisions.
20121103 Add &is.nan, &is.inf, etc. and use them in most places,
hopefully to get more consistent handling of special cases. Work a
while on fixing the inconsistencies, e.g. for a while 1/0 gave NAN at
scale=14 but Inf at scale=200.
20121209 Add g_BASIC.unsaved variable, accept single-quote REM
statements on immediate input.
20130118 Fix bug in parsing OLD command
20130208 Handle the syntax error that comes if you type "1+2t" where t
is a valid variable.
20130224 &define.Chist does nothing when BASIC is running. &BASIC.save
sets g_BASIC.unsaved to 0.
20130604 Add date to splash screen
20131212 Add force_ee param to prnt3 and pr.nt2, use it in prnt1 when
we're printing any PT1 or higher, to tell it we need the Mx10^E form.
Add g_min_mant_digits constant (currently set to 5). Here is a
comparison of selected lines of the output of 'vals.basic':
-------------before------------- ---------------after---------------
3.6846676 x 10 ^ 30102999 3.6846676 x 10 ^ 30102999
4.613 x 10 ^ 301029995 4.613 x 10 ^ 301029995
4.36353 x 10 ^ 3010299956 10 ^ ( 3.0102999566398 x 10 ^ 9 )
2.5021 x 10 ^ 30102999566 10 ^ ( 3.0102999566398 x 10 ^ 10 )
9.626 x 10 ^ 301029995663 10 ^ ( 3.0102999566398 x 10 ^ 11 )
6.88 x 10 ^ 3010299956639 10 ^ ( 3.0102999566398 x 10 ^ 12 )
10 ^ ( 30102999566398 ) 10 ^ ( 3.0102999566398 x 10 ^ 13 )
10 ^ ( 3.0102999566398 x 10 ^ 24 ) 10 ^ ( 3.0102999566398 x 10 ^ 24 )
...
10 ^ ( 1.388651 x 10 ^ 301029995 ) 10 ^ ( 1.388651 x 10 ^ 301029995 )
10 ^ ( 1.31355 x 10 ^ 3010299956 ) 10 ^ [ 10 ^ ( 3.0102999561184 x 10 ^ 9 ) ]
10 ^ ( 7.5321 x 10 ^ 30102999565 ) 10 ^ [ 10 ^ ( 3.0102999565876 x 10 ^ 10 ) ]
10 ^ ( 2.897 x 10 ^ 301029995663 ) 10 ^ [ 10 ^ ( 3.0102999566346 x 10 ^ 11 ) ]
10 ^ ( 2.07 x 10 ^ 3010299956639 ) 10 ^ [ 10 ^ ( 3.0102999566393 x 10 ^ 12 ) ]
10 ^ [ 10 ^ ( 30102999566397 ) ] 10 ^ [ 10 ^ ( 3.0102999566397 x 10 ^ 13 ) ]
20131217 Reduce g_min_mant_digits to 3 and make the
mantissa-truncation algorithms a bit more accuate.
As a convenience for #bc# users and those with numeric keypads, a
bare '.' is now a synonym for '%', and '**' is a synonym for '^'.
20131218 Add a lot to the built-in help
20140123 Print the "factorial will give only XX digits" warning only when
the scale changes (so if you do "scale 50" twice in a row you only get the
warning once).
20140715 Add comparison operators; add demotion to &splt so that
e.g. '1p1.000(1)e2' is converted to '0p1.00(26)e100'; add comments in
TO DO section outlining plans for a more complete BASIC language
20140717 Add GOTO, catch ^C so user can stop a runaway BASIC program.
Add IF..THEN (one-line goto variant) and STOP; print statistics when
program finishes running.
20140718 Add READ and DATA
20140719 SQR is now an alias for SQRT; change the marker character in
eval.1/2 to single-quote (previously it was backtick and before that
pipe). BASIC implementation is now pretty much on par with the October
1968 version, except for the lack of DEF FN_ and MAT statements.
20140720 Add INPUT statement.
20140722 Add block comment section describing how the parsing and
evaluation work; remove unused 'showeach' parameter in a few places.
20140723 Start writing qualify.basic and fix a few bugs revealed by it
(most notably,'0.00(1)' works now).
20140904 Add Chuquet names million through nonillion
20141208 Switch to GPL v3.
20160317 Fix a couple bugs in parsing of PRINT statement arguments
20160520 Allow 0s at beginning of a large number input.
20180105 Format=5 now includes input expression, which is often useful
20180208 fix.erase now removes any remaining non-ASCII characters,
this is to avoid messing up the screen when the user enters "up-arrow"
as input (and without having defined the variable 'a'), causing
normalise to print "ESC ( 0" which renders the terminal unusable.
20181025 Add -h/--help and 'help manpage'
20190207 Date of a version sent me by Alan Eliasen, which implements
a generalised 3-argument hyper function (that version of hypercalc is
called 'ae-hypercalc' and is in my ~/bin, see its help and revision
history for more info)
20190212 Allow direct input of 'U' in numbers (e.g. 1.23U1); rename
&split() to &splt() to avoid confusion with the Perl builtin
20190314 A little more margin in pt.mul's check against scale in the
PT2*PT2 case; add comments in pt.div about unhandled cases
20190319 Suppress printing messages when setting 'scale' if there is
a semicolon; useful for BASIC programs that need higher precision but
don't actually use the gamma function.
20190327 Avoid printing unneeded "1 x " before "10 ^"
20190328 Add notes about stuctured IF; avoid saving a file as
"foo.basic.basic"
20180330 Fix PRINT tab and newline after a string; structured multiline
IF .. ELSE .. END IF now works.
20190409 Make pt.roundup do nothing if the input is an integer and
has fewer digits than scale. This fixes imprecise result in j from the
statement "j=1e5000". pt.div() now does integer precise 10^N in cases
like 1p5000/1p4900 (a test of an idea I want to do more of). Replace a
lot of f.mul calls with literal multiplcation in the f64 (native)
versions.
20190411 Add boolean operators AND, OR, NOT and equivalent symbols &,
\|, ~ or !. For now, the first two also function as cheap bitwise
operators. ! is now overloaded: !6 is logical NOT, 6! is factorial.
20190413 Implement GOSUB and RETURN; add RND() function.
20190414 Improve parsing of variables and functions in eval.1() to
prepare for user-defined functions and arrays.
20190415 More eval.1 changes, add constants true and false.
20190418 Add reading of array values (but still can't set an array value)
20190419 Track lvalues throughout expression evaluation (including
calls to define_ir and splt) and determine if an application of the '='
operator can be interpreted as an assignment; only eval_3 needs to be
changed to make it possible to assign to an array element.
20190420 add pvu.sint, p.1d_lookup and p.2d_lookup; 1D and 2D arrays
now work (after refactoring assignment, IF evaluation, and a few other
things); eliminate use of 'A' for arctan.
20190421 Add RESTORE statement
20190422 Several fixes (e.g. READing array elements) to facilitate
wumpus.basic
20190504 Add 'pragma' command
20190505 Support out-of-order NEXT (happens when a GOTO exits a loop
causing a different NEXT to be reached)
20190907 Add p.mod and pt.mod, and some changes to try to overload
'%' as both a "variable" and an operator. Programs rubik.basic and
bb2.basic demonstrate it.
20190911 'run' rommand now accepts an argument, it will load and run
the specified program. Trace is now off by default.
20190927 Increase g.gammalim to 101 and add a little more debugging;
add simple.nint and use it in f.gamma, pt.gamma
20190928 Add intgam and return exact values for factorial when scale>14
and argument is in [1..164]
20190929 fbc primitives for add, sub, mul, and div now preserve
simple.int property whenever possible. Modulo now works when scale>14.
20190418 #list# now only puts one blank space after the line number.
20201221 Fix a bug in fbc f.add calling simple.nint
20210615 Add p_vr2 and fix parsing of e.g. "%-2", which had been broken
since 20190907. Note that modulo still doesn't work when scale is large.
20211002 Improved pt.mod when using fbc_* primitives.
20211102 Statements can now be separated by ';:' which combines the
effects of ';' and ';' (just the value is printed)
20220311 Add int() function (using the existing two versions of f_int
that were there for ages). Fix parsing error with arctan(). CAT command
now does multiple columns.
20220819 Use normal time() routine as a fallback for missing Time::HiRes
20230219 Print a distinct error for IF without END IF
20230220 Add $listerrs and @forline for better error reporting
20230222 Add $inbline for better syntax error messages when running a
BASIC program.
20240127 Allow : or ; after IF..THEN; semicolon after LET assignment
should work now; add to 'help basic'
20240731 Setting scale to 0 or negative now sets it to the default.
Fix bug in pt_mod, "3 % 4" now works properly when using fbc primitives.
Fix bug in argument parsing, allowing a command like "run mb1997.basic"
to be sent from the command line.
|; # End of revision history
$ignored_block_comment = qq@
BUGS, TTD / TO DO and FUTURE IMPROVEMENTS
LIST parser does not handle "if a<0 then goto 200" and reports it as a NEXT WITHOUT FOR
Accept multiple 'e' in input to help support common usage (these are
also accepted by a lot of the JavaScript libraries):
e1.2e34 = 10^1.2e34
ee123 = 10^10^123
ee1.2e34 = 10^10^1.2e34
eee123 = 10^10^10^123
... (etc.)
New "pp" for towers of tetration:
3pp7 = 10^^10^^10^^7
And reserve "`" for future use in providing a lexicograpically logical format:
45`1.23 = 10^45*1.23
6p45`1.23 = 6 PT 1.23e45
4pp = 10^^^4 = 10^^10^^10^^10
4pp`45`1.23 = 10^^10^^10^^10^^(10^1.23e45)
4pp`3p`22`1.11 = 10^^10^^10^^10^^(10^10^10^(10^22*1.11))
Factorial in bc mode is wrong for arguments above about 166 (compare
f_gamma to pt_gamma)
Modulo (%) does not work when scale is 15 or more (i.e. when using fbc
as native type). I think fixing this would require not always normalising
'17' to '1.7e1'.
Add pragma modes for sloppy NEXT and strict NEXT. Add ALTAIR (or
MSFT?) pragma which implies sloppy NEXT. If in strict NEXT mode, also
prohibit GOTO from exiting a loop scope.
"INPUT A(I)" does not work because it only recognizes plain variables.
To handle this I need to do the same thing that assignment does:
evaluate the argument of INPUT to discover if it is an lvalue and
get its lvalue key if so.
When loading a BASIC program, reset scale, format, pragma to defaults
(but the program or the user can then change any of these)
Arguments to LIST command to show parts of a program (have it do a full
BASIC_parse to get the indentation, saving the lines of text to a temp
array, from which the partial listing can be generated)
Try to unify and improve the format and content of the common parsing
errors. Here are two examples:
C1 = 4!0!6
*** Cannot parse '4!0!6' (nrm1)
C2 = b=4;d=6;b!0!d
*** Invalid syntax; I was left with 'I5!0!I4' (ev1)
In the (ev1) case I'd like to have it substitute I5 and I4 before
displaying to the user. The code near the end of eval.1 shows how to
recognise and convert 'I5'. The same would be useful in other error
messages such as "Illegal array index 'I4'"
Add \ as shorthand for int(a/b) (as in VB).
Exact integer exponentiation -- might need a faster int function in bc
(the current f_int does fbc.fix2sci(fbc.sci2fix(x)) ) Look at the place
in pt_div that calls simple.int() and note the limited range of cases
(start with quick tests base and exponent both integers, 1 to jump out of a loop.
This can be done in BASIC.parse by creating a new "scope-zone" with
each FOR statement, remembering the scope-zone of each non-FOR line,
and making sure control never transfers into a different scope zone.
NEXT statement should be tagged with the scope-zone that was created
by its FOR, so you can GOTO the NEXT statement from within the loop,
and a FOR should have the scope-zone of the loop it's inside (or 0 if
it's at the top level)
* Generalize IF to allow IF .. THEN FOO for any keyword FOO
* It's okay to overload bitwise and logical operations onto the same
symbols (that's what VB does)
* 1970's extensions by Microsoft:
- strings and functions ASC, CHR$, LEFT$, LEN, MID$, RIGHT$, SPC,
STR, TAB, VAL
- single-character input with GET
- SGN function (returns sign -1 0 or 1)
- STEP parameter in FOR loops
* (see also error messages at www.vintage-basic.net/downloads/Vintage_BASIC_Users_Guide.html)
Other BASIC ideas of my own:
* PRAGMA command to select different BASIC compatibility modes
Write some BASIC programs to test Hypercalc. For example, a program
that tests all branches of the pt.gamma code.
Add is.nan and isinf routines and use them to standardize the tests for
Infinity and NAN throughout all the functions (like f_div and pt.div).
Currently the uncertainty field is forced to zero whenever a PT1 or
higher quantity is computed. This is based on the idea that a computed
answer is useless if the uncertainty is a lot larger than the value.
But actually, such values are useful for computing something like the
number of possible universe-histories.
But if I am going to extend uncertainties to PT1 and higher quantities,
I need to address two issues:
* How to transition gracefully from a ratio-based limit similar
to what I am doing now, to a ratio-of-logarithm limit for PT1s, and a
ratio of log-log limit for PT2, etc.
* How to display quantities with very large uncertainties. Consider
these examples:
1.12e100 to 1.34e100 : "1.23(11)e100" (uncertainty is in the mantissa)
1.0e100 to 2.3e101 : ???
1.00e101 to 1.00e103 : "1e102(1)" (uncertainty is in the exponent)
Add Lambert W function. Based on the paper "Corless 1996 Lambert.pdf"
(pp. 21 and 27) the function can be computed by Halley's method
iteration, as in this example:
C1 = ln(10)-ln(ln(10))
R1 = 1.4685526477461
C2 = %-(%*e^%-10)/(e^%*(1+%)-(%+2)*(%*e^%-10)/(2*%+2))
R2 = 1.7416030169899
C3 = !!
!!: %-(%*e^%-10)/(e^%*(1+%)-(%+2)*(%*e^%-10)/(2*%+2))
R3 = 1.7455279920176
C4 = !!
R4 = 1.7455280027407
C5 = !!
R5 = 1.7455280027407
C6 = %*e^%
R6 = 10
Presently, 10^10^7 prints "1.00000012{x}10^{10000000}". The erroneous
low-order digits can be eliminated by the following method:
Choose an epsilon e.
Convert (x-e), x, and (x+e) to strings
Compare "(x-e)" to "x" and keep all digits that are identical.
Compare "x" to "(x+e)" and keep all digits that are identical.
Keep whichever of the preceding strings yields more digits.
When a value "rolls over" from one PT level to the next (for
example, when multiplying 10^299 by 100) the number of mantissa bits
diminishes (or increases) by about 10 bits (because the rollover
cutoff value is approximately 2^2^10). Error introduced during such an
operation can propagate from intermediate results into any final
result, so th epsilon should be chosen as if the mantissa precision is
43 bits, rather than the actual precision of 53 bits.
If the above doesn't always work, use a slightly bigger epsilon.
Regardless of what is displayed, retain all the bits of each result
for use in subsequent calculations (I think hypercalc already does
this)
For more precision in f.gamma and pt.gamma, perhaps I can use the
Lanczos series given in A090674 and A090675. Details and analysis are
in "Pugh 2004 Analysis.pdf". There is a good practical explanation at:
www.vttoth.com/CMS/projects/41-the-lanczos-approximation
Source code is in "viktor-toth-lanczos-gamma-function.tgz"
Documentation in "VT Toth Gamma web page.rtfd"
(old URL: www.rskey.org/lanczos.htm)
en.wikipedia.org/wiki/Lanczos_approximation
I might also want to look at PARI's implementation (mpgamma() and
mplngamma() in src/basemath/trans2.c)
NOTE: To evaluate accuracy of gamma, compare "ln(299!)" in Hypercalc
to "lngamma(300.0)" in PARI/GP.
The following method to compute gamma(z) might work better:
Gamma(z) = x^z e^(-x) SIGMA[ n=0..K, x^n/(z(z+1)(z+2)...(z+n)) ] + R
= x^z e^(-x) SIGMA[ n=0..K, x^n/PI[ i=0..n, z+i ] ] + R
where R, the "residual", is an integral that is always less than x e^(-x)
Thus we can approximate gamma(z) by the following steps:
* Given D = the number of digits that are needed
* Determine x such that x e^-x is less than 10^-D (and thus R is less
than 10^-D). For example, if D=30, x is about 73 because 73*e^-73 is
about 10^-30. A good quick approximation is x = D ln(10) + ln(D) + 1
* Determine K such that the Kth term of the SIGMA is 10^D times smaller
than the initial term. For example, if D=30 and z=0 (the smallest z
we need to deal with), K is about 256, because 73^256/256! is about
10^-30. For this step the Sterling approximation can be used for the
factorial function.
(Alternately, and to avoid doing more terms than we need when z is
larger, just keep adding terms until the next term is less than 10^-D
times the sum)
The drawback of this approach is the huge number of terms that need to
be added together. In this example there would be about 32000
multiplications to do the nested SIGMA and PI the simple way.
Obviously the partial product PI[i=0..n, z+i] can be carried from
one term of the SIGMA to the next, but it's still a lot of work.
OVERVIEW OF OPERATION
mainloop
BASIC_run
docmd10
(calls docmd6)
docmd6
docmd4
docmd3
expand_ih2
set_var, hi.init, printing and formatting routines
eval_1
eval_2
p_add, p_sub, p_mul, p_sqrt, p_negate, p_fact, p_ln, p_logn etc.
pt_add, pt_sub, pt_mul, pt_sqrt, pt_negate, etc.
pt_addpos, pt_addlog, etc.
f.add, f.sub, f.mul, f.sqrt, f_neg, etc.
("f64 version" uses built-in functions;
"fbc version" uses a child #bc# process)
Lexing and parsing are all done by string manipulation. To illustrate,
consider the following sequence of input: ("3+4", "j = 17",
"c1*ln(pi+j)"). Here the actions needed for the third input are
described in detail; each description starts with the literal value of
the string currently being evaluated.
C1 = 3+4
'c1' is defined to be the string '3+4'
C2 = j = 17
'j' is defined to be 17
C3 = c1*ln(pi+j)
In do.cmd3, 'c1' is replaced with '(3+4)'
(3+4)*ln(pi+j)
In do.cmd3, this string "(3+4)*ln(pi+j)" is stored into command history C4.
In eval.1, single-quotes are placed around the whole expression
'(3+4)*ln(pi+j)'
In eval.1, the value of 'j', "0 PT 1.70E1(0)", is stored in intermediate
value I5
'(3+4)*ln(pi+I5)'
In eval.1, the value of 'pi' is replaced with I2 (which is always pi)
'(3+4)*ln(I2+I5)'
In eval.1, the predefined function name 'ln' is replaced with its
abbreviation 'K'
'(3+4)*K(I2+I5)'
The expression is now in canonical form ready for processing by eval.2.
In eval.2, the first part '(3+4)*K(' and the final part ')' are "held"
so that it can work on the section 'I2+I5' which contains no parentheses
'I2+I5'
In eval.2, the addition is recognized and performed, the result
"0 PT 20.1415926535898(0)" is stored in I6
'I6'
In eval.2, the "held" parts of the expression are restored. Since the first
part ends in a function name, the original parentheses are replaced with
curly braces
'(3+4)*K{I6}'
In eval.2, now the '(' and ')*K{I6}' are "held" to work on "3+4"
'3+4'
The addition is performed and result "0 PT 7(0)" stored in I7
'I7'
The "held" parts are restored; this time the parentheses are not part of
a function so they are dropped.
'I7*K{I6}'
There are now no more parentheses, so no further "holding" is needed.
in eval.2, the 'K{I6}' is recognized and the ln function of I6 is computed,
with the result "0 PT 3.00278696328944(0)" stored into I8
'I7*I8'
eval.2 recognizes a multiplication and performs it, storing the
result "0 PT 21.0195087430261(0)" in I9
'I9'
The expression is now a single atom, which eval_2 returns to eval.1.
eval.1 removes the single-quotes and substitutes the value of I9
0P21.0195087430261
do.cmd3 assigns this result to the results array R4.
prnt1 (via various subroutines, usually pr.nt2 and prnt3) formats and prints
the result "21.019508743026"
OLD NOTES
There is a discrepancy vs. Wolfram Alpha on large factorials:
Wolfram|Alpha Hypercalc error
Cases where W|A gives N^N as the answer for N!
(10^100)! 3p2.008600171761918 3p2.0085921235101 -0.000008
(10^20)! 3p1.328400603937241 3p1.3282061673145 -0.00019
(10^10)! 3p1.041392685158225 3p1.0406306991294 -0.00076
Mystery (truncated Stirling series?):
(10^8)! 2p8.878849437722037 2p8.878849435655 -2.06e-9
(10^7)! 2p7.817281446362576 2p7.8172814258513 -2.05e-8
(10^6)! 2p6.745520692234177 2p6.7455204892842 -2.03e-7
Cases where W|A gives Gamma[N] as the answer fo N!
100000! 2p5.659505898232984 2p5.6595106542789 +0.0000048
30000! 2p5.083799800996968 2p5.0838158325409 +0.000016
10000! 2.8462596809e35659 2.846259682e35659 OK
1000! 4.02387260077e2567 4.02387260088e2567 OK
Oddly, Wolfram Alpha seems to be in error. For 20000!, it gives the
answer in two forms:
Input:
20000!
Decimal approximation:
1.8192063202303451348276... x 10^77337
Power of 10 representation:
10^10^4.888364627124846
We can check the conversion from the 1.819e77337 form to the
2p4.888... form by calculating log(77337+log(1.81920632023)).
Hypercalc and Mathematica agree that the answer is about
4.8883887806252, showing that the error is in Wolfram Alpha's
conversion to "Power of 10 representation".
{Later I computed the ratio of the two Wolfram answers, which is
20000, making it clear that the wrong answer is actually Gamma[20000].
Meeussen Wouter responded to my math-fun posting with the same
discovery.}
20130228: For (10^10)! and the larger cases, WolframAlpha is using
the approximation n! ~ n^n. Thus, their answer for (10^10)! is
10^10^11. The 10^20 and 10^100 cases are also n^n.
For some intermediate values of N including 10^6 and 10^8, the
error is much smaller; it is clearly not giving Gamma[N] or N^N,
and yet there is still an error.
Steps that I took to implement % (modulo) operator:
Get it to a point where variable values are set without being
normalised unless the value requires it (negative exponent of 10 i.e.
absolute value less than 1, or PT greater than 0).
Make splt() leave the value alone if it is a simple integer. As it
stands now, '17' gets turned into '1.70E1'. This change needed to
happen inside normalise() because that routine is responsible for
taking user input "1e5000" and promoting it to 1P5.0E3. It still needs
to do that or something equivalent (1P5000 would be okay) but it also
needs to leave '1000' alone and preferably turn '1e3' into '1000'.
Make sure that plain integers make it into operator functions like
p_mul or pt_mul.
Try to move handling of '%' so that it can be overloaded in a way
similar to how 'x' is overloaded. This means allowing '%' to be
recognised as a variable name. I made '%' both an argument and a
non-argument (as defined by p_arg and p_nonarg)
@;
$hd = $ENV{"HOME"};
$p_progname = "[a-z][-a-z_0-9]*[a-z0-9]";
$p_fname = "[A-Z_0-9]+";
$p_var = "[a-z][a-z_0-9]*";
$p_vr2 = "\%|[a-z][a-z_0-9]*"; # Only used for parsing e,g, "%-2"
$p_multvars = "[a-z_0-9, ]*";
$p_nonvar = "[^a-z_0-9]";
# Note that '%' is both an argument and a non-argument
$p_arg = "\%|[0-9.EINPRU]+";
$p_nonarg = "[^0-9.EINPRU]";
$p_na2 = "[^0-9._A-Z]";
$p_rawarg = "[-.0-9EP()]+"; # For parsing INPUT and DATA statements
$p_rawargs = "[-.0-9EP(), ]+";
# The special letters are: (addfunc: choose a new letter here)
#
# A
# B - cos()
# C - input history string (during input only)
# D
# E - for scientific notation
# F - defined functions, function parameters (future, reserved)
# G
# H
# I - intermediate result
# J
# K - ln()
# L
# M - logn()
# N - negative (a minus sign within a number)
# O
# P - PT (the power-tower part of a number)
# Q - sqrt()
# R - history result
# S - sin()
# T - tan()
# U - uncertainty (the error part of a number's mantissa)
# V - root()
# W - lambert() (future, reserved)
# X - exp()
# x - Synonym for * (multiplication)
# Z
# Print a prompt and wait for a Y or N answer.
sub yorn
{
my($prompt) = @_;
my($ans);
$ans = "";
while ($ans eq "") {
print $prompt;
$ans = <>; chomp $ans;
$ans =~ tr/A-Z/a-z/;
if ($ans =~ m/^y/) {
return 1;
} elsif ($ans =~ m/^n/) {
return 0;
}
print "Please answer Y or N.\n";
$ans = "";
}
}
sub seterase
{
# Note: These environment variables are nonstandard. I really need to find
# a standard way to test which stty erase needs to be set. It depends
# on your operating system and what type of terminal connection you're
# running in (xterm, telnet, ssh, etc.) Suggestions are welcome!
$erase_bs = $ENV{"ERASE_BS"};
$erase_del = $ENV{"ERASE_DEL"};
if ($erase_del) {
system("stty erase '^?'");
} elsif ($erase_bs) {
system("stty erase '^H'");
} else {
system("stty erase '^?'");
}
}
# This is from #mira#; it automatically detects and handles improper
# setting of the backspace or delete erase character
sub fixerase
{
my($l) = @_;
if ($l =~ m/\010/) {
# The user's input contains ^H characters.
# First, perform the intended deletion
while ($l =~ m/.\010/) {
$l =~ s/.\010//;
}
# Then fix it by setting ^H to be the erase character.
print "stty erase ^H\n";
system "stty erase '^H'";
} elsif ($l =~ m/\177/) {
# The user's input contains delete (^?) characters.
# First, perform the intended deletion
while ($l =~ m/.\177/) {
$l =~ s/.\177//;
}
# Then fix it by setting ^? to be the erase character.
print "stty erase ^?\n";
system "stty erase '^?'";
}
$l =~ s/\011/ /g;
# Convert any remaining illegal characters to something printable
$l =~ s/([^\010 -~])/sprintf("\\0%o",ord($1))/ge;
return $l;
}
sub dbg1
{
my($a, $mask) = @_;
# $a =~ s/\'//g;
$a .= "\n";
if ($dbg1flag & $mask) {
print $a;
}
print $g_logfile $a;
}
sub isnan { my($a) = @_; return($a =~ m/nan/i); }
sub isinf { my($a) = @_; return($a =~ m/inf/i); }
sub isninf { my($a) = @_; return($a =~ m/[-n]inf/i); }
sub ispinf { my($a) = @_; return((&isinf($a) && (!(&isninf($a))))); }
sub finite { my($a) = @_; return(!($a =~ m/inf/i)); }
# Return true iff the argument is a positive integer with $sc ("scale")
# digits or fewer.
sub simple_int
{
my($x, $sc) = @_;
$x =~ s/\.0*$//;
if (($x =~ m/^[0-9]+$/) && (length($x) <= $sc)) {
return length($x);
}
return 0;
}
# Return true iff the argument is an integer (negative or positive)
# with $sc ("scale") digits or fewer.
sub simple_nint
{
my($x, $sc) = @_;
$x =~ s/\.0*$//;
$x =~ s/^[-]//;
if (($x =~ m/^[0-9]+$/) && (length($x) <= $sc)) {
return length($x);
}
return 0;
}
#--------------------------------------------------- F64 primitives
#
# These routines, originally from "hcfloat.c", perform primitive
# functions on floating-point values. In most cases the function
# is built-in to perl, but we do lots of special checks for
# things like infinity and arguments that generate imaginary or
# complex answers.
#
# perl provides: abs, atan2, cos, exp, int, log, sin, sqrt
$g_f64_nan = nan;
$g_f64_inf = inf;
$g_f64_ninf = -inf;
# Two f64 primitives are explicit so they can be used efficiently
# for e.g. the uncertainty calculations
# This divide routine catches the divide by zero case.
sub f64_div
{
my($n, $d) = @_;
if (&isnan($n) || &isnan($d)) {
&dbg1("f64.div NAN/NAN : NAN]]", 32);
return $g_f64_nan;
} elsif (&f_eq($d, $g_0)) {
&dbg1("f64.div K/0 : NAN", 32);
return $g_f64_nan;
}
return ($n / $d);
}
# Our ln routine is strange -- it accepts negative arguments and returns
# the magnitude of the answer. It also special-cases infinities and zero.
sub f64_ln
{
my($x) = @_;
my($v);
&dbg1("f64_ln $x", 1);
# Handle 0 and infinities first
if ($x == 0.0) {
&dbg1("f64_ln(0) : ninf", 32);
return $g_f64_ninf;
} elsif (&isinf($x)) {
&dbg1("f64_ln(inf) : inf", 32);
return $g_f64_inf;
} elsif (&isnan($x)) {
&dbg1("f64_ln(NAN) : NAN", 32);
return $g_f64_nan;
}
# If the argument is negative we make it positive, because
# we want to return the real component of the logarithm of the number,
# and for any complex number, the real component of the log is the log
# of the number's magnitude.
if ($x < 0.0) {
$x = - $x;
}
$v = log($x);
return $v;
}
$f64_prim = <<'endquote';
# These are the full primitives
sub f_int # f64 version
{
my($a) = @_;
return int($a);
}
sub f_lt # f64 version
{
my($a, $b) = @_;
return ($a < $b);
}
sub f_le # f64 version
{
my($a, $b) = @_;
return ($a <= $b);
}
sub f_eq # f64 version
{
my($a, $b) = @_;
return ($a == $b);
}
sub f_ge # f64 version
{
my($a, $b) = @_;
return ($a >= $b);
}
sub f_gt # f64 version
{
my($a, $b) = @_;
return ($a > $b);
}
sub f_ne # f64 version
{
my($a, $b) = @_;
return ($a != $b);
}
sub f_neg # f64 version
{
my($a) = @_;
return (- $a);
}
sub f_add # f64 version
{
my($a, $b) = @_;
return ($a + $b);
}
sub f_mul # f64 version
{
my($a, $b) = @_;
return ($a * $b);
}
# This divide routine catches the divide by zero case.
sub f_div # f64 version
{
my($n, $d) = @_;
if (&isnan($n) || &isnan($d)) {
&dbg1("f_div NAN/NAN : NAN", 32);
return $g_nan;
}
if (&finite($n)) {
# finite numerator
if (&f_eq($d, $g_0)) {
# Anything/0 = NAN
&dbg1("f_div K/0 : NAN", 32);
return $g_nan;
} elsif (&isinf($d)) {
# Anything/infinity = 0
&dbg1("f_div K/inf : 0", 32);
return $g_0;
}
} else {
# infinite numerator
if (&finite($d)) {
if (&f_lt($n, 0)) {
&dbg1("f_div inf/-K : ninf", 32);
return &f_neg($n);
}
&dbg1("f_div inf/K : inf", 32);
return $n;
} else {
# inf/inf
&dbg1("f_div inf/inf : NAN", 32);
return $g_nan;
}
}
return ($n / $d);
}
# following are the partial primitives.
sub f_sub # f64 version
{
my($a, $b) = @_;
&dbg1("f.sub $a - $b", 32);
return ($a - $b);
# return &f_add($a, &f_neg($b));
}
# Following are the non-primitives
# Our exp routine tests the infinity and overflow cases itself, just in
# case the library function doesn't.
sub f_exp # f64 version
{
my($x) = @_;
my($v);
&dbg1("$curprim f.exp $x", 1);
# Handle infinities first
if (&isnan($x)) {
&dbg1("f.exp(nan) : nan", 32);
return $g_nan;
} elsif (&ispinf($x)) {
&dbg1("f.exp(inf) : inf", 32);
return $g_inf;
} elsif (&isninf($x)) {
&dbg1("f.exp(ninf) : 0", 32);
return $g_0;
}
# Handle the extreme overflow cases.
if (&f_lt($x, $g_n2000)) {
&dbg1("f.exp(< -2000) : return $g_0", 32);
return $g_0;
} elsif (&f_gt($x, $g_2000)) {
&dbg1("f.exp(> 2000) : return $g_inf", 32);
return $g_inf;
}
$v = exp($x);
return $v;
} # End of f.exp
# Our ln routine is strange -- it accepts negative arguments and returns
# the magnitude of the answer. It also special-cases infinities and zero.
sub f_ln # f64 version
{
my($x) = @_;
my($v);
&dbg1("$curprim f.ln $x", 1);
# Handle 0 and infinities first
if (&isnan($x)) {
&dbg1("f.ln(nan) : nan", 32);
return $g_nan;
} elsif (&f_eq($x, $g_0)) {
&dbg1("f.ln(0) : ninf", 32);
return $g_ninf;
} elsif (&isinf($x)) {
&dbg1("f.ln(inf) : inf", 32);
return $g_inf;
}
# If the argument is negative we make it positive, because
# we want to return the real component of the logarithm of the number,
# and for any complex number, the real component of the log is the log
# of the number's magnitude.
if (&f_le($x, $g_0)) {
$x = &f_neg($x);
}
$v = log($x);
return $v;
}
sub f_pow10 # f64 version
{
my($n) = @_;
&dbg1("$curprim f.pow10 $n", 1);
return &f_exp($g_ln_10 * $n);
}
sub f_log_n # f64 version
{
my($n, $b) = @_;
&dbg1("$curprim f.log_n v{ $b } $n", 1);
return &f_div(&f_ln($n), &f_ln($b));
}
sub f_log10 # f64 version
{
my($n) = @_;
&dbg1("$curprim f_log10 $n", 1);
return (&f_ln($n) * $g_log10_e);
}
# sqrt(2 pi n) * n^n * e^(-n) * e^(1/12n)
# 1/2 ln(2 pi n) + n ln(n) - n + 1/12n - 1/1260 n^3 + ...
# t = n - 1;
# l = 1/2 ln(2 pi) + (n + 1/2) ln(n) - n + 1/(12 n) - 1/(360 n^3) + ...
# g = e^l
#
# %%% Accuracy is not good enough for scale>20. See notes in header.
sub f_gamma # f64 version
{
my($n) = @_;
my($l, $scal1, $n2, $np);
&dbg1("$curprim f_gamma $n", 1);
if (&isnan($n)) {
&dbg1("f_gamma(nan) : nan", 32);
return $g_nan;
}
# For very low negative arguments, the gamma function is so
# close to zero that we treat it as zero. However, for negative
# integers it's infinite, so we handle that case explicitly.
if (&f_lt($n, $g_n50)) {
if (&f_eq($n, &f_int($n))) {
&dbg1("f_gamma(-N) : inf", 32);
return $g_inf;
}
return $g_0;
}
# Our approximation formula doesn't work well for small values, so
# we use the recurrence relation gamma(n+1) = n gamma(n).
$scal1 = $g_1;
while ($n < $g_10) { # "10" depends on precision
$scal1 = $scal1 * $n;
$n = &f_add($n, $g_1);
}
# Since we're using Stirling's series for factorials, we have
# to subtract 1 to make it be a gamma function series.
$n = &f_sub($n, $g_1);
$l = $g_05_ln_2pi;
$l = &f_add($l, &f_add($n, $g_0_5) * &f_ln($n));
$l = &f_sub($l, $n);
$n2 = $n * $n;
$np = $n;
$l = &f_add($l, &f_div($g_1, $g_12 * $np));
$np = $np * $n2;
$l = &f_sub($l, &f_div($g_1, $g_360 * $np));
$np = $np * $n2;
$l = &f_add($l, &f_div($g_1, $g_1260 * $np));
$np = $np * $n2;
$l = &f_sub($l, &f_div($g_1, $g_1680 * $np));
$np = $np * $n2;
$l = &f_add($l, &f_div($g_1, $g_1188 * $np));
$np = $np * $n2;
$l = &f_sub($l, &f_div($g_691, $g_360360 * $np));
$np = $np * $n2;
$l = &f_add($l, &f_div($g_7, $g_1092 * $np));
$np = $np * $n2;
$l = &f_sub($l, &f_div($g_3617, $g_122400 * $np));
return (&f_div(&f_exp($l), $scal1));
}
# square root routine handles infinities and negative arguments properly
sub f_sqrt # f64 version
{
my($x) = @_;
&dbg1("$curprim f_sqrt $x", 1);
if (&isnan($x)) {
&dbg1("f_sqrt(nan) : nan", 32);
return $g_nan;
}
# If the argument is negative we return 0, because 0 is
# the real component of the square root of a negative number.
if (&f_lt($x, $g_0)) {
&dbg1("f_sqrt(-X) : 0", 32);
return $g_0;
} elsif (&isinf($x)) {
&dbg1("f_sqrt(inf) : inf", 32);
return $g_inf;
}
return sqrt($x);
}
# The sine algorithm uses the identity
# sin(a+b) = sin a cos b + cos a sin b
# The value being sin'd is broken into two pieces one of which is a multiple
# of pi/16 and the other is between -pi/32 and pi/32. Then we can use the
# Taylor series to get the sin of the latter part and a table for the
# first part
sub f_sin # f64 version
{
my($x) = @_;
&dbg1("$curprim f_sin $x", 1);
if (&isnan($x)) {
&dbg1("f_sin(nan) : nan", 32);
return $g_nan;
}
# Handle arguments out of range
# Note: The best way to handle things like this is to extend the
# numeric representation to include an "error" term, expressing the
# standard deviation of the computed value vs. the actual value.
# Then, the error of the sin function is the error of the argument
# times the cosine, but converging on +-1.0 when the error of the
# argument is 2 pi or larger. In the limit case the computed value
# will be expressed as 0.0 and the error as 1.0.
#
# Just for fun, I went to an office supply store and tested 5
# current-model scientific calculators (covering all price ranges)
# to see what they did when you take sin or cos of larger and larger
# numbers. Each calculator had a different cutoff value, but all
# seemed to choose a value that was related to the calculator's
# internal precision. The five cutoff values were:
# 5*10^7 pi
# 4.45*10^9
# 1*10^10
# 1*10^12
# 1*10^13
if (&f_gt($x, $g_sin_limit)) {
return $g_0;
} elsif (&f_lt($x, &f_neg($g_sin_limit))) {
return $g_0;
}
return sin($x);
}
sub f_cos # f64 version
{
my($x) = @_;
&dbg1("$curprim f_cos $x", 1);
if (&isnan($x)) {
&dbg1("f_cos(nan) : nan", 32);
return $g_nan;
}
# Handle out-of-range
if (&f_gt($x, $g_sin_limit)) {
return $g_1;
} elsif (&f_lt($x, &f_neg($g_sin_limit))) {
return $g_1;
}
return cos($x);
}
sub f_tan # f64 version
{
my($x) = @_;
&dbg1("$curprim f_tan $x", 1);
if (&isnan($x)) {
&dbg1("f_tan(nan) : nan", 32);
return $g_nan;
}
# Handle out-of-range
if (&f_gt($x, $g_sin_limit)) {
return $g_inf;
} elsif (&f_lt($x, &f_neg($g_sin_limit))) {
return $g_inf;
}
return &f_div(sin($x),cos($x));
}
# Arctangent function.
#
# Av{0} = 1 / sqrt(1 + x^{2}) , Bv{0} = 1
# Av{n+1} = (Av{n} + Bv{n}) / 2 ,
# Bv{n+1} = sqrt(Av{n+1} * Bv{n})
# arctan(x) = LIMIT [x * Av{0} / Av(n)]
sub f_arctan # f64 version
{
my($x) = @_;
my($a, $b, $a0, $ao, $i);
&dbg1("$curprim f_arctan $x", 1);
if (&isnan($x)) {
&dbg1("f_arctan(nan) : nan", 32);
return $g_nan;
}
# A few special cases, the x==0 case is just for speed
if (&f_eq($x, $g_0)) {
return $g_0;
} elsif (&ispinf($x)) {
&dbg1("f_arctan(inf) : pi/2", 32);
return &f_div($g_pi, $g_2);
} elsif (&isninf($x)) {
&dbg1("f_arctan(-inf) : -pi/2", 32);
return &f_div($g_pi, $g_n2);
}
$a0 = &f_div($g_1, &f_sqrt(&f_add($g_1, $x * $x)));
$a = $a0;
$b = $g_1;
# Set up ao to be different from a so the loop has a chance to start
$ao = &f_sub($a, $g_1);
for($i=0; ($i<25) && (&f_ne($a, $ao)); $i++) {
$ao = $a;
$a = &f_div(&f_add($a, $b), $g_2);
$b = &f_sqrt($a * $b);
}
return &f_div($x * $a0, $a);
}
# arcsin(x) = arctan(x / sqrt(1 - x*x))
sub f_arcsin # f64 version
{
my($x) = @_;
my($a);
&dbg1("$curprim f_arcsin $x", 1);
if (&isnan($x)) {
&dbg1("f_arcsin(nan) : nan", 32);
return $g_nan;
}
$a = $x * $x;
if (&f_gt($a, $g_1)) {
return $g_0;
}
return &f_arctan(&f_div($x, &f_sqrt(&f_sub(1, $a))));
}
# arccos(x) = pi/2 - arctan(x / sqrt(1 - x*x))
sub f_arccos # f64 version
{
my($x) = @_;
my($a);
&dbg1("$curprim f_arccos $x", 1);
if (&isnan($x)) {
&dbg1("f_arccos(nan) : nan", 32);
return $g_nan;
}
$a = $x * $x;
if (&f_gt($a, $g_1)) {
return $g_0;
}
return &f_div($g_pi, $g_2)
- &f_arctan(&f_div($x, &f_sqrt(&f_sub($g_1, $a))));
}
# pow function is more complex than exp and pow10, because
# it has to deal with any base. Specifically, if the base is negative
# it computes the angle and magnitude of the complex answer
# so it can calculate the real component.
sub f_pow # f64 version
{
my($b, $e) = @_;
my($mag, $scal2);
&dbg1("$curprim f_pow $b $e", 1);
if (&isnan($b) || &isnan($e)) {
&dbg1("f_pow(nan) : nan", 32);
return $g_nan;
}
# Handle negative bases
if (&f_lt($b, $g_0)) {
# The angle of the base is pi, so the angle of the
# exponent will be pi * e. Take cosine of this to
# determine how much of the answer will be in the
# real component.
$b = &f_neg($b);
$scal2 = &f_cos($g_pi * $e);
} else {
$scal2 = $g_1;
}
# Compute magnitude of answer
&dbg1("f_pow : mag = exp(mul(ln($b), $e)", 32);
$mag = &f_exp(&f_ln($b) * $e);
# If we got an overflow, we might be able to salvage
# the computation by using logarithms. This happens when we compute
# -20.0 to the power of 238.49999999999 (exactly 10 9's there)
# The magnitude is 1.97e310, which overflows, but the scale
# is 3.1416e-10 so the answer is actually well within
# range (and even within PT0 range)
&dbg1("f_pow test $mag > $g_ovr1", 32);
if (&ispinf($mag) || &f_gt($mag, $g_ovr1)) {
&dbg1(" : true", 32);
if (&f_gt($scal2, $g_0)) {
$mag = &f_add(&f_ln($b) * $e, &f_ln($scal2));
&dbg1("f_pow : return exp($mag)", 32);
return &f_exp($mag);
} else {
$mag = &f_add(&f_ln($b) * $e, &f_ln(&f_neg($scal2)));
&dbg1("f_pow : return neg(exp($mag))", 32);
return &f_neg(&f_exp($mag));
}
} else {
&dbg1(" : false", 32);
# Return real component of answer
&dbg1("f_pow : return mul($scal2, $mag)", 32);
return ($scal2 * $mag);
}
} # End of f_pow
# Root function contains the same complexity (pun intended) as
# the pow function.
sub f_root # f64 version
{
my($b, $r) = @_;
my($mag, $scal3);
&dbg1("$curprim f_root $r v/ $b", 1);
if (&isnan($b) || &isnan($r)) {
&dbg1("f_root(nan) : nan", 32);
return $g_nan;
}
# Handle negative bases
if (&f_lt($b, $g_0)) {
# The angle of the base is pi, so the angle of the
# answer will be pi / r. Take cosine of this to
# determine how much of the answer will be in the
# real component.
$b = &f_neg($b);
$scal3 = &f_cos(&f_div($g_pi, $r));
} else {
$scal3 = $g_1;
}
$mag = &f_exp(&f_div(&f_ln($b), $r));
return ($scal3 * $mag);
}
endquote
sub f64_init
{
eval($f64_prim);
my($t);
# These constants are mostly used by the various transcendental functions
$g_0 = 0.0;
$g_0_5 = 0.5;
$g_1 = 1.0;
$g_n1 = -1.0;
$g_2 = 2.0;
$g_n2 = -2.0;
$g_4 = 4.0;
$g_5 = 5.0;
$g_6 = 6.0;
$g_7 = 7.0;
$g_8 = 8.0;
$g_10 = 10.0;
$g_12 = 12.0;
$g_16 = 16.0;
$g_n50 = -50.0;
$g_360 = 360.0;
$g_691 = 691.0;
$g_1092 = 1092.0;
$g_1188 = 1188.0;
$g_1260 = 1260.0;
$g_1680 = 1680.0;
$g_n2000 = -2000.0;
$g_2000 = 2000.0;
$g_3617 = 3617.0;
$g_122400 = 122400.0;
$g_360360 = 360360.0;
$g_2p32m1 = 4294967295.0;
$g_pi = 3.1415926535897932;
# Initialize infinities using Perl's built-in multiply (which should
# work provided it is using IEEE 64-bit double precision).
$t = 1.0e300;
$t=$t*$t; $t=$t*$t; $t=$t*$t; $t=$t*$t; $t=$t*$t; $t=$t*$t; $t=$t*$t;
$g_inf = $t;
$g_ninf = -$t;
$g_nan = $g_inf+$g_ninf;
}
sub f64_close
{
# undef(&f_int);
# undef(&f_lt);
}
# --------------------------------------------------- FBC primitives
# bce is the "engine" that feeds problems to the running #bc# process
# and gets results.
sub bce
{
my($expr, $expect_response) = @_;
my($result, $l);
print $bc_out ($expr . "\n");
$l = ($expect_response ? "\\" : "");
while ($l =~ m|\\$|) {
$l = <$bc_in>; chomp $l;
$result .= $l;
$result =~ s|\\$||;
}
&dbg1("bce : $expr => $result", 4);
return $result;
}
# fbc.norm normalises a number in scientific format. For example, it converts
# "0.02e3" to "2.0e1", and "23e45" to "2.3e46".
# It does not use &bce, so it can be used by non-bc routines as well.
sub fbc_norm
{
my($x) = @_;
my($xs, $xm, $xe);
my($s, $xi, $xf);
my($mi, $mf, $e);
&dbg1("fbc_norm $x", 1);
if (&isinf($x)) {
&dbg1("fbc_norm inf: $x", 33);
return $x;
} elsif (&isnan($x)) {
&dbg1("fbc_norm nan: $x", 33);
return $x;
}
# make sure there is a sign
if (!($x =~ m/^[-+]/)) {
$x = "+" . $x;
}
# extract the three parts of the number
if ($x =~ m|^([+-])([0-9.]+)E([-+0-9]+)$|) {
$xs = $1; $xm = $2; $xe = $3;
} elsif ($x =~ m|^([+-])([0-9.]+)$|) {
$xs = $1; $xm = $2; $xe = 0;
} else {
&dbg1("fbc_norm illegal input |$x|", 1);
}
# Make sure there is a decimal point
if (!($xm =~ m|\.|)) {
$xm .= ".0";
}
# make sure it doesn't start with a bare decimal point
if ($xm =~ m/^\./) {
$xm = "0" . $xm;
}
# extract integer and fraction portions
if ($xm =~ m|^([0-9]+)\.([0-9]+)$|) {
$xi = $1; $xf = $2;
&dbg1("fbc_norm xi $xi xf $xf", 1);
} else {
print "fbc_norm illegal mantissa |$x|\n";
}
# handle cases where mantissa was less than 1 (normalise by decreasing
# exponent)
if ($xi == 0) {
$x = $xi . $xf;
if ($x =~ m|^(0+[1-9])([0-9]*)$|) {
$mi = $1; $mf = $2;
$e = 1 - length($mi);
$mi =~ s|^0+||;
$x = $xs . $mi . "." . $mf . "E" . ($e + $xe);
&dbg1("fbc_norm a: $x", 1);
return $x;
}
# else, it was zero
$x = "0.0E0";
&dbg1("fbc_norm b: $x", 1);
return $x;
}
# else
$e = length($xi) - 1;
$x = $xi . $xf;
$x =~ s|^([0-9])|$1\.|;
$e = $e + $xe;
# check for overflow
if ($e > 300) {
$x = $xs . "inf";
&dbg1("fbc_norm e: $x", 1);
return $x;
}
$x = $xs . $x . "E" . $e;
&dbg1("fbc_norm c: $x", 1);
return $x;
} # End of fbc.norm
# fix2sci converts from fixed-point to scientific notation. It's very simple
# because all the hard work is done by fbc_norm
# It does not use &bce, so it can be used by non-bc routines as well.
sub fbc_fix2sci
{
my($x) = @_;
&dbg1("fbc_fix2sci in: $x", 1);
$x =~ s/e/E/g;
$x =~ s/ //g;
if (&isinf($x)) {
&dbg1("fbc_fix2sci inf: $x", 33);
return $x;
} elsif (&isnan($x)) {
&dbg1("fbc_fix2sci nan: $x", 33);
return $x;
}
if (!($x =~ m/E/)) {
$x = $x . "E+0";
}
$x = &fbc_norm($x);
&dbg1("fbc_fix2sci b: $x", 1);
return $x;
} # End of fbc.fix2sci
# fbc.split takes a number in any format and returns the sign, mantissa and
# exponent that it would have if expressed in normalised scientific notation.
# It is used by most of the nontrivial #bc# primitives.
# It does not use &bce, so it can be used by non-bc routines as well.
sub fbc_split
{
my($x) = @_;
my($s, $m, $e);
&dbg1("fbc_split in: '$x'", 1);
$x =~ s/ //g;
# map infinity %%% might want to just return "inf" in the exponent field
# and make callers check for infinity
if (&isninf($x)) {
&dbg1("fbc_split ninf: ( - , 1 , $x )", 1);
return("-", 1, $x);
} elsif (&isinf($x)) {
&dbg1("fbc_split +inf: ( + , 1 , $x )", 1);
return("+", 1, $x);
} elsif (&isnan($x)) {
&dbg1("fbc_split nan: ( + , 1 , $x )", 1);
return("+", 1, $x);
}
# Convert to scientific if appropriate
if (!($x =~ m/E/)) {
$x = &fbc_fix2sci($x);
}
# Make sure it has a sign
if (!($x =~ m|^[+-]|)) {
$x = "+" . $x;
}
# get sign, mantissa and exponent
if ($x =~ m|^([+-])([0-9.]+)E([-+0-9]+)$|) {
$s = $1; $m = $2; $e = $3;
} else {
&dbg1("fbc.split illegal input |$x|", 1);
}
&dbg1("fbc.split -> ( $s , $m , $e )", 1);
return($s, $m, $e);
} # End of fbc.split
# sci2fix converts from 1.23e10 format to something like
# 12300000000.0
#
# Despite the name, it takes input with or without an 'e' and exponent.
# It doubles as an int function, pass nonzero in the 2nd parameter.
sub fbc_sci2fix
{
my($x, $intonly) = @_;
my($s, $m, $mi, $mf, $e);
my($ri, $rf, $rv);
&dbg1("fbc.sci2fix $x $intonly", 1);
# get sign, mantissa and exponent
if ($x =~ m|^([+-]?)([.0-9]+)[eE]([+-][0-9]+)$|) {
$s = $1; $m = $2; $e = $3;
} elsif ($x =~ m|^([+-]?)([.0-9]+)[eE]([0-9]+)$|) {
$s = $1; $m = $2; $e = $3;
} elsif ($x =~ m|^([+-]?)([.0-9]+)$|) {
$s = $1; $m = $2; $e = 0;
} elsif ($x =~ m|^([.0-9]+)$|) {
$s = "+"; $m = $1; $e = 0;
} else {
print "fbc.sci2fix illegal input |$x|\n";
}
if ($s eq '') {
$s = '+';
}
# Get integer and fractional parts
if ($m =~ m|^([0-9]+)\.([0-9]*)$|) {
$mi = $1; $mf = $2;
} else {
$mi = $m; $mf = "";
}
# check sign of exponent
if ($e > 0) {
# positive exponent - append some digits
if (length($mf) < $e) {
$ri = $mi . $mf . ("0" x ($e - length($mf)));
$rf = "";
} elsif (length($mf) > $e) {
$ri = $mi . substr($mf, 0, $e);
$rf = substr($mf, $e);
} else {
$ri = $mi . $mf;
$rf = "";
}
} elsif ($e < 0) {
# negative exponent - take digits from mantissa integer and transfer over
# into fraction
$e = 0 - $e;
if (length($mi) < $e) {
$ri = "0";
$rf = ("0" x ($e - length($mi))) . $mi . $mf;
} elsif (length($mi) > $e) {
$ri = substr($mi, 0 - $e);
$rf = substr($mi, 0, 0 - $e) . $mf;
} else {
$ri = "0";
$rf = $mi . $mf;
}
} else {
# zero exponent - the mantissa is the result
$ri = $mi; $rf = $mf;
}
if ($intonly) {
$rv = $s . $ri;
} else {
$rv = $s . $ri . "." . $rf;
}
&dbg1("fbc.sci2fix: $rv", 1);
return $rv;
} # End of fbc.sci2fix
# fbc_prim defines the primitives for BC-based floating point.
$fbc_prim = <<'endquote';
# These are the full primitives
sub f_int # fbc version
{
my($a) = @_;
return &fbc_fix2sci(&fbc_sci2fix($a, 1));
}
# This function does what the <=> operator does for normal Perl numbers.
# All the comparative routines (f_lt, f_ge, f_ne etc) call this and compare
# the result to 0.
sub f_cmp # fbc version
{
my($a, $b) = @_;
my($as, $am, $ae, $bs, $bm, $be);
&dbg1("$curprim f.cmp $a <=> $b", 1);
($as, $am, $ae) = &fbc_split($a);
($bs, $bm, $be) = &fbc_split($b);
$as .= "1"; $bs .= "1";
# handle the easy cases
if ($as < $bs) {
&dbg1("$curprim f.cmp a: -1", 1);
return -1;
} elsif ($as > $bs) {
&dbg1("$curprim f.cmp b: 1", 1);
return 1;
}
# zero has to be handled as a special case
if (($am == 0) && ($bm == 0)) {
&dbg1("$curprim f.cmp h: 0", 1);
return 0;
} elsif ($am == 0) {
&dbg1("$curprim f.cmp f: " . (-1 * $bs), 1);
return (-1 * $bs);
} elsif ($bm == 0) {
&dbg1("$curprim f.cmp g: $as", 1);
return ($as);
}
# They're the same sign. From here on we treat them both as positive
# but multiply our return value by the sign
# Infinity is the simplest case at this point
if (&isinf($ae) && &isinf($be)) {
&dbg1("$curprim f.cmp i: 0", 1);
return 0;
} elsif (&isinf($ae)) {
&dbg1("$curprim f.cmp j: $as", 1);
return ($as);
} elsif (&isinf($be)) {
&dbg1("$curprim f.cmp k: " . (-1 * $as), 1);
return (-1 * $as);
}
# Exponents are another easy compare
if ($ae < $be) {
&dbg1("$curprim f.cmp c: " . (-1 * $as), 1);
return (-1 * $as);
} elsif ($ae > $be) {
&dbg1("$curprim f.cmp d: " . $as, 1);
return $as;
}
# Finally we have to compare the mantissas. We use a string order compare
# for this.
# It won't catch equals if trailing zeros differ
$am =~ s/0+$//;
$bm =~ s/0+$//;
$as = $as * ($am cmp $bm);
&dbg1("$curprim f.cmp e: " . $as, 1);
return $as;
}
sub f_lt # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f.lt $a < $b", 1);
return (&f_cmp($a, $b) < 0);
}
sub f_le # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f_le $a <= $b", 1);
return (&f_cmp($a, $b) <= 0);
}
sub f_eq # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f_eq $a == $b", 1);
return (&f_cmp($a, $b) == 0);
}
sub f_ge # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f_ge $a >= $b", 1);
return (&f_cmp($a, $b) >= 0);
}
sub f_gt # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f_gt $a > $b", 1);
return (&f_cmp($a, $b) > 0);
}
sub f_ne # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f_ne $a != $b", 1);
return (&f_cmp($a, $b) != 0);
}
# Negate is about as easy as they get. The sign is just a character at the
# beginning of the string, so we can perform the operation without asking #bc#.
sub f_neg # fbc version
{
my($a) = @_;
&dbg1("$curprim f_neg $a", 1);
if (&isninf($a)) {
&dbg1("$curprim f_neg(ninf) : inf", 32);
$a = $g_inf;
} elsif (&isinf($a)) {
&dbg1("$curprim f_neg(inf) : ninf", 32);
$a = $g_ninf;
} elsif (&isnan($a)) {
&dbg1("$curprim f_neg(nan) : nan", 32);
$a = $g_nan;
} elsif ($a =~ m/^-/) {
$a =~ s/^-//;
} else {
$a = "-" . $a;
$a =~ s/-\+/-/;
}
&dbg1("$curprim f_neg a: $a", 1);
return ($a);
} # End of f.neg
# me_magcompare
sub me_magcompare # fbc version
{
my($am, $ae, $bm, $be) = @_;
if ($ae > $be) {
return 1;
} elsif ($ae < $be) {
return -1;
}
return ($am cmp $bm);
}
# m_truncround is currently just a placeholder in case I want to explicitly
# do roundoff at all stages in my computations. For now, I just rely on the
# fact that bc rounds for me.
sub m_truncround # fbc version
{
my($x) = @_;
return $x;
}
# me_addpos adds two positive floating-point numbers, already split into
# mantissa and exponent.
sub me_addpos # fbc version
{
my($am, $ae, $bm, $be) = @_;
my($ed);
# put the bigger one first
if (&me_magcompare($am, $ae, $bm, $be) < 0) {
($am, $ae, $bm, $be) = ($bm, $be, $am, $ae);
}
# eliminate any difference in exponents
$ed = $ae - $be;
if ($ed > 0) {
$bm =~ s/\.//;
$bm = "0." . ("0" x ($ed - 1)) . $bm;
$be = $ae;
}
&dbg1("me_addpos using $be", 1);
# time to add
$am = &bce("$am + $bm", 1);
# truncate and round
$am = &m_truncround($am);
# append exponent and normalise
$am = $am . "E" . $ae;
$am = &fbc_norm($am);
return $am;
}
# me_subpos takes two positive floating-point numbers, already split into
# mantissa and exponent. The first one is known to be larger. It subtracts
# the second from the first and returns the answer.
sub me_subpos # fbc version
{
my($am, $ae, $bm, $be) = @_;
my($ed, $zp, $rest);
# eliminate any difference in exponents
$ed = $ae - $be;
if ($ed > 0) {
$bm =~ s/\.//;
$bm = "0." . ("0" x ($ed - 1)) . $bm;
$be = $ae;
}
# time to subtract
$am = &bce("$am - $bm", 1);
# truncate and round
$am = &m_truncround($am);
# append exponent and normalise
$am = $am . "E" . $ae;
$am = &fbc_norm($am);
return $am;
}
sub f_add # fbc version
{
my($a, $b) = @_;
my($as, $am, $ae, $bs, $bm, $be);
my($mc);
&dbg1("$curprim f_add $a + $b", 1);
if (&simple_nint($a, $curscale-1) && &simple_nint($b, $curscale-1)) {
&dbg1("$curprim simple.nint true", 1);
return &bce("$a + $b", 1);
}
($as, $am, $ae) = &fbc_split($a);
($bs, $bm, $be) = &fbc_split($b);
$as .= "1"; $bs .= "1";
if (&isnan($ae) || &isnan($be)) {
&dbg1("$curprim f_add(nan+nan) : nan", 32);
return $g_nan;
}
# if signs are equal, call addpos. Else use subpos.
if ($as == $bs) {
if ($as < 0) {
return &f_neg(&me_addpos($am, $ae, $bm, $be));
} else {
return &me_addpos($am, $ae, $bm, $be);
}
}
# Signs are opposite, so it's a - b or b - a. We have to figure out
# which is bigger so we can put it first in the call to me_subpos
$mc = &me_magcompare($am, $ae, $bm, $be);
if ($mc < 0) {
# a smaller than b
if ($bs > 0) {
# big positive, small negative; answer is positive
return &me_subpos($bm, $be, $am, $ae);
} else {
# big one is negative, answer will be negative
return &f_neg(&me_subpos($bm, $be, $am, $ae));
}
} else {
# b smaller than a
if ($as > 0) {
# big positive, small negative; answer is positive
return &me_subpos($am, $ae, $bm, $be);
} else {
# big one is negative, answer will be negative
return &f_neg(&me_subpos($am, $ae, $bm, $be));
}
}
}
# fbc version of f.mul.
sub f_mul # fbc version
{
my($a, $b) = @_;
my($as, $am, $ae, $bs, $bm, $be);
&dbg1("$curprim f.mul $a * $b", 1);
if (($as=&simple_nint($a, $curscale))
&& ($bs=&simple_nint($b, $curscale))
&& ($as+$bs <= $curscale)) {
return &bce("$a * $b", 1);
}
($as, $am, $ae) = &fbc_split($a);
($bs, $bm, $be) = &fbc_split($b);
$as .= "1"; $bs .= "1";
if (&isnan($ae) || &isnan($be)) {
&dbg1("$curprim f.mul(nan) : nan", 32);
return $g_nan;
}
if (($am == 0) || ($bm == 0)) {
&dbg1("$curprim f.mul a: $g_0", 1);
return $g_0;
}
# handle infinities
if (($ae > 300) || ($be > 300)) {
if ($as * $bs > 0) {
&dbg1("$curprim f.mul b: inf", 1);
return "inf";
}
&dbg1("$curprim f.mul c: Ninf", 1);
return "Ninf";
}
$am = &bce("$am * $bm", 1);
$ae = $ae + $be;
if ($as * $bs > 0) {
$as = "+";
} else {
$as = "-";
}
$a = $as . $am . "E" . $ae;
$a = &fbc_norm($a);
&dbg1("$curprim f.mul d: " . $a, 1);
return $a;
} # End of f.mul
# This divide routine catches the divide by zero case.
sub f_div # fbc version
{
my($a, $b) = @_;
my($as, $am, $ae, $bs, $bm, $be);
&dbg1("$curprim f_div $a / $b", 1);
if ( ($as=&simple_nint($a, $curscale))
&& ($bs=&simple_nint($b, $curscale))
&& ($as>=$bs) ) {
return &bce("$a / $b", 1);
}
if (&isnan($a) || &isnan($b)) {
&dbg1("$curprim f_div(nan) : nan", 32);
return $g_nan;
}
if (&finite($a)) {
# finite numerator
if (&f_eq($b, $g_0)) {
# Anything/0 = NAN
&dbg1("$curprim f_div(X/0) : nan", 32);
return $g_nan;
} elsif (&isinf($b)) {
# Anything/infinity = 0
&dbg1("$curprim f_div(X/inf) : 0", 32);
return $g_0;
}
} else {
# infinite numerator
if (&finite($b)) {
if (&f_lt($b, $g_0)) {
&dbg1("$curprim f_div(inf/-X) : ninf", 32);
return &f_neg($a);
}
&dbg1("$curprim f_div(inf/X) : inf", 32);
return $a;
} else {
# inf/inf
&dbg1("$curprim f_div(inf/inf) : nan", 32);
return $g_nan;
}
}
($as, $am, $ae) = &fbc_split($a);
($bs, $bm, $be) = &fbc_split($b);
$as .= "1"; $bs .= "1";
$am = &bce("$am / $bm", 1);
$ae = $ae - $be;
if ($as * $bs > 0) {
$as = "+";
} else {
$as = "-";
}
$a = $as . $am . "E" . $ae;
$a = &fbc_norm($a);
&dbg1("$curprim f_div b: " . $a, 1);
return $a;
}
# Our exp routine tests the infinity and overflow cases itself, just in
# case the library function doesn't.
sub f_exp # fbc version
{
my($x) = @_;
my($nf);
&dbg1("$curprim f_exp $x", 1);
# Handle infinities first
if (&isninf($x)) {
&dbg1("$curprim f_exp(ninf) : 0", 32);
return $g_0;
} elsif (&isinf($x)) {
&dbg1("$curprim f_exp(inf) : inf", 32);
return $g_inf;
} elsif (&isnan($x)) {
&dbg1("$curprim f_exp(nan) : nan", 32);
return $g_nan;
}
$nf = 0;
# Handle the extreme overflow cases.
if (&f_lt($x, $g_n2000)) {
return $g_0;
} elsif (&f_gt($x, $g_2000)) {
return $g_inf;
} elsif (&f_lt($x, $g_0)) {
# #bc# loses precision when we evaluate e.g. e^-30 at scale 20, which
# gives a value like ".00000000000000123...". So we negate the argument
# and take the reciprocal of the result.
$nf = 1;
$x = &f_neg($x);
}
$x = &fbc_sci2fix($x);
$x =~ s/\+//g;
$x = &bce("e( $x )", 1);
$x = &fbc_fix2sci($x);
if ($nf) {
$x = &f_div($g_1, $x);
}
&dbg1("$curprim f_exp e: " . $x, 1);
return $x;
} # End of f.exp
# Our ln routine is strange -- it accepts negative arguments and returns
# the magnitude of the answer. It also special-cases infinities and zero.
sub f_ln # fbc version
{
my($x) = @_;
my($xs, $xm, $xe);
&dbg1("$curprim f_ln $x", 1);
($xs, $xm, $xe) = &fbc_split($x); $xs .= "1";
if (&isnan($xe)) {
&dbg1("$curprim f_ln(nan) : nan", 32);
return $g_nan;
}
# If the argument is negative we make it positive, because
# we want to return the real component of the logarithm of the number,
# and for any complex number, the real component of the log is the log
# of the number's magnitude.
if ($xs < 0) {
$x = &f_neg($x);
}
# Handle 0 and infinities first
if ($xm == 0.0) {
&dbg1("$curprim f_ln(0): " . $g_ninf, 33);
return $g_ninf;
} elsif (&isinf($xe)) {
&dbg1("$curprim f_ln(inf): " . $g_inf, 33);
return $g_inf;
}
$x = &fbc_sci2fix($x);
$x =~ s/\+//g;
$x = &bce("l( $x )", 1);
$x = &fbc_fix2sci($x);
&dbg1("$curprim f_ln d: " . $x, 1);
return $x;
}
# following are the partial primitives.
sub f_sub # fbc version
{
my($a, $b) = @_;
&dbg1("$curprim f_sub $a - $b", 1);
$a = &f_add($a, &f_neg($b));
&dbg1("$curprim f_sub: " . $a, 1);
return $a;
}
# Following are the non-primitives
sub f_pow10 # fbc version
{
my($n) = @_;
&dbg1("$curprim f.pow10 $n", 1);
$n = &f_exp(&f_mul($g_ln_10, $n));
&dbg1("$curprim f.pow10: " . $n, 1);
return $n;
}
sub f_log_n # fbc version
{
my($n, $b) = @_;
&dbg1("$curprim f.log_n v{ $b } $n", 1);
return &f_div(&f_ln($n), &f_ln($b));
}
sub f_log10 # fbc version
{
my($n) = @_;
&dbg1("$curprim f_log10 $n", 1);
$n = &f_mul(&f_ln($n), $g_log10_e);
&dbg1("$curprim f_log10: " . $n, 1);
return $n;
}
# sqrt(2 pi n) * n^n * e^(-n) * e^(1/12n)
# 1/2 ln(2 pi n) + n ln(n) - n + 1/12n - 1/1260 n^3 + ...
# t = n - 1;
# l = 1/2 ln(2 pi) + (n + 1/2) ln(n) - n + 1/(12 n) - 1/(360 n^3) + ...
# g = e^l
#
# %%% Accuracy is not good enough for scale>20. See notes in header.
sub f_gamma # fbc version
{
my($n) = @_;
my($l, $scal4, $n2, $np);
&dbg1("$curprim f_gamma $n", 2);
if (&isnan($n)) {
&dbg1("$curprim f_gamma(nan) : nan", 32);
return $g_nan;
}
# For very low negative arguments, the gamma function is so
# close to zero that we treat it as zero. However, for negative
# integers it's an infinite jump discontinuity, so we handle that case
# explicitly.
if (&f_lt($n, $g_n50)) {
if (&simple_nint($n, $curscale)) {
&dbg1("$curprim f_gamma(-int) : nan", 32);
return $g_nan;
} elsif (&f_eq($n, &f_int($n))) {
&dbg1("$curprim f_gamma(-N) : nan", 32);
return $g_nan;
}
return $g_0;
}
# Our approximation formula doesn't work well for small values, so
# we use the recurrance relation gamma(n+1) = n gamma(n).
$scal4 = $g_1;
while (&f_lt($n, $gamma_minarg)) { # scale up to our current precision
$scal4 = &f_mul($scal4, $n);
$n = &f_add($n, $g_1);
}
&dbg1(" f_gamma: minarg = $gamma_minarg; n scaled to $n", 2);
if ($dbg1flag & 2) {
my ($est1) = &f_div("657931.0", &f_mul("300.0", &f_pow($n, "25.0")));
&dbg1(" with new n, residual = $est1", 2);
}
# Since we're using Stirling's series for factorials, we have
# to subtract 1 to make it be a gamma function series.
$n = &f_sub($n, $g_1);
$l = $g_05_ln_2pi; # %%%
$l = &f_add($l, &f_mul(&f_add($n, $g_0_5), &f_ln($n)));
$l = &f_sub($l, $n);
$n2 = &f_mul($n, $n);
$np = $n;
&dbg1(" f_gamma: l = $l", 2);
$l = &f_add($l, &f_div($g_1, &f_mul($g_12, $np))); # N^-1 term
$np = &f_mul($np, $n2);
$l = &f_sub($l, &f_div($g_1, &f_mul($g_360, $np))); # N^-3 term
$np = &f_mul($np, $n2);
$l = &f_add($l, &f_div($g_1, &f_mul($g_1260, $np))); # N^-5
$np = &f_mul($np, $n2);
$l = &f_sub($l, &f_div($g_1, &f_mul($g_1680, $np))); # N^-7
$np = &f_mul($np, $n2);
$l = &f_add($l, &f_div($g_1, &f_mul($g_1188, $np))); # N^-9
$np = &f_mul($np, $n2);
$l = &f_sub($l, &f_div($g_691, &f_mul($g_360360, $np))); # N^-11
$np = &f_mul($np, $n2);
$l = &f_add($l, &f_div($g_7, &f_mul($g_1092, $np))); # N^-13
$np = &f_mul($np, $n2);
$l = &f_sub($l, &f_div($g_3617, &f_mul($g_122400, $np))); # N^-15
$np = &f_mul($np, $n2);
$l = &f_add($l, &f_div("43867.0", &f_mul("244188.0", $np))); # N^-17
$np = &f_mul($np, $n2);
$l = &f_sub($l, &f_div("174611.0", &f_mul("125400.0", $np))); # N^-19
$np = &f_mul($np, $n2);
$l = &f_add($l, &f_div("77683.0", &f_mul("5796.0", $np))); # N^-21
$np = &f_mul($np, $n2);
$l = &f_sub($l, &f_div("236364091.0", &f_mul("1506960.0", $np))); # N^-23
# The residual is less than the subsequent term, which is
# + 657931 / ( 300 N^25 )
# and the term after that is:
# - 3392780147 / ( 93960 N^27 )
# The last term is 3617/(122400 N^15), where N is the argument of
# the factorial function being approximated. For scale=50, 27!
# produces N=60, so the last term is about 6e-29. That's only enough
# for 29 digits of accuracy!
return (&f_div(&f_exp($l), $scal4));
}
# square root routine handles infinities and negative arguments properly
sub f_sqrt # fbc version
{
my($x) = @_;
my($xs, $xm, $xe);
&dbg1("$curprim f_sqrt $x", 1);
($xs, $xm, $xe) = &fbc_split($x); $xs .= "1";
# If the argument is negative we return 0, because 0 is
# the real component of the square root of a negative number.
if ($xs < 0) {
&dbg1("$curprim f_sqrt(-X) : 0", 32);
return $g_0;
} elsif (&isninf($x)) {
&dbg1("$curprim f_sqrt(ninf) : 0", 32);
return $g_0;
} elsif (&isinf($x)) {
&dbg1("$curprim f_sqrt(inf) : inf", 32);
return $g_inf;
} elsif (&isnan($x)) {
&dbg1("$curprim f_sqrt(nan) : nan", 32);
return $g_nan;
} elsif ($xm == 0.0) {
&dbg1("$curprim f_sqrt a: " . $g_0, 1);
return $g_0;
}
$x = &fbc_sci2fix($x);
$x =~ s/\+//g;
$x = &bce("e(l( $x )/2)", 1);
$x = &fbc_fix2sci($x);
&dbg1("$curprim f_sqrt d: " . $x, 1);
return $x;
}
# The sine algorithm uses the identity
# sin(a+b) = sin a cos b + cos a sin b
# The value being sin'd is broken into two pieces one of which is a multiple
# of pi/16 and the other is between -pi/32 and pi/32. Then we can use the
# Taylor series to get the sin of the latter part and a table for the
# first part
sub f_sin # fbc version
{
my($x) = @_;
&dbg1("$curprim f_sin $x", 1);
if (&isnan($x)) {
&dbg1("$curprim f_sin(nan) : nan", 32);
return $g_nan;
}
# Handle arguments out of range
# Note: The best way to handle things like this is to extend the
# numeric representation to include an "error" term, expressing the
# standard deviation of the computed value vs. the actual value.
# Then, the error of the sin function is the error of the argument
# times the cosine, but converging on +-1.0 when the error of the
# argument is 2 pi or larger. In the limit case the computed value
# will be expressed as 0.0 and the error as 1.0.
#
# Just for fun, I went to an office supply store and tested 5
# current-model scientific calculators (covering all price ranges)
# to see what they did when you take sin or cos of larger and larger
# numbers. Each calculator had a different cutoff value, but all
# seemed to choose a value that was related to the calculator's
# internal precision. The five cutoff values were:
# 5*10^7 pi ~= 1.57*10^8
# 4.45*10^9
# 1*10^10
# 1*10^12
# 1*10^13
if (&f_gt($x, $g_sin_limit)) {
return $g_0;
} elsif (&f_lt($x, &f_neg($g_sin_limit))) {
return $g_0;
}
$x = &fbc_sci2fix($x);
$x =~ s/\+//g;
$x = &bce("s( $x )", 1);
$x = &fbc_fix2sci($x);
&dbg1("$curprim f_sin : $x", 1);
return $x;
}
sub f_cos # fbc version
{
my($x) = @_;
&dbg1("$curprim f_cos $x", 1);
if (&isnan($x)) {
&dbg1("$curprim f_cos(nan) : nan", 32);
return $g_nan;
}
# Handle out-of-range
if (&f_gt($x, $g_sin_limit)) {
return $g_1;
} elsif (&f_lt($x, &f_neg($g_sin_limit))) {
return $g_1;
}
$x = &fbc_sci2fix($x);
$x =~ s/\+//g;
$x = &bce("c( $x )", 1);
$x = &fbc_fix2sci($x);
&dbg1("$curprim f_cos : $x", 1);
return $x;
}
sub f_tan # fbc version
{
my($x) = @_;
&dbg1("$curprim f_tan $x", 1);
if (&isnan($x)) {
&dbg1("$curprim f_tan(nan) : nan", 32);
return $g_nan;
}
# Handle out-of-range
if (&f_gt($x, $g_sin_limit)) {
return $g_inf;
} elsif (&f_lt($x, &f_neg($g_sin_limit))) {
return $g_inf;
}
$x = &f_div(f_sin($x),f_cos($x));
&dbg1("$curprim f_tan : $x", 1);
return $x;
}
# Arctangent function.
#
# Av{0} = 1 / sqrt(1 + x^{2}) , Bv{0} = 1
# Av{n+1} = (Av{n} + Bv{n}) / 2 ,
# Bv{n+1} = sqrt(Av{n+1} * Bv{n})
# arctan(x) = LIMIT [x * Av{0} / Av(n)]
sub f_arctan # fbc version
{
my($x) = @_;
my($a, $b, $a0, $ao, $i);
if (&isnan($x)) {
&dbg1("$curprim f_arctan(nan) : nan", 32);
return $g_nan;
}
# A few special cases, the x==0 case is just for speed
if (&f_eq($x, $g_0)) {
return $g_0;
} elsif (&ispinf($x)) {
&dbg1("$curprim f_arctan(inf) : pi/2", 32);
return &f_div($g_pi, $g_2);
} elsif (&isninf($x)) {
&dbg1("$curprim f_arctan(ninf) : -pi/2", 32);
return &f_div($g_pi, $g_n2);
}
$a0 = &f_div($g_1, &f_sqrt(&f_add($g_1, &f_mul($x, $x))));
$a = $a0;
$b = $g_1;
# Set up ao to be different from a so the loop has a chance to start
$ao = &f_sub($a, $g_1);
for($i=0; ($i<25) && (&f_ne($a, $ao)); $i++) {
$ao = $a;
$a = &f_div(&f_add($a, $b), $g_2);
$b = &f_sqrt(&f_mul($a, $b));
}
return &f_div(&f_mul($x, $a0), $a);
}
# arcsin(x) = arctan(x / sqrt(1 - x*x))
sub f_arcsin # fbc version
{
my($x) = @_;
my($a);
if (&isnan($x)) {
&dbg1("$curprim f_arcsin(nan) : nan", 32);
return $g_nan;
}
$a = &f_mul($x, $x);
if (&f_gt($a, $g_1)) {
return $g_0;
}
return &f_arctan(&f_div($x, &f_sqrt(&f_sub(1, $a))));
}
# arccos(x) = pi/2 - arctan(x / sqrt(1 - x*x))
sub f_arccos # fbc version
{
my($x) = @_;
my($a);
if (&isnan($x)) {
&dbg1("$curprim f_arccos(nan) : nan", 32);
return $g_nan;
}
$a = &f_mul($x, $x);
if (&f_gt($a, $g_1)) {
return $g_0;
}
return &f_div($g_pi, $g_2)
- &f_arctan(&f_div($x, &f_sqrt(&f_sub($g_1, $a))));
}
# pow function is more complex than exp and pow10, because
# it has to deal with any base. Specifically, if the base is negative
# it computes the angle and magnitude of the complex answer
# so it can calculate the real component.
sub f_pow # fbc version
{
my($b, $e) = @_;
my($mag, $scal5);
&dbg1("$curprim f_pow $b $e", 1);
if (&isnan($b) || &isnan($e)) {
&dbg1("$curprim f_pow(nan) : nan", 32);
return $g_nan;
}
# Handle negative bases
if (&f_lt($b, $g_0)) {
# The angle of the base is pi, so the angle of the
# exponent will be pi * e. Take cosine of this to
# determine how much of the answer will be in the
# real component.
$b = &f_neg($b);
$scal5 = &f_cos(&f_mul($g_pi, $e));
} else {
$scal5 = $g_1;
}
# Compute magnitude of answer
$mag = &f_exp(&f_mul(&f_ln($b), $e));
# If we got an overflow, we might be able to salvage
# the computation by using logarithms. This happens when we compute
# -20.0 to the power of 238.49999999999 (exactly 10 9's there)
# The magnitude is 1.97e310, which overflows, but the scale
# is 3.1416e-10 so the answer is actually well within
# range (and even within PT0 range)
if (&f_gt($mag, $g_ovr1)) {
if (&f_gt($scal5, $g_0)) {
$mag = &f_add(&f_mul(&f_ln($b), $e), &f_ln($scal5));
return &f_exp($mag);
} else {
$mag = &f_add(&f_mul(&f_ln($b), $e), &f_ln(&f_neg($scal5)));
return &f_neg(&f_exp($mag));
}
} else {
# Return real component of answer
return &f_mul($scal5, $mag);
}
}
# Root function contains the same complexity (pun intended) as
# the pow function.
sub f_root # fbc version
{
my($b, $r) = @_;
my($mag, $scal6);
if (&isnan($b) || &isnan($r)) {
&dbg1("$curprim f_root(nan) : nan", 32);
return $g_nan;
}
# Handle negative bases
if (&f_lt($b, $g_0)) {
# The angle of the base is pi, so the angle of the
# answer will be pi / r. Take cosine of this to
# determine how much of the answer will be in the
# real component.
$b = &f_neg($b);
$scal6 = &f_cos(&f_div($g_pi, $r));
} else {
$scal6 = $g_1;
}
$mag = &f_exp(&f_div(&f_ln($b), $r));
return &f_mul($scal6, $mag);
} # End of f.root
# addfunc: new functions can go here
endquote
sub fbc_init
{
my($init_scale) = @_;
my($BCIN, $BCOUT);
if (!($bc_running)) {
$BCIN = new IO::File;
$BCOUT = new IO::File;
# Before spawning, disable SIGINT so that a ^C won't kill the #bc#
# process (this also allows BASIC.run to catch ^C allowing the user
# to get out of infinite loops)
$SIG{INT} = 'IGNORE';
$pid = open2($BCIN, $BCOUT, 'bc -l 2>&1');
die "Can't open2 bc -l" unless ($pid);
$bc_in = $BCIN; $bc_out = $BCOUT;
&dbg1("open2 gave: $pid $bc_in $bc_out", 4);
$init_scale += 2;
&bce("scale=$init_scale", 0);
&bce("3*4", 1);
&bce("27^143", 1);
$bc_running = 1;
}
# Define the primitives
eval($fbc_prim);
# These constants are mostly used by the various transcendental functions
$g_0 = "0";
$g_0_5 = "0.5";
$g_1 = "1";
$g_n1 = "-1";
$g_2 = "2";
$g_n2 = "-2";
$g_4 = "4";
$g_5 = "5";
$g_6 = "6";
$g_7 = "7";
$g_8 = "8";
$g_10 = "10";
$g_12 = "12";
$g_16 = "15";
$g_16 = "16";
$g_n50 = "-50";
$g_360 = "360";
$g_691 = "691";
$g_1092 = "1092";
$g_1188 = "1188";
$g_1260 = "1260";
$g_1680 = "1680";
$g_n2000 = "-2000";
$g_2000 = "2000";
$g_3617 = "3617";
$g_122400 = "122400";
$g_360360 = "360360";
$g_2p32m1 = "4294967295";
}
sub fbc_rescale
{
my($newscale) = @_;
$newscale += 2;
&bce("scale=$newscale", 0);
}
sub fbc_close
{
&dbg1("fbc_close", 4);
close($bc_out);
while(<$bc_in>) { }
close($bc_in);
$bc_running = 0;
}
#--------------------------------------------------- hybrids
#
# These routines operate on top of both sets of primitives, but aren't
# full PT routines. They're used for conversion and formatting.
# pvu.sint is like simple.int but takes a PT and unc as well (it just makes
# sure they are zero and calls simple.int)
sub pvu_sint
{
my($xp, $xv, $xu, $sc) = @_;
return (($xp == 0) && ($xu == 0) && (&simple_int($xv, $sc)));
}
# normalise works on string-format floating-point numbers of the form
# [+-]1.23e[+-]456 and [+-]1.234u12e[+-]456, and works over either type
# of primitive operators.
# Its main function is to promote values like "23e456" to a PT1, and as
# such it returns a PT tuple as a result.
sub normalise
{
my($x) = @_;
my($m, $m1, $m2, $m3, $e, $s, $t1, $t2, $t3);
my($unc, $u1, $u2, $eu);
&dbg1("nrm01 x '$x'", 8);
if (&simple_int($x, $curscale)) {
return (0, $x, 0);
}
# Extract mantissa part (which includes uncertainty if any)
if ($x =~ m|^([-+0-9.U]+)E([-+0-9]+)$|i) {
# 1.02E34, 1.02U1E34, etc.
$m = $1; $e = $2;
&dbg1("nrm02 m $m e $e", 8);
} elsif ($x =~ m|^([-+0-9.U]+)$|i) {
# 1.02, 1.02U1, etc.
$m = $1; $e = 0;
&dbg1("nrm03 m $m e $e", 8);
} else {
print STDERR "*** Cannot parse '$x' (nrm1) $inbline\n";
return(0, $g_0, '0');
}
# Make sure mantissa has an uncertainty component (add one if not present)
$m =~ s/u/U/g;
if ($m =~ m/U.+U/) {
print STDERR ("*** Cannot parse multiple U components: '$x' (nrm3) $inbline\n");
return(0, $g_0, '0');
} elsif ($m =~ m/U[0-9]+$/) {
# Okay
} elsif ($m =~ m/U$/) {
$m .= "0";
&dbg1("nrm04 m $m", 8);
} elsif ($m =~ m/U/) {
print STDERR ("*** Cannot parse U component of '$x' (nrm4) $inbline\n");
return(0, $g_0, '0');
} else {
$m .= "U0";
&dbg1("nrm05 m $m", 8);
}
$m1 = $m2 = "";
# Now extract the first mantissa digit and eliminate any extra zeros
if (0) {
} elsif ($m =~ m|^([-+]?)0*[.](0+)([1-9])([0-9]*)U([0-9]+)$|) {
# 000.|0|1||, .|0|1||, 0.|00|1|23|, or .|00|1|23|
$s = $1; $t1 = $2; $t2 = $3; $t3 = $4; $u1 = $5;
&dbg1("nrm06 0.0 s $s t1 $t1 t2 $t2 t3 $t3 u1 $u1", 8);
$m1 = "$s$t2";
$e = $e - (1 + length($t1));
$eu = $e - length($t3);
$m2 = $t3 . "0";
} elsif ($m =~ m|^([-+]?)0*[.]([1-9])([0-9]*)U([0-9]+)$|) {
# 0.|1||, .|1||, 0.|1|23|, or .|1|23|
$s = $1; $t1 = $2; $t2 = $3; $u1 = $4;
&dbg1("nrm07 0.D s $s t1 $t1 t2 $t2 u1 $u1", 8);
$m1 = "$s$t1";
$e--;
$eu = $e - length($t2);
$m2 = $t2 . "0";
} elsif ($m =~ m|^([-+]?)(0*[1-9])[.]([0-9]*)U([0-9]+)$|) {
# |01|.||, |1|.||, |01|.|23|, or |1|.|23|
$s = $1; $m1 = $2; $m2 = $3; $u1 = $4;
&dbg1("nrm08 D. s $s m1 $m1 m2 $m2 u1 $u1", 8);
$eu = $e - length($m2);
$m2 = $m2 . "0"; # Make sure m2 has at least 1 digit
} elsif ($m =~ m|^([-+]?0*[1-9])([0-9]+)[.]([0-9]*)U([0-9]+)$|) {
# |01|2|.||, |1|2|.||, |01|2|.|34|, or |1|2|.|34|
$m1 = $1; $m2 = $2; $m3 = $3; $u1 = $4;
&dbg1("nrm09 DD. m1 $m1 m2 $m2 m3 $m3 u1 $u1", 8);
$eu = $e - length($m3);
$e += length($m2);
$m2 = $m2 . $m3;
} elsif ($m =~ m|^([-+]?0*[1-9])([0-9]*)U([0-9]+)$|) {
# |01||, |1||, |01|2|, or |1|2|
$m1 = $1; $m2 = $2; $u1 = $3;
&dbg1("nrm10 D m1 $m1 m2 $m2 u1 $u1", 8);
$eu = $e;
$e += length($m2);
$m2 = $m2 . "0"; # Make sure m2 has at least 1 digit
} elsif ($m =~ m|^[-+]?0*[.](0+)U([0-9]+)$|) {
# decimal point and 1 or more zeros and no exponent: .0 or 0.00, etc.
# Sign does not matter.
$m1 = '0'; $m2 = $1; $u1 = $2;
$eu = $e - length($m2);
&dbg1("nrm11 .0 or 0.00 : m1 $m1 m2 $m2 u1 $u1 eu $eu", 8);
# %%% Eventually we'll want to be smarter about cases where the
# uncertainty is larger than the value, and report errors when a
# calculation that makes the uncertainty distribution nonlinear,
# e.g. computing e^4(1).
} elsif ($m =~ m|^[-+]?0+[.]U([0-9]+)$|) {
# 000. or 0.
$m1 = '0'; $m2 = ''; $u1 = $1;
$eu = $e;
&dbg1("nrm12 0. or 000. : m1 $m1 m2 '' u1 $u1 eu $eu", 8);
} elsif ($m =~ m|^[-+]?0+U([0-9]+)$|) {
# 0 or 00 with no decimal point; the uncertainty is an integer.
# %%% Again, we do not try to retain any uncertainty here.
&dbg1("nrm13 0 or 00", 8);
&dbg1("nrm14 :: 0 PT $g_0 (0)", 8);
return(0, $g_0, '0');
} else {
print STDERR ("*** Cannot parse '$m' (nrm2) $inbline\n");
return(0, $g_0, '0');
}
# Rebuild the mantissa in a normal 1.23 format
$m = "$m1.$m2";
# Append the exponent, to get the full number as "1.23E5" or "-1.23E-5"
$x = $m . "E" . $e;
# Uncertainty is constructed the same way
if ($u1 eq '0') {
$um = $u1;
$unc = $u1;
&dbg1("nrm15 um $um unc $unc", 8);
} elsif ($u1 =~ m/^([1-9])$/) {
# Single-digit uncertainty
$um = $u1;
$unc = $u1 . "E" . $eu;
&dbg1("nrm16 um $um unc $unc", 8);
} elsif ($u1 =~ m/^([0-9])([0-9]+)$/) {
# Multi-digit uncertainty. Turn "123e7" into "1.23e9"
$u1 = $1; $u2 = $2;
$eu += length($u2);
$um = $u1 . "." . $u2;
$unc = $um . "E" . $eu;
&dbg1("nrm17 um $um unc $unc", 8);
} else {
print STDERR ("*** Cannot rebuild unc from '$u1' (nrm5)\n");
return(0, $g_0, '0');
}
# At this point $x and $unc are properly formatted to pass as arguments
# to either of the f_xxx libraries. However their components ($m, $e,
# $um and $eu) are used below if we need to promote
if ($e < -300) {
# %%% Here is another spot we have to change if/when we support
# uncertainty greater than the value.
&dbg1("nrm18 :: 0 PT $g_0 (0)", 8);
return(0, $g_0, '0');
} elsif ($e > 300) {
# Need to promote. First, extract sign
if ($m < 0) {
$s = -1;
$m = &f_neg($m);
} else {
$s = 1;
}
# take log. Note, we will call whichever f_xxx routines are
# current and that's the right thing to do.
# New uncertainty is u/(x ln(10)) where u is the old uncertainty.
# Example: 1.0000(10)e310 x=1e310 unc=1e307
# 1 PT 310.000434 unc 0.000434
# The way we divide by x is by subtracting the exponent $e and
# dividing by the mantissa $m. We do the exponent part first, create
# a properly formatted number then use f_xxx routines to divide out
# the mantissa of X and ln(10).
$eu = $eu - $e;
$unc = $um . "E" . $eu;
$unc = &f_div($unc, &f_mul($m, $g_ln_10));
&dbg1("nrm19 eu $eu um $um unc $unc", 8);
# Now we can rebuild the value of X. Since we're promoting it to a
# PT1, the new value is the old exponent plus the log10 of the old
# mantissa.
# At this point it is no longer a pure "mantissa", as it is greater than
# 9.999; likewise $unc might be less than 1.0 or greater than 9.999
$x = &f_add($e, &f_log10($m));
# put sign back in
$x = &f_mul($s, $x);
&dbg1("nrm20 :: 1 PT $x ($unc)", 8);
return (1, $x, $unc);
}
# else
# no promote necessary
&dbg1("nrm21 :: 0 PT $x ($unc)", 8);
return (0, $x, $unc);
} # end of norm.alise
# splt takes string numbers of the format "2P[+-]3.4E[+-]56", or simple
# floating-point numbers like "[+-]3.4E[+-]56", plain numbers like "234.56",
# or IR inlines like "I17", performs the appropriate conversion or
# value retrieval and returns a PT tuple result.
sub splt
{
my($x) = @_;
my($i);
my($xv_pt, $xv_val, $xv_unc); # for normalizing, if string is for example
# "1.2e345" or "2pt1.2e345"
my($t1);
&dbg1("splt($x)", 32);
# Recognize '.' as a synonym for '%' ('.' is from #bc#, '%' is from #maxima#)
if ($x eq '.') {
return ($a2_pt, $a2_val, $a2_unc, '');
}
if ($x =~ m|^I([0-9]+)|) {
# retrieve a stored intermediate result
$i = $1;
&dbg1(" I -> ($ir_pt[$i], $ir_val[$i], $ir_unc[$i], $ir_lvk[$i])", 32);
return($ir_pt[$i], $ir_val[$i], $ir_unc[$i], $ir_lvk[$i]);
} elsif ($x =~ m|^R([0-9]+)|) {
# retrieve a stored history result
$i = $1;
&dbg1(" R -> ($h_pt[$i], $h_val[$i], $h_unc[$i], '')", 32);
return($h_pt[$i], $h_val[$i], $h_unc[$i], '');
} elsif ($x =~ m|^\%|) {
# retrieve last result
&dbg1(" % -> ($h_pt[$i], $h_val[$i], $h_unc[$i], '')", 32);
return($a2_pt, $a2_val, $a2_unc, '');
} elsif ($x =~ m|^(.+)P(.+)$|) {
my($x_pt, $x_val, $x_unc);
# convert an inline PT number.
# first, assume it's legal, i.e. the val part is within range
$x_pt = $1;
$x_val = $2;
print "splt 2 $x : " if ($format_debug);
$x_val =~ s|N|-|g;
# Now check if the val is out of range. If so, add additional levels
# to the power-tower. This is where "1P1E310" gets turned into 2P310.
($xv_pt, $xv_val, $xv_unc) = &normalise($x_val);
if ($xv_pt > 0) {
$x_pt += $xv_pt;
} elsif (($x_pt > 0) && ($xv_val < 300)) {
# Handle the other kind of denormalised values, where the PT is
# too high. For example, '1p1.000(1)e2' is converted to '0p1.00(26)e100'
$t1 = &f_pow10($xv_val);
$xv_unc = &f_pow10($xv_val+$xv_unc) - $t1;
$xv_val = $t1;
$x_pt -= 1;
}
print "$x_pt $xv_val $xv_unc\n" if ($format_debug);
return ($x_pt, $xv_val, $xv_unc, '');
}
# else, this is a normal number "1.23" or "-23.45", or scientific notation
# "1.23E45". This is where "1E5000" gets turned into 1P5000.
print "splt 1 $x : " if ($format_debug);
$x =~ s|N|-|g;
($xv_pt, $xv_val, $xv_unc) = &normalise($x);
print "$xv_pt $xv_val $xv_unc\n" if ($format_debug);
return ($xv_pt, $xv_val, $xv_unc, '');
} # End of splt
#--------------------------------------------------- hcfloat.h
#
# This is the data type used for user calculations. The first field "pt"
# is an integer representing how many times we've taken the logarithm
# (base 10) of the value being represented. The "val" field is
# the value, or the log of the value, or the log of the log of the
# value, etc. If the value being represented is negative, the "val"
# field is negative.
#
# typedef struct {
# unsigned int pt;
# double val;
# } pt_real;
my $unused2 = q`
Patashu break_eternity.js
README.md acknowledges "nathanisbored, for coming up with a short
and sweet formula for 10^a + 10^b == 10^c which has been used in add
and mul."
It is mostly equivalent to what we do in Hypercalc, except that the
nathanisbored version cleverly handles the signs as well (accomplishing
our addlog and sublog in a single routine)
add:
// "layer" is the level or "pt". In the code below, it has already
// been set up that "a" is the larger in magnitude. FC is a constructor
// that takes (sign, layer, mantissa) in that order
if (layera === 1 && layerb === 0) {
if (Math.abs(a.mag - Math.log10(b.mag)) > MAX_SIGNIFICANT_DIGITS) {
return a;
} else {
var _magdiff = Math.pow(10, a.mag - Math.log10(b.mag));
var _mantissa = b.sign + a.sign * _magdiff;
return FC(Math.sign(_mantissa), 1,
Math.log10(b.mag) + Math.log10(Math.abs(_mantissa)));
}
}
// only remaining case: layera === 1 && layerb === 1
if (Math.abs(a.mag - b.mag) > MAX_SIGNIFICANT_DIGITS) {
return a;
} else { // pt_addmul steps:
var _magdiff2 = Math.pow(10, a.mag - b.mag); // 1: subtract ; 2: antilog
var _mantissa2 = b.sign + a.sign * _magdiff2; // 3: add to 1
return FC(Math.sign(_mantissa2), 1,
b.mag + Math.log10(Math.abs(_mantissa2))); // 4: log ; 5: add to A
}
(multiply just calls add)
`;
#------------------------------------------------------ hcpt.c
#
# These routines (from "hcpt.c" in my PalmPilot Hypercalc app) do all the
# calculation functions and operate on "PT" format values.
# Return the logarithm of the sum of the antilog of two numbers. For example,
# If the inputs are 301.0 and 299.0, it returns 301.004321, because
# 10^301 + 10^299 = 10^301.004321. This is used for adding PT1's and for
# multiplying PT2's. For subtraction and division use pt.sublog
#
# To compute the log of the answer, we take the log of the two inputs
# and subtract, then take 10 to the power of that value, take the
# antilog, add to 1 and take the log again:
#
# To compute: 10^301 + 10^299
# Input mantissas: A=301.0, B=299.0
# 1: subtract: -2.0
# 2: antilog: 0.01
# 3: add to 1: 1.01
# 4: logarithm: 0.004321
# 5: add to A: 301.004321
# Answer: 10^301.004321... = 1.01e301
#
sub pt_addlog
{
my($lar, $sm) = @_;
my($t);
&dbg1("pt.addlog $lar + $sm", 32);
$t = &f_sub($sm, $lar); # -2.0
$t = &f_add($g_1, &f_pow10($t)); # 1.01
$t = &f_add($lar, &f_log10($t)); # 301.004321
return $t;
}
# A similar operation for subtraction
sub pt_sublog
{
my($lar, $sm) = @_;
my($t);
&dbg1("pt.sublog $lar - $sm", 32);
$t = &f_sub($sm, $lar); # -2.0
$t = &f_sub($g_1, &f_pow10($t)); # 0.99
$t = &f_add($lar, &f_log10($t)); # 300.995635
return $t;
}
# Addition and subtraction are done through two intermediate routines,
# addpos and subpos. addpos adds any two positive values (not necessarily
# with the largest first) and subpos subtracts a positive value from another
# positive value, where the result is known to be positive or zero. These
# two routines combined with appropriate sign manipulation cover all the
# cases of addition and subtraction.
sub pt_addpos
{
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc) = @_;
my($xpt, $xv, $xu, $ypt, $yv, $yu);
my($s1, $u1);
my($ans_pt, $ans_val, $ans_unc);
# Put bigger argument first, so we have fewer cases to handle
if ($x_pt > $y_pt) {
$xpt = $x_pt; $ypt = y_pt;
$xv = $x_val; $yv = $y_val;
$xu = $x_unc; $yu = $y_unc;
} elsif (($x_pt == $y_pt) && &f_gt($x_val, $y_val)) {
$xpt = $x_pt; $ypt = $y_pt;
$xv = $x_val; $yv = $y_val;
$xu = $x_unc; $yu = $y_unc;
} else {
$xpt = $y_pt; $ypt = $x_pt;
$xv = $y_val; $yv = $x_val;
$xu = $y_unc; $yu = $x_unc;
}
# Deal with infinities.
if (&f_eq($xv, $g_inf)) {
return ($g_pt_inf, $g_inf, 0);
} elsif (&f_eq($xv, $g_nan)) {
return ($g_pt_nan, $g_nan, 0);
}
if ($xpt == 0) {
# both are simple numbers, add and test for promote
$s1 = &f_add($xv, $yv);
$u1 = $xu + $yu; # &f_add($xu, $yu);
if (&f_gt($s1, $g_ovr1)) {
# Sum overflowed, promote to PT1
$ans_pt = 1;
$ans_val = &f_log10($s1);
# %%% $ans_unc = $ans_val - &f_log10($s1-$u1);
return ($ans_pt, $ans_val, 0);
} else {
$ans_pt = 0;
$ans_val = $s1;
$ans_unc = $u1;
return ($ans_pt, $ans_val, $ans_unc);
}
} elsif ($xpt == 1) {
# Adding a PT0 to a PT1, answer will usually be x, except in cases like
# {1, 301.0} + {0, 1.0e299} => {1, 301.004321}
# Adding two PT1's is similar, and usually reduces to just returning X.
# The only difference is that we don't have to take the log of y.
if ($ypt == 0) {
# PT1 plus PT0
# print "pt_addlog($xv, f_log10($yv))\n";
$ans_val = &pt_addlog($xv, &f_log10($yv));
} else {
# PT1 plus PT1
# print "pt_addlog($xv, $yv)\n";
$ans_val = &pt_addlog($xv, $yv);
}
# $ans_unc = $xu %%% this is wrong, it will actually be bigger
return (1, $ans_val, 0);
}
# else: X is PT2 or larger. Answer is always uncomputably larger than X.
return ($xpt, $xv, 0);
} # End of pt_addpos
# pt_negate is trivial, but is implemented as a separate routine to try to
# simplify the work that would be entailed if I change the method of
# representing sign in PT's.
sub pt_negate
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_val);
$ans_val = &f_neg($x_val);
return ($x_pt, $ans_val, $x_unc);
}
# Subtract a smaller positive value from a larger positive value, to give
# a positive result.
sub pt_subpos
{
my($x_pt, $x_val, $x_unc, $m_pt, $m_val, $m_unc) = @_;
my($xpt, $xv, $mpt, $mv, $s1, $u1);
my($ans_pt, $ans_val, $ans_unc);
&dbg1("pt.subpos ($x_pt, $x_val, $x_unc) - ($m_pt, $m_val, $m_unc)", 32);
$xpt = $x_pt; $mpt = $m_pt;
$xv = $x_val; $mv = $m_val;
# The uncertainty of a sum is the sum of the uncertainties.
$u1 = $x_unc + $m_unc; # &f_add($x_unc, $m_unc);
# Deal with infinities. If it's +inf + -inf, +inf wins.
if (&f_eq($xv, $g_inf)) {
return ($g_pt_inf, $g_inf, 0);
} elsif (&f_eq($xv, $g_nan)) {
return ($g_pt_nan, $g_nan, 0);
}
# If args are equal, answer is 0
if (($xpt == $mpt) && (&f_eq($xv, $mv))) {
return (0, $g_0, $u1);
}
# If m is zero, answer is x (but uncertainty still needs to be xu+mu)
if (($mpt == 0) && (&f_eq($mv, $g_0))) {
return ($x_pt, $x_val, $u1);
}
if ($xpt == 0) {
# both are simple numbers, subtract -- no demote is possible
$s1 = &f_sub($xv, $mv);
return (0, $s1, $u1);
} elsif (($xpt == 1) && ($mpt == 0)) {
# Subtracting a PT0 from a PT1. Answer will usually be x, but in a few
# cases like {1, 301.0} - {0, 1.0e299} => {1, 300.995678}
# To compute the log of the answer, we take the log of the two inputs
# and subtract, then take 10 to the power of that value, take the
# antilog, add to 1 and take the log again
# inputs: {1, 301.0} - {0, 1.0e299}
# logs: 301.0, 299.0
# subtract: -2.0
# antilog: 0.01
# sub from 1: 0.99
# log: -0.004321
# add: 300.995678
# final answer: {1, 300.995678}
# In addition, we check for demote to a PT0 answer.
$s1 = &f_sub(&f_log10($mv), $xv);
$s1 = &f_sub($g_1, &f_pow10($s1));
$s1 = &f_add($xv, &f_log10($s1));
if (&f_gt($s1, $g_log10_ovr1)) {
$ans_unc = $x_unc; # %%% will actually be bigger
return (1, $s1, 0);
}
# else
return (0, &f_pow10($s1), 0);
} elsif ($xpt == 1) {
# This is the case where both are PT1's. We actually do the same thing as
# in the previous case (although it usually reduces to just returning X)
# the only difference being that we don't have to take the log of y.
$s1 = &f_sub($mv, $xv);
$s1 = &f_sub($g_1, &f_pow10($s1));
$s1 = &f_add($xv, &f_log10($s1));
if (&f_gt($s1, $g_log10_ovr1)) {
$ans_unc = $x_unc; # %%% will actually be bigger
return (1, $s1, 0);
}
# else, demote
$xv = &f_pow10($s1);
# $u1 = $xv - &f_pow10($s1 - $x_unc); %%% this needs work
return(0, $xv, 0);
} else {
# X is PT2 or larger. No matter what Y is, the answer is X because X is
# bigger.
return ($xpt, $xv, $x_unc);
}
}
$unused1 = q@
# Compare routine -- right now it isn't used anywhere.
int pt_compare(pt_real x, pt_real y)
{
int neg;
if (($x_val < 0) && ($y_val > 0)) {
return -1;
} elsif (($x_val > 0) && ($y_val < 0)) {
return 1;
}
# Both are of same sign. Force to both positive
if ($x_val < 0) {
$x_val = - $x_val;
$y_val = - $y_val;
neg = -1;
} else {
neg = 1;
}
# check difference in PTs
if ($x_pt > $y_pt) {
return neg * 1;
} elsif ($x_pt < $y_pt) {
return neg * -1;
}
# check difference in values
if ($x_val > $y_val) {
return neg * 1;
} elsif ($x_val < $y_val) {
return neg * -1;
}
# If they made it through all this, they're equal!
return 0;
}
@;
# Normal comparison -- returns -1, 0, or 1 according as xy
# Use pt_magcompare to ignore sign
sub pt_compare
{
my($x_pt, $x_val, $y_pt, $y_val) = @_;
my($xv, $yv, $neg);
$xv = $x_val; $yv = $y_val;
if (&f_lt($xv, $g_0) && &f_gt($yv, $g_0)) {
return -1;
} elsif (&f_gt($xv, $g_0) && &f_lt($yv, $g_0)) {
return 1;
}
# Both are of same sign. Force to both positive
if (&f_lt($xv, $g_0)) {
$xv = &f_neg($xv);
$yv = &f_neg($yv);
$neg = -1;
} else {
$neg = 1;
}
# check difference in PTs
if ($x_pt > $y_pt) {
return $neg;
} elsif ($x_pt < $y_pt) {
return $neg * -1;
}
# check difference in values
if (&f_gt($xv, $yv)) {
return $neg;
} elsif (&f_lt($xv, $yv)) {
return $neg * -1;
}
# If they made it through all this, they're equal!
return 0;
}
# Magnitude compare -- tells you which is "larger" as in further from 0.
# Use pt.compare for standard compare.
sub pt_magcompare
{
my($x_pt, $x_val, $y_pt, $y_val) = @_;
my($xv, $yv);
$xv = $x_val; $yv = $y_val;
if (&f_lt($xv, $g_0)) {
$xv = &f_neg($xv);
}
if (&f_lt($yv, $g_0)) {
$yv = &f_neg($yv);
}
# check difference in PTs
if ($x_pt > $y_pt) {
return 1;
} elsif ($x_pt < $y_pt) {
return -1;
}
# check difference in values
if (&f_gt($xv, $yv)) {
return 1;
} elsif (&f_lt($xv, $yv)) {
return -1;
}
# If they made it through all this, they're equal!
return 0;
}
# Now, finally, we can do addition. There are four cases of positive and
# negative arguments, and in addition if the signs are different there are
# two cases corresponding to which is of greater magnitude. This routine
# dispatches each of the 6 cases using the two compare routines to figure
# out which case is present and the two arithmetic routines to generate
# answers, with pt_negate to massage the signs of arguments and results
# where necessary.
sub pt_add
{
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc) = @_;
# Deal with infinities. If it's +inf and -inf, +inf wins.
if (&isninf($x_val)) {
&dbg1("pt_add ninf + X: ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
} elsif (&isninf($y_val)) {
&dbg1("pt_add X + ninf: ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
} elsif (&isinf($x_val)) {
&dbg1("pt_add inf + X: inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isinf($y_val)) {
&dbg1("pt_add X + inf: inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isnan($x_val)) {
&dbg1("pt_add nan + X: nan", 32);
return ($g_pt_nan, $g_nan, 0);
} elsif (&isnan($y_val)) {
&dbg1("pt_add X + nan: nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if (&f_ge($x_val, $g_0)) {
if (&f_ge($y_val, $g_0)) {
# Both positive
return &pt_addpos($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
} else {
# +X, -Y
if (&pt_magcompare($x_pt, $x_val, $y_pt, $y_val) > 0) {
# X is of greater magnitude
return &pt_subpos($x_pt, $x_val, $x_unc,
&pt_negate($y_pt, $y_val, $y_unc));
} else {
return &pt_negate(&pt_subpos(&pt_negate($y_pt, $y_val, $y_unc),
$x_pt, $x_val, $x_unc));
}
}
} elsif (&f_ge($y_val, $g_0)) {
# -X, +Y
if (&pt_magcompare($x_pt, $x_val, $y_pt, $y_val) > 0) {
# X is of greater magnitude
return &pt_negate(&pt_subpos(&pt_negate($x_pt, $x_val, $x_unc),
$y_pt, $y_val, $y_unc));
} else {
return &pt_subpos($y_pt, $y_val, $y_unc,
&pt_negate($x_pt, $x_val, $x_unc));
}
} else {
# Both negative
return &pt_negate(&pt_addpos(&pt_negate($x_pt, $x_val, $x_unc),
&pt_negate($y_pt, $y_val, $y_unc)));
}
}
# After going to all that work to do addition, subtraction is easy!
sub pt_sub
{
my($x_pt, $x_val, $x_unc, $m_pt, $m_val, $m_unc) = @_;
return &pt_add($x_pt, $x_val, $x_unc,
&pt_negate($m_pt, $m_val, $m_unc));
}
# We define PT versions of pow10 and log10 next, because
# the multiply and divide routines use them to reduce certain cases to adds.
# pt.log10 is very simple, because it's a single function argument and
# there are no strange boundary conditions where we demote or promote.
sub pt_log10
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_pt, $ans_val, $ans_unc);
# Handle 0 and infinities first
if (&f_eq($x_val, $g_0)) {
&dbg1("pt_log10(0): ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
} elsif (&ispinf($x_val)) {
&dbg1("pt_log10(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isninf($x_val)) {
&dbg1("pt_log10(ninf): 0", 32);
return (0, $g_0, 0);
} elsif (&isnan($x_val)) {
&dbg1("pt_log10(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if (&f_lt($x_val, $g_0)) {
# Real component of log of negative number is the log of the magnitude
$x_val = &f_neg($x_val);
}
if ($x_pt == 0) {
# Call normal log10 function
$ans_pt = 0;
$ans_val = &f_log10($x_val);
# d(log10(u)) = du/(ln(10) u)
# We don't need to take abs(x_val) because we just made x_val positive.
$ans_unc = &f64_div($x_unc, $k_ln_10 * $x_val); # &f_div($x_unc, &f_mul($g_ln_10, $x_val));
return ($ans_pt, $ans_val, $ans_unc);
}
# else
# Just subtract 1 from PT
$ans_pt = $x_pt - 1;
$ans_val = $x_val;
return ($ans_pt, $ans_val, 0);
}
# pt.pow10 is almost as simple as pt.log10, and for similar reasons.
sub pt_pow10
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_pt, $ans_val, $ans_unc);
# Handle infinities first
if (&ispinf($x_val)) {
&dbg1("pt_pow10(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isninf($x_val)) {
&dbg1("pt_pow10(ninf): 0", 32);
return (0, $g_0, 0);
} elsif (&isnan($x_val)) {
&dbg1("pt_pow10(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if ($x_pt == 0) {
# Handle all negative PT0's and any positive PT0's for which
# the answer is in PT0 range.
if (&f_lt($x_val, $g_log10_ovr1)) {
$ans_pt = 0;
$ans_val = &f_pow10($x_val);
# d(10^u) = ln(10) 10^u du
$ans_unc = $k_ln_10 * $ans_val * $x_unc;
return ($ans_pt, $ans_val, $ans_unc);
}
}
# Now handle negative PT1's, etc. We only have to test $x_val here,
# because the negative PT0 case was already handled.
if (&f_lt($x_val, $g_0)) {
return (0, $g_0, 0);
}
# All other cases: simple promote
$ans_pt = $x_pt + 1;
$ans_val = $x_val;
return ($ans_pt, $ans_val, 0);
} # End of pt.pow10
# The multiply routine is implemented fully from primitives, with
# 12 separate cases for all the combinations of PT class of the
# two arguments and the result. It's this way because the
# multiply was implemented before the add, and it is left as-is
# for accuracy and speed.
sub pt_mul
{
my($px_pt, $px_val, $px_unc, $py_pt, $py_val, $py_unc) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
my($xpt, $xv, $xu, $ypt, $yv, $yu);
my($p1, $d1, $sign);
my($ans_pt, $ans_val, $ans_unc);
$x_pt = $px_pt; $y_pt = $py_pt;
$x_val = $px_val; $y_val = $py_val;
$x_unc = $px_unc; $y_unc = $py_unc;
if (&isnan($x_val) || &isnan($y_val)) {
&dbg1("pt.mul(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Enforce positive arguments and remember sign.
$sign = $g_1;
if (&f_lt($x_val, $g_0)) {
$sign = &f_neg($sign);
$x_val = &f_neg($x_val);
}
if (&f_lt($y_val, $g_0)) {
$sign = &f_neg($sign);
$y_val = &f_neg($y_val);
}
# Put bigger argument first, so we have fewer cases to handle
if (&pt_magcompare($x_pt, $x_val, $y_pt, $y_val) > 0) {
$xpt = $x_pt; $ypt = $y_pt;
$xv = $x_val; $yv = $y_val;
$xu = $x_unc; $yu = $y_unc;
} else {
$xpt = $y_pt; $ypt = $x_pt;
$xv = $y_val; $yv = $x_val;
$xu = $y_unc; $yu = $x_unc;
}
if (&f_eq($yv, $g_0) && &f_eq($yu, $g_0)) {
# don't need to test $ypt because $yv==0 only happens when $ypt==0
# trivial case
return (0, $g_0, 0);
} elsif (&f_eq($xv, $g_inf)) {
# don't need to test pt field, val==inf only occurs when pt==MAXINT
if (&f_gt($sign, 0)) {
return ($g_pt_inf, $g_inf, 0);
} else {
return ($g_pt_inf, $g_ninf, 0);
}
} elsif ($xpt == 0) {
# %%% it would be good to avoid the overflow before it happens
# Simple case: both are plain numbers
$p1 = &f_mul($xv, $yv);
&dbg1("pt.mul got: " . $p1, 1);
if (&f_ge($p1, $g_ovr1)) {
# Overflowed range of pt0: recompute product as a pt1
$p1 = &f_add(&f_log10($xv), &f_log10($yv));
$ans_pt = 1;
$ans_val = &f_mul($sign, $p1);
return ($ans_pt, $ans_val, 0);
} else {
# It fits in a PT0
$ans_pt = 0;
$ans_val = &f_mul($sign, $p1);
# Product rule: d(uv) = u dv + v du
# xv and yv are both positive because of sign handling above
$ans_unc = $xv * $yu + $xu * $yv;
return ($ans_pt, $ans_val, $ans_unc);
}
} elsif (($xpt == 1) && ($ypt == 0)) {
# X is PT1 and Y is PT0.
# We don't worry about promoting to
# pt2 because that can only happen if X is near {1, 1e300}
# and if it were, we're calculating log(answer) = 1e100
# plus log(y), and since log(y) can be no bigger than 300
# the answer would always be 1e300.
# However, we do have to worry about demoting to PT0.
$p1 = &f_add($xv, &f_log10($yv));
if (&f_gt($p1, $g_log10_ovr1)) {
$ans_pt = 1;
$ans_val = &f_mul($sign, $p1);
return ($ans_pt, $ans_val, 0);
} else {
$ans_pt = 0;
$ans_val = &f_mul($sign, &f_pow10($p1));
return ($ans_pt, $ans_val, 0);
}
} elsif ($xpt == 1) {
# Both are PT1's -- just add the logs.
$d1 = &f_add($xv, $yv);
if (&f_gt($d1, $g_ovr1)) {
# Promote to PT2
$ans_pt = 2;
$ans_val = &f_mul($sign, &f_log10($d1));
return ($ans_pt, $ans_val, 0);
} elsif (&f_gt($d1, $g_log10_ovr1)) {
$ans_pt = 1;
$ans_val = &f_mul($sign, $d1);
return ($ans_pt, $ans_val, 0);
} else {
# Demote to PT0
$ans_pt = 0;
$ans_val = &f_mul($sign, &f_pow10($d1));
return ($ans_pt, $ans_val, 0);
}
} elsif (($xpt == 2) && ($ypt < 2)) {
# X is PT2 and Y is PT0 or PT1. The answer is always equal to the larger, which is x.
$ans_pt = $xpt;
$ans_val = &f_mul($sign, $xv);
return ($ans_pt, $ans_val, 0);
} elsif ($xpt == 2) {
# Both are PT2's. In this case, the product is equal to the larger
# except in cases like {2, 312.0} * {2, 308.0} where the two
# values are within +- scale or so, where we have to do the
# log(sum(exp(diff))) thing.
# inputs: {2, 312.0} * {2, 308.0}
# logs: 312.0, 308.0
# subtract: -4.0
# antilog: 0.0001 = 1.0e-4
# add to 1: 1.0001
# log: 0.000043
# add: 312.000043
# final answer: {2, 312.000043}
#
$d1 = &f_sub($xv, $yv);
if (&f_lt($d1, ($curscale+3))) {
# x is a little bigger
$ans_pt = 2;
$ans_val = &f_pow10(&f_sub($yv, $xv));
$ans_val = &f_add($g_1, $ans_val);
$ans_val = &f_add($xv, &f_log10($ans_val));
$ans_val = &f_mul($sign, $ans_val);
return ($ans_pt, $ans_val, 0);
} else {
# x is way bigger
$ans_pt = 2;
$ans_val = &f_mul($sign, $xv);
return ($ans_pt, $ans_val, 0);
}
} elsif ($xpt > 2) {
# For anything with X bigger than a PT2, the product is the
# larger of the two.
$ans_pt = $xpt;
$ans_val = &f_mul($sign, $xv);
return ($ans_pt, $ans_val, 0);
}
# default fall-through
return (0, $g_0, 0);
} # End of pt.mul
sub pt_div
{
my($an_pt, $an_val, $an_unc, $ad_pt, $ad_val, $ad_unc) = @_;
my($n_pt, $n_val, $n_unc, $d_pt, $d_val, $d_unc);
my($ans_pt, $ans_val, $ans_unc);
my($sign, $p1);
$n_pt = $an_pt; $d_pt = $ad_pt;
$n_val = $an_val; $d_val = $ad_val;
$n_unc = $an_unc; $d_unc = $ad_unc;
if (&isnan($n_val) || &isnan($d_val)) {
&dbg1("pt_div(nan/nan) : nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Handle divide by zero case first, it's easy
if ($d_val == 0) {
&dbg1("pt_div(K/0) : nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Enforce positive arguments and remember sign.
$sign = $g_1;
if (&f_lt($n_val, $g_0)) {
$sign = &f_neg($sign);
$n_val = &f_neg($n_val);
}
if (&f_lt($d_val, $g_0)) {
$sign = &f_neg($sign);
$d_val = &f_neg($d_val);
}
# Deal with infinities.
if (&isinf($n_val)) {
if (&f_gt($sign, $g_0)) {
&dbg1("pt_div(inf/K) : inf", 32);
return ($g_pt_inf, $g_inf, 0);
} else {
&dbg1("pt_div(inf/-K) : ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
}
}
if (($n_pt == 0) && ($d_pt == 0)) {
# Simple case: both are plain numbers. Divide and check
# for promote (which happens when dividing 1e200 by 1e-200,
# for example)
$p1 = &f_div($n_val, $d_val);
if (&f_ge($p1, $g_ovr1)) {
# Overflowed range of pt0: promote to pt1
$p1 = &f_sub(&f_log10($n_val), &f_log10($d_val));
$ans_pt = 1;
$ans_val = &f_mul($sign, $p1);
&dbg1("pt_div a got: " . $ans_val, 1);
return ($ans_pt, $ans_val, 0);
} else {
$ans_pt = 0;
$ans_val = &f_mul($sign, $p1);
&dbg1("pt_div b got: " . $ans_val . " , d_val $d_val", 1);
# Quotient rule: d(u/v) = (v du - u dv) / v^2
# We add instead of subtracting here, because (unlike a derivative)
# the uncertainty increases just as much when the numerator and
# denominator both increase, as it would if one increases while the other
# decreases.
# Use of the abs() function is not needed because $n_val and $d_val
# were made positive above.
$ans_unc = &f64_div($d_val * $n_unc + $n_val * $d_unc,
&f_mul($d_val, $d_val));
# &f_div(
# &f_add(&f_mul($d_val, $n_unc), &f_mul($n_val, $d_unc)),
# &f_mul($d_val, $d_val));
return ($ans_pt, $ans_val, $ans_unc);
}
} elsif (($n_pt == 1) && ($d_pt == 0)) {
# Dividing a PT1 by a PT0. Need to check for demote, but promote
# isn't possible.
$p1 = &f_sub($n_val, &f_log10($d_val));
if (&f_lt($p1, $g_log10_ovr1)) {
$ans_pt = 0;
$ans_val = &f_mul($sign, &f_pow10($p1));
return ($ans_pt, $ans_val, 0);
} else {
$ans_pt = 1;
$ans_val = &f_mul($sign, $p1);
return ($ans_pt, $ans_val, 0);
}
} elsif (($n_pt == 0) && ($d_pt == 1)) {
# Dividing a PT0 by a PT1. If the PT0 is something like 1e200
# and the PT1 is say 1e305, the answer is 1e-105 which is a
# valid nonzero PT0. So, we have to compute an answer.
$p1 = &f_sub(&f_log10($n_val), $d_val);
$ans_pt = 0;
$ans_val = &f_mul($sign, &f_pow10($p1));
return ($ans_pt, $ans_val, 0);
} elsif (($n_pt == 1) && ($d_pt == 1)) {
# Both arguments are PT1. Subtract the logs and check for demote.
$p1 = &f_sub($n_val, $d_val);
if (&f_lt($p1, $g_log10_ovr1)) {
# We can demote. The new value should be 10 to the power of the
# difference p1. We handle two common integer cases to avoid doing an
# inexact pow10. %%% I'd rather do this inside f_pow10 but I want to
# think about it first.
$ans_pt = 0;
if (&simple_int($p1, $curscale)) {
if ($p1 < $curscale) {
$p1 = '1' . ('0' x $p1);
} else {
$p1 = '1e' . int($p1);
}
} else {
$p1 = &f_pow10($p1);
}
} else {
$ans_pt = 1;
}
$ans_val = &f_mul($sign, $p1);
return ($ans_pt, $ans_val, 0);
} elsif ($n_pt < $d_pt) {
# In all the other cases, if the arguments are of different
# PT types the answer is simple.
return (0, $g_0, 0);
} elsif ($n_pt > $d_pt) {
# In these cases the answer is the numerator.
# %%% not so - consider 10^10^301 / 10^10^299, should be 10^(9.9x10^300)
$ans_pt = $n_pt;
$ans_val = &f_mul($sign, $n_val);
return ($ans_pt, $ans_val, 0);
} else {
# %%% Here's where we need to handle (10^10^1000.2) / (10^10^1000.1)
# Numerator and denominator of same PT type, PT2 or greater.
# Ratio uncomputably differs from:
# 0 if D is of greater magnitude,
# N if N is of greater magnitude
# 1.0 or -1.0 if they are equal.
if (&f_lt($n_val, $d_val)) {
return (0, $g_0, 0);
} elsif (&f_eq($n_val, $d_val)) {
$ans_pt = 0;
$ans_val = $sign;
return ($ans_pt, $ans_val, 0);
} else {
$ans_pt = $n_pt;
$ans_val = &f_mul($sign, $n_val);
return ($ans_pt, $ans_val, 0);
}
}
} # End of pt.div
sub pt_mod
{
my($an_pt, $an_val, $an_unc, $ad_pt, $ad_val, $ad_unc) = @_;
my($n_pt, $n_val, $n_unc, $d_pt, $d_val, $d_unc);
my($sign, $p1, $p2, $p3, $p4, $p5, $p6);
&dbg1("pt.mod |$an_pt|$an_val|$an_unc| % |$ad_pt|$ad_val|$ad_unc|", 32);
$n_pt = $an_pt; $d_pt = $ad_pt;
$n_val = $an_val; $d_val = $ad_val;
$n_unc = $an_unc; $d_unc = $ad_unc;
if (&isnan($n_val) || &isnan($d_val)) {
&dbg1("pt.mod(nan/nan) : nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Handle divide by zero case first, it's easy
if ($d_val == 0) {
&dbg1("pt.mod(K/0) : nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Enforce positive arguments and remember sign.
$sign = $g_1;
if (&f_lt($n_val, $g_0)) {
$sign = &f_neg($sign);
$n_val = &f_neg($n_val);
}
if (&f_lt($d_val, $g_0)) {
&dbg1("pt.mod(n%-K) : 0", 32);
return (0, 0, 0);
}
# Deal with infinities.
if (&isinf($n_val)) {
&dbg1("pt.mod(inf%d) : 0", 32);
return (0, 0, 0);
} elsif (&isinf($d_val)) {
&dbg1("pt.mod(n%inf) : 0", 32);
return ($n_pt, $n_val, $n_unc);
}
if ($n_pt > 0) {
&dbg1("pt.mod(pt1%d) : 0", 32);
return (0, 0, 0);
}
if ($d_pt > 0) {
&dbg1("pt.mod(n%pt1) : 0", 32);
return (0, 0, 0);
}
if (!(&simple_int($n_val, $curscale))) {
&dbg1("pt.mod(nonint%d) : 0", 32);
return (0, 0, 0);
}
if (!(&simple_int($d_val, $curscale))) {
&dbg1("pt.mod(n%nonint) : 0", 32);
return (0, 0, 0);
}
$p1 = &f_div($n_val, $d_val);
&dbg1("pt.mod p1 = $p1", 32);
if ($p1 =~ m/^(\d+)/) {
$p2 = $1;
&dbg1("pt.mod p2a = $p2", 32);
} elsif ($p1 =~ m/^\.\d+/) {
$p2 = 0;
&dbg1("pt.mod p2b = $p2", 32);
} elsif ($p1 =~ m/^\+(\d+)\.\d+E0$/) {
$p2 = $1;
&dbg1("pt.mod p2c = $p2", 32);
} else {
&dbg1("pt.mod(n/d) got '$p1' : 0", 32);
return (0, 0, 0);
}
$p3 = &f_mul($p2, $d_val);
&dbg1("pt.mod p3 = $p3", 32);
if (!(&simple_int($p3, $curscale))) {
&dbg1("pt.mod(n/d) n/d*d not int : 0", 32);
return (0, 0, 0);
}
$p4 = &f_sub($n_val, $p3);
&dbg1("pt.mod p4 = $p4", 32);
if (&simple_int($p4, $curscale)) {
&dbg1("pt.mod(n/d) easy case : $p4", 32);
$p6 = &f_mul($sign, $p4);
return (0, $p6, 0);
}
$p5 = &f_add($p4, 0.5);
&dbg1("pt.mod p5 = $p5", 32);
if ($p5 =~ m/^\+?(\d)\.\d*E-1/) {
# Common case when we're using fbc_* routines, if the mod answer should
# be zero then at this point we have +4.999E-1 or +5.E-1
$p5 = 0;
} elsif ($p5 =~ m/^\+?(\d+)\.\d*$/) {
# Here we're hoping it is something like 142.49999
$p5 = $1;
} else {
&dbg1("pt.mod(n/d) n-(n/d*d) not int : 0", 32);
return (0, 0, 0);
}
$p6 = &f_mul($sign, $p5);
&dbg1("pt.mod -> $p6", 32);
return(0, $p6, 0);
} # End of pt.mod
$unused1 = q|
# The Palm version of Hypercalc had a percent key, which multiplied an
# argument by a given percentage. For example, with inputs (12,50) it
# returns 6 because that's 50% of 12.
pt_real pt_percent(pt_real p, pt_real x)
{
my($ans_pt, $ans_val);
pt_real ans;
$ans_pt = 0;
$ans_val = 100.0;
ans = &pt_div(p, ans);
ans = &pt_mul(ans, x, 0);
return ($ans_pt, $ans_val);
}
|;
sub pt_exp
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_pt, $ans_val, $ans_unc);
my($l1);
# Handle infinities first
if (&ispinf($x_val)) {
&dbg1("pt_exp(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isninf($x_val)) {
&dbg1("pt_exp(ninf): 0", 32);
return (0, $g_0, 0);
} elsif (&isnan($x_val)) {
&dbg1("pt_exp(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if ($x_pt == 0) {
# There are three cases; the third (involving promotion to PT2)
# only happens when we're called from pt.gamma with an argument
# like {0, 7e302}, which is about as high as it will ever get.
if (&f_lt($x_val, $g_ln_ovr1)) {
# Simple case: answer is within PT0 range
$ans_pt = 0;
$ans_val = &f_exp($x_val);
# d(e^x) = e^x dx
$ans_unc = $ans_val * $x_unc; # &f_mul($ans_val, $x_unc);
return ($ans_pt, $ans_val, $ans_unc);
} elsif (&f_lt(&f_div($x_val, $g_ln_10), $g_ovr1)) {
# Promote to PT1
$ans_pt = 1;
$ans_val = &f_div($x_val, $g_ln_10);
return ($ans_pt, $ans_val, 0);
} else {
# Promote to PT2
$ans_pt = 2;
$ans_val = &f_log10(&f_div($x_val, $g_ln_10));
return ($ans_pt, $ans_val, 0);
}
}
# Now handle the rest of the negative cases
if (f_lt($x_val, $g_0)) {
return (0, $g_0, 0);
}
# Now handle positive PT1
if ($x_pt == 1) {
# e^(10^X) = 10^(log10(e) * 10^X) = 10^(10^(log10(log10(e)) + X ))
# X is from:
# 10^300 -- answer is 10^(10^(300+logloge)) = 10^(10^299.637) = 10^(4.342e299)
# to
# 10^(1e300) -- answer is 10^(10^(10^300+logloge)).
# Since log10(log10(e)) is negative, we sometimes have to produce
# a PT1 answer.
$l1 = &f_add($x_val, $g_log10_log10_e);
if (&f_lt($l1, $g_log10_ovr1)) {
$ans_pt = 1;
$ans_val = &f_mul($g_log10_e, &f_pow10($x_val));
return ($ans_pt, $ans_val, 0);
} else {
return (2, $l1, 0);
}
} else {
# X is PT2 or greater. e^X is uncomputably larger than 10^x, so we
# just promote to the next higher PT with the same val.
return ($x_pt + 1, $x_val, 0);
}
# default fall-through (never reached)
return (0, $g_0, 0);
} # End of pt.exp
sub pt_ln
{
my($x_pt, $x_val, $x_unc) = @_;
my($l1);
my($ans_pt, $ans_val, $ans_unc);
# Handle 0 and infinities first
if (&isnan($x_val)) {
&dbg1("pt_ln(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
} elsif (&f_eq($x_val, $g_0)) {
&dbg1("pt_ln(0): ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
} elsif (&ispinf($x_val)) {
&dbg1("pt_ln(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isninf($x_val)) {
&dbg1("pt_ln(ninf): 0", 32);
return (0, $g_0, 0);
}
if (&f_lt($x_val, $g_0)) {
# Real component of log of negative number is the log of the magnitude
$x_val = &f_neg($x_val);
}
if ($x_pt == 0) {
# Call normal ln function
$ans_pt = 0;
$ans_val = &f_ln($x_val);
# d(ln(u)) = du/u
# We made x_val non-negative, but need to use f_div because it still
# might be zero
$ans_unc = &f64_div($x_unc, $x_val);
return ($ans_pt, $ans_val, $ans_unc);
} elsif ($x_pt == 1) {
# ln(x) = log10(x) / log10(e) = $x_val / log10(e) = $x_val * $g_ln_10
$l1 = &f_mul($x_val, $g_ln_10);
if (&f_lt($l1, $g_ovr1)) {
return (0, $l1, 0);
} else {
$ans_pt = 1;
$ans_val = &f_log10($l1);
return ($ans_pt, $ans_val, 0);
}
} elsif ($x_pt == 2) {
# Have to subtract log10(log10(e))
$l1 = &f_sub($x_val, $g_log10_log10_e);
if (&f_lt($l1, $g_log10_ovr1)) {
$ans_pt = 0;
$ans_val = &f_pow10($l1);
return ($ans_pt, $ans_val, 0);
} else {
return (1, $l1, 0);
}
} else {
$ans_pt = $x_pt - 1;
$ans_val = $x_val;
return ($ans_pt, $ans_val, 0);
}
} # End of pt.ln
# A routine that breaks out all the cases explicitly would be a little
# more accurate and a little faster -- but it's not enough to be of
# immediate concern for the usability of the calculator.
sub pt_pow
{
my($ab_pt, $ab_val, $ab_unc, $ae_pt, $ae_val, $ae_unc) = @_;
my($b_pt, $b_val, $b_unc, $e_pt, $e_val, $e_unc);
my($p1, $scal7);
my($ans_pt, $ans_val);
&dbg1("pt_pow( $ab_pt:$ab_val($ab_unc) ^ ($ae_pt:$ae_val($ae_unc)) )", 32);
$b_pt = $ab_pt; $e_pt = $ae_pt;
$b_val = $ab_val; $e_val = $ae_val;
$b_unc = $ab_unc; $e_unc = $ae_unc;
if (&isnan($b_val) || &isnan($e_val)) {
&dbg1("pt_pow(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Handle 0's, 1's, and infinities first. Some of these are handled
# quickly as a speed optimization, and others because they're special
# cases.
# Here is a table illustrating the special cases; a total of 19
# special cases are handled. Any entry that says "." is computed
# the normal way, but the others are special cases. For the "C*i"
# cases we actually compute the cosine of the exponent to determine
# the sign of infinity that should result. The rows/columns labeled
# -5, -0.5, 0.5 and 5 represent any typical numbers (there is nothing
# special about -5 -0.5, 0.5 or 5)
# +--------------EXPONENT---------------
# pow |ninf -5 -1 -0.5 0 0.5 1 5 inf
# +-----+-------------------------------------
# | inf| 0 0 0 0 1 inf inf inf inf
# | 5| 0 . . . 1 . 5 . inf
# B 1| 1 1 1 1 1 1 1 1 1
# A 0.5| inf . . . 1 . 0.5 . 0
# S 0| nan nan nan nan 1 0 0 0 0
# E -0.5| inf . . . 1 . -0.5 . 0
# | -1| 1 . . . 1 . -1 . 1
# | -5| 0 . . . 1 . -5 . inf
# | ninf| 0 0 0 0 1 C*i ninf C*i inf
if (&f_eq($e_val, $g_0)) {
return (0, 1, 0);
} elsif (&f_eq($b_val, $g_1)) {
return (0, 1, 0);
} elsif (&f_eq($e_val, $g_1)) {
return ($ab_pt, $ab_val, $ab_unc);
} elsif (&isinf($b_val)) {
# Don't have to check exponent == 0 because it's already been checked
if (&f_gt($e_val, $g_0)) {
&dbg1("pt_pow(inf^X): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} else {
&dbg1("pt_pow(inf^-X): 0", 32);
return (0, $g_0, 0);
}
} elsif (&f_eq($b_val, $g_0)) {
# Don't have to check exponent == 0 because it's already been checked
if (&f_gt($e_val, $g_0)) {
return (0, $g_0, 0);
} else {
&dbg1("pt_pow(0^-X): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
}
$p1 = &f_mul($b_val, $b_val);
if (&ispinf($e_val)) {
if (&f_gt($p1, $g_1)) {
&dbg1("pt_pow(X^inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&f_eq($p1, $g_1)) {
&dbg1("pt_pow(1^inf): 1", 32);
return (0, $g_1, 0);
} else {
&dbg1("pt_pow((x<1)^inf): 0", 32);
return (0, $g_0, 0);
}
} elsif (&isninf($e_val)) {
if (&f_gt($p1, $g_1)) {
&dbg1("pt_pow(X^ninf): 0", 32);
return (0, $g_0, 0);
} elsif (&f_eq($p1, $g_1)) {
&dbg1("pt_pow(1^ninf): 1", 32);
return (0, $g_1, 0);
} else {
&dbg1("pt_pow((x<1)^ninf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
}
} elsif (&isninf($b_val)) {
if (&f_lt($e_val, $g_0)) {
&dbg1("pt_pow(ninf^-x): 0", 32);
return (0, $g_0, 0);
} elsif (&f_eq($e_val, $g_1)) {
&dbg1("pt_pow(ninf^1): ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
} elsif (&ispinf($e_val)) {
&dbg1("pt_pow(ninf^inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
}
$p1 = &f_cos(&f_mul($e_val, $g_pi));
if (&f_gt($p1, $g_0)) {
&dbg1("pt_pow(ninf^X): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&f_eq($p1, $g_0)) {
&dbg1("pt_pow(ninf^X): 0", 32);
return (0, $g_0, 0);
} else {
&dbg1("pt_pow(ninf^X): ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
}
}
# extract sign and scale information
if (&f_lt($b_val, $g_0)) {
# raising a negative base to a power
if ($e_pt == 0) {
# Compute the real component of the angle
$scal7 = &f_cos(&f_mul($e_val, $g_pi));
} else {
# It'll round off to a multiple of 2pi, so just scale
# by 1.
$scal7 = $g_1;
}
$b_val = &f_neg($b_val);
} else {
$scal7 = $g_1;
}
# Now $b_val is positive, so it's okay to take the log of it if we need to.
if (($b_pt == 0) && ($e_pt == 0)) {
# Since this is the most common case, we will try to compute it using
# native operations
# To avoid overflow we should first compute the log of the answer
$p1 = &f_mul(&f_log10($b_val), $e_val);
&dbg1("mul(log10($b_val), $e_val) : $p1", 32);
if (&f_lt($p1, $g_log10_ovr1)) {
&dbg1(" -> not overflow", 32);
# It's safe to compute it for real
$p1 = f_pow($b_val, $e_val);
$ans_pt = 0;
$ans_val = &f_mul($scal7, $p1);
# d(b^p) = b^p (ln(b) dp + p db/b)
# b_val is positive but e_val might still be negative
$ans_unc = $p1 * (log($b_val) * $e_unc
+ abs($e_val) * &f_div($b_unc, $b_val));
# &f_mul($p1,
# &f_add(
# &f_mul(&f_ln($b_val), $e_unc),
# &f_mul($e_val, &f_div($b_unc, $b_val))
# )
# );
return ($ans_pt, $ans_val, $ans_unc);
}
&dbg1(" -> oops, overflow", 32);
# Overflowed on direct computation, so compute it by logs instead.
# p1 is still f_mul(f_log10(b_val), e_val)
if (&f_lt($p1, $g_ovr1)) {
$ans_pt = 1; # %%% bug here: if scale is small it can bring answer back into PT0 range
if (&f_eq($scal7, $g_1)) {
$ans_val = $p1;
} elsif (&f_gt($scal7, $g_0)) {
$ans_val = &f_add($p1, &f_log10($scal7));
} else {
$ans_val = &f_neg(&f_add($p1, &f_log10(&f_neg($scal7))));
}
return ($ans_pt, $ans_val, 0);
} else {
# This case happens for e.g. {0, 1e10} to the power of {0, 5e299}
# The answer is {1, 5e300} which must be promoted to {2, 300.698}
# The contribution of scale is uncomputable, except for its sign.
$ans_pt = 2;
$p1 = &f_log10($p1);
if (&f_gt($scal7, $g_0)) {
$ans_val = $p1;
} else {
$ans_val = &f_neg($p1);
}
return ($ans_pt, $ans_val, 0);
}
}
# For the remaining cases, it seems that we should be able to
# just use powers and logs.
($ans_pt, $ans_val, $ans_unc) = &pt_pow10(
&pt_mul(&pt_log10($b_pt, $b_val, $b_unc),
$e_pt, $e_val, $e_unc));
# Factor in the scale.
# So I can take -1e2000 to the power of 0.25 and it will get 7.07e500
$b_pt = 0;
$b_val = $scal7;
($ans_pt, $ans_val, $ans_unc) = &pt_mul($ans_pt, $ans_val, $ans_unc,
$b_pt, $b_val, 0);
return ($ans_pt, $ans_val, $ans_unc);
} # End of pt.pow
# There is a lot of similarity with the pt_pow routine because pt.root
# is just dividing the log by the exponent rather than multiplying.
#
# r-th root of b = pow(b, 1.0 / r)
# = exp(ln(b) / r)
# = exp(exp(ln(ln(b)) - ln(r)))
sub pt_root
{
my($ab_pt, $ab_val, $ab_unc, $ar_pt, $ar_val, $ar_unc) = @_;
my($ans_pt, $ans_val, $ans_unc);
my($b_pt, $b_val, $b_unc, $r_pt, $r_val, $r_unc);
my($p1, $scal8);
($b_pt, $b_val, $b_unc, $r_pt, $r_val, $r_unc)
= ($ab_pt, $ab_val, $ab_unc, $ar_pt, $ar_val, $ar_unc);
if (&isnan($b_val) || &isnan($r_val)) {
&dbg1("pt_root(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Deal with 0th root, which is like raising to the power of infinity
if (&f_eq($r_val, $g_0)) {
if (&f_eq($b_val, $g_0)) {
# 0th root of 0 is 0
&dbg1("pt_root(0, 0): 0", 32);
return (0, $g_0, 0);
} elsif (&f_gt($b_val, $g_n1) && &f_lt($b_val, $g_1)) {
# 0th root of a number less than 1
&dbg1("pt_root((x<1), 0): 1", 32);
return (0, $g_1, 0);
} else {
&dbg1("pt_root((x>1), 0): inf", 32);
return ($g_pt_inf, $g_inf, 0);
}
}
# extract sign and scale information
if (&f_lt($b_val, $g_0)) {
if ($r_pt == 0) {
# We can calculate sin
$scal8 = &f_cos(&f_div($g_pi, $r_val));
} else {
# We're raising to a power that's very very close to 0,
# so close that the cosine is essentially 1.0
$scal8 = $g_1;
}
$b_val = &f_neg($b_val);
} else {
$scal8 = $g_1;
}
# Now $b_val is non-negative, so it's okay to take the log of it if we
# need to.
if (($b_pt == 0) && ($r_pt == 0)) {
# Compute the root directly. This will overflow if we're taking say
# the 0.1th root of 1e100, which is like taking 1e100 to the 10th
# power.
$p1 = &f_root($b_val, $r_val);
if (&f_lt($p1, $g_ovr1)) {
$ans_pt = 0;
$ans_val = &f_mul($scal8, $p1);
# d(b^(1/r)) = b^(1/r) (db / b r - ln(b) dr / r^2)
$ans_unc = $p1 * (abs($b_unc / ($b_val * $r_val))
+ abs(&f64_ln($b_val) * $r_unc / ($r_val * $r_val)));
return ($ans_pt, $ans_val, $ans_unc);
}
# Overflowed on direct computation, so compute it by logs instead.
$p1 = &f_div(&f_log10($b_val), $r_val);
if (&f_lt($p1, $g_ovr1)) {
# %%% bug here: if scale is small it can bring answer back into PT0 range
$ans_pt = 1;
if (&f_eq($scal8, 1.0)) {
$ans_val = $p1;
} elsif (&f_gt($scal8, $g_0)) {
$ans_val = &f_add($p1, &f_log10($scal8));
} else {
$ans_val = &f_neg(&f_add($p1, &f_log10(&f_neg($scal8))));
}
return ($ans_pt, $ans_val, 0);
} else {
# This case happens when taking e.g. the {0, 2e-300} root of {0, 1e10},
# which is equivalent to raising {0, 1e10} to the power of {0, 5e299}
# The answer is {1, 5e300} which must be promoted to {2, 300.698}
# The contribution of scale is uncomputable, except for its sign.
$ans_pt = 2;
$p1 = &f_log10($p1);
if (&f_gt($scal8, $g_0)) {
$ans_val = $p1;
} else {
$ans_val = &f_neg($p1);
}
return ($ans_pt, $ans_val, 0);
}
}
# For the remaining cases, it seems that we should be able to
# just use powers and logs.
($ans_pt, $ans_val, $ans_unc) = &pt_pow10(
&pt_div(&pt_log10($b_pt, $b_val, 0),
$r_pt, $r_val, 0));
# Factor in the scale.
# So I can take the 4th root of -1e2000 and it will get 7.07e500
($ans_pt, $ans_val, $ans_unc) = &pt_mul($ans_pt, $ans_val, $ans_unc,
0, $scal8, 0);
return ($ans_pt, $ans_val, $ans_unc);
} # end of pt.root
# log base b of n = log(n) / log(b)
# == pow10(log(log(n)) - log(log(b)))
#
# By the way, this function is a better match for being the analogue of the
# divide function because of the symmetry of the formula for computing it.
sub pt_log_n
{
my($n_pt, $n_val, $n_unc, $b_pt, $b_val, $b_unc) = @_;
my($ans_pt, $ans_val, $ans_unc, $bl);
# Handle 0 and infinities first
if (&f_eq($n_val, $g_0)) {
&dbg1("pt_log_n(0): ninf", 32);
return ($g_pt_inf, $g_ninf, 0);
} elsif (&ispinf($n_val)) {
&dbg1("pt_log_n(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isninf($n_val)) {
&dbg1("pt_log_n(ninf): 0", 32);
return (0, $g_0, 0);
} elsif (&isnan($n_val) || &isnan($b_val)) {
&dbg1("pt_log_n(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Real component of log of a negative number is the log of the magnitude
if (&f_lt($n_val, $g_0)) {
$n_val = &f_neg($n_val);
}
if ($n_pt == 0) {
if ($b_pt == 0) {
# Both arguments are PT0. The biggest the answer can possibly be is
# somewhere around 1.0e19 (which happens when you take the log of 1e300
# to base 1.0000...001), definitely well within PT0 bounds.
$ans_pt = 0;
$ans_val = &f_log_n($n_val, $b_val);
# diff(ln(n)/ln(b)) = (b ln(b) dn - n ln(n) db) / n b ln(b)^2
$bl = &f64_ln($b_val);
$ans_unc = &f64_div(abs($b_val * $n_unc * $bl)
+ abs($n_val * $b_unc * &f64_ln($n_val))
, abs($b_val * $n_val * $bl * $bl));
return ($ans_pt, $ans_val, $ans_unc);
}
}
# Otherwise we just defer to the primitives.
# %%% This needs to be changed; right now it only works if b is greater
# than 0
return &pt_div(&pt_log10($n_pt, $n_val, $n_unc),
&pt_log10($b_pt, $b_val, $b_unc));
} # End of pt.log_n
sub intgam
{
my($n) = @_;
my($i, $rv);
if (!($n =~ m/^\d+$/)) {
return 0;
}
if (($n <= 0) || ($n >= 168)) {
return 0;
}
if ($igv[$n] ne '') {
return $igv[$n];
}
$igv[1] = 1;
for ($i=2; $i<=$n; $i++) {
if ($igv[$i] eq '') {
$igv[$i] = &f_mul($i-1, $igv[$i-1]);
}
}
return $igv[$n];
} # End of int.gam
# t = n - 1
# l = 1/2 ln(2 pi) + (t + 1/2) ln(t) - t + 1/(12 t) - 1/(360 t^3) + ...
# gamma(x) = e^l
#
# %%% Accuracy is not good enough for scale>20. See notes in header.
sub pt_gamma
{
my($n_pt, $n_val, $n_unc) = @_;
my($ans_pt, $ans_val, $ans_unc, $t, $l, $l2, $adj, $n2, $np, $u1);
&dbg1("pt.gamma($n_pt|$n_val|$u_unc)", 32);
# Handle infinities first
if (&ispinf($n_val)) {
&dbg1("pt.gamma(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&isninf($n_val)) {
&dbg1("pt.gamma(ninf): 0", 32);
return (0, $g_0, 0);
} elsif (&isnan($n_val)) {
&dbg1("pt.gamma(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# Now handle negatives
if (&f_lt($n_val, $g_0)) {
if ($n_pt > 0) {
&dbg1("pt.gamma(-X): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} elsif (&simple_nint($n_val, $curscale)) {
&dbg1("pt.gamma(-int): inf", 32);
return ($g_pt_inf, $g_inf, 0);
} else {
$ans_pt = 0;
$ans_val = &f_gamma($n_val);
# The uncertainty calculation is a finite differential rather than
# an infinitesimal derivative, because it's just too much of a pain
# to implement the true derivative of gamma
if (&f_gt($n_unc, $g_0)) {
$ans_unc = abs($ans_val - &f_gamma($n_val + $n_unc));
} else {
$ans_unc = $g_0;
}
return ($ans_pt, $ans_val, $ans_unc);
}
}
if ($n_pt == 0) {
# # Handle integer argument
# if (&simple_int($n_val, $curscale) && ($n_val >= 0) && ($n_val < 167)) {
# $ans_pt = 0;
# $ans_val = &intgam($n_val);
# &dbg1("pt.gamma($n_val): intgam -> $ans_val", 32);
# $ans_unc = $g_0;
# return ($ans_pt, $ans_val, $ans_unc);
# }
# NOTE: All the following commented-out debugging prints are computing
# the same things that are now in the set.scale code inside hi.init.
if (0) {
my($est1, $nvm1);
$nvm1 = &f_sub($n_val, "1.0");
# Calculate size of N^-25 term using this value of N
print "curscale = $curscale; n_val-1 = $nvm1, gammalim = $g_gammalim\n";
$est1 = &f_div("657931.0", &f_mul("300.0",
&f_pow($nvm1, "25.0")));
print "residual at this arg $nvm1: est1 = $est1\n";
# Calculate size of N^-25 term using $g_gammalim
$est1 = &f_div("657931.0", &f_mul("300.0",
&f_pow($g_gammalim, "25.0")));
$est1 = int(&f_neg(&f_log10($est1)));
print "min digits: $est1\n";
# Same two calculations with the N^-9 term, which tells us what the
# error will be if we use the "shorter Stirling" code below.
#
$est1 = &f_div("43867.0", &f_mul("244188.0", &f_pow($nvm1, "17.0")));
print "8-term residual at this arg $nvm1: $est1\n";
$est1 = &f_div("43867.0", &f_mul("244188.0", &f_pow($g_gammalim, "17.0")));
print "8-term residual at minarg $g_gammalim: $est1\n";
# Same two calculations with the N^-9 term, which tells us what the
# error will be if we use the "shorter Stirling" code below.
#
$est1 = &f_div("1.0", &f_mul("1188.0", &f_pow($nvm1, "9.0")));
print "6-term residual at this arg $nvm1: $est1\n";
$est1 = &f_div("1.0", &f_mul("1188.0", &f_pow($g_gammalim, "9.0")));
print "6-term residual at minarg $g_gammalim: $est1\n";
}
# Dispatch sufficiently small values to f_gamma
if (&f_lt($n_val, $g_gammalim)) {
&dbg1("pt.gamma using f_gamma", 2);
$ans_pt = 0;
$ans_val = &f_gamma($n_val);
# As noted above, finite differential because derivative is too hard
if (&f_gt($n_unc, $g_0)) {
$ans_unc = abs($ans_val - &f_gamma($n_val + $n_unc));
} else {
$ans_unc = $g_0;
}
return ($ans_pt, $ans_val, $ans_unc);
}
&dbg1("pt.gamma using shorter Stirling", 2);
# Start computing terms of log(gamma)
# The "lower" and "upper" show the range of values we are likely to have
# Note that n=167 corresponds to the factorial of 166 and 166!=9e297
# lower upper
$t = &f_sub($n_val, $g_1); # 166.0 1.0e300
$l = $g_05_ln_2pi; # 0.918938533...
&dbg1("pt.gamma: 1 $l", 2);
$l = &f_add($l, &f_mul(&f_add($t, $g_0_5), &f_ln($t)));
# 852.0649... 6.907e302
&dbg1("pt.gamma: 2 $l", 2);
$l = &f_sub($l, $t); # 686.0649... 6.897e302
&dbg1("pt.gamma: 3 $l", 2);
$n2 = &f_mul($t, $t);
$np = $t;
$lm = &f_mul($g_12, $np);
&dbg1("pt.gamma: term 4 lm = $lm", 2);
$adj = &f_div($g_1, $lm);
# 5.02e-4 0.0
$l2 = &f_add($l, $adj);
if (&f_eq($l2, $l)) {
# We're so big that the first 3 terms are enough. So, we're ready to
# call exp! (This happens when n is bigger than about 1.0e7)
return &pt_exp(0, $l, 0);
}
&dbg1("pt.gamma more Stirling", 2);
# No, keep going for a while...
$l = $l2;
&dbg1("pt.gamma: 4 $l", 2);
$np = &f_mul($np, $n2);
$lm = &f_mul($g_360, $np);
&dbg1("pt.gamma: term 5 lm = $lm", 2);
$adj = &f_div($g_1, $lm);
# 6.0e-10 0.0
$l2 = &f_sub($l, $adj);
if (&f_eq($l2, $l)) {
# For values of n greater than about 1e4, this is as
# far as we need to go.
return &pt_exp(0, $l, 0);
}
&dbg1("pt.gamma 3 more Stirling", 2);
# Alright, add in 3 more terms (the most it would ever take)
# and then call exp.
$l = $l2;
&dbg1("pt.gamma: 5 $l", 2);
$np = &f_mul($np, $n2);
$lm = &f_mul($g_1260, $np);
&dbg1("pt.gamma: term 6 lm = $lm", 2);
$lt = &f_div($g_1, $lm);
&dbg1("pt.gamma: term 6 = $lt", 2);
$l = &f_add($l, $lt);
&dbg1("pt.gamma: 6 $l", 2);
$np = &f_mul($np, $n2);
$lm = &f_mul($g_1680, $np);
&dbg1("pt.gamma: term 6 lm = $lm", 2);
$lt = &f_div($g_1, $lm);
&dbg1("pt.gamma: term 7 = $lt", 2);
$l = &f_sub($l, $lt);
&dbg1("pt.gamma: 7 $l", 2);
return &pt_exp(0, $l, 0);
} elsif ($n_pt == 1) {
# N is PT1.
# The t = n+1 is irrelevant now, as are other similar additive
# elements of the formula, so the formula reduces to
# l = n ln(n) - n, gamma(x) = e^l
# We convert this to l = n (ln(n) - 1) because otherwise the
# error in the subtract would dominate.
# There's enough robustness in pt.exp and the other pt_ops
# that we can do the calculations directly.
# lower upper
$ans_pt = $n_pt; # 1.0e300 10^(1.0e300)
$ans_val = $n_val;
($ans_pt, $ans_val, $u1)
= &pt_ln($ans_pt, $ans_val, 0);# 690.775 2.302e300
# Notice: n ln(n) would be 6.90775e302 10^(1.0e300)
# and n ln(n) - n would be 6.89775e302 0.0!
($ans_pt, $ans_val, $u1)
= &pt_sub($ans_pt, $ans_val, 0,
0, $g_1, 0); # 689.775 2.302e300
($ans_pt, $ans_val)
= &pt_mul($n_pt, $n_val, 0,
$ans_pt, $ans_val, 0); # 6.89775e302 10^(1.0e300)
return &pt_exp($ans_pt, $ans_val, 0);
} else {
# N is PT2 or higher.
# As seen in the upper limit case of the PT1 code above,
# for sufficiently large values gamma(n) reduces to pt.exp(n).
return &pt_exp($n_pt, $n_val, 0);
}
} # End of pt.gamma
# sqrt is done differently from pow(x, 0.5) because there is a builtin
# sqrt routine (which is faster) and because sqrt of a negative number
# should have a real component of exactly 0.
sub pt_sqrt
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_pt, $ans_val, $ans_unc, $v1);
if (&isnan($x_val)) {
&dbg1("pt_sqrt(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
# First handle negatives (including negative infinity), 'cause they're easy
# The square root of any negative quantity is imaginary, i.e. the real
# component is zero
if (&f_le($x_val, $g_0)) {
return (0, $g_0, 0);
}
# Handle infinity
if (&ispinf($x_val)) {
&dbg1("pt_sqrt(inf): inf", 32);
return ($g_pt_inf, $g_inf, 0);
}
if ($x_pt == 0) {
# X is PT0, just call normal square root routine.
$ans_val = &f_sqrt($x_val);
# d(sqrt(u)) = du / 2 sqrt(u)
$ans_unc = &f64_div($x_unc, 2.0 * $ans_val);
# &f_div($x_unc, &f_mul($g_2, $ans_val));
return (0, $ans_val, $ans_unc);
} elsif ($x_pt == 1) {
# X is PT1. Divide value by 2 and check for demote.
$v1 = &f_div($x_val, $g_2);
if (&f_lt($v1, $g_log10_ovr1)) {
# demote to PT0
return (0, &f_pow10($v1), 0);
} else {
return (1, $v1, 0);
}
} elsif ($x_pt == 2) {
# X is PT2. Subtract log of 2 and check for demote.
$v1 = &f_sub($x_val, $g_log10_2);
if (&f_lt($v1, $g_log10_ovr1)) {
# demote to PT1
return (1, &f_pow10($v1), 0);
} else {
return (2, $v1, 0);
}
}
# else
# X is PT3 or higher. Square root is uncomputably smaller.
return ($x_pt, $x_val, 0);
} # End of pt_sqrt
# Sine and cosine are very simple.
sub pt_sin
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_unc);
if (&isnan($x_val)) {
&dbg1("pt_sin(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if ($x_pt == 0) {
# X is PT0, just call normal sine routine.
$ans_unc = abs(cos($x_val)) * $x_unc;
return (0, &f_sin($x_val), $ans_unc);
}
# else, X is too big for us to calculate the sine accurately, just return 0
return (0, 0, 0);
}
sub pt_cos
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_unc);
if (&isnan($x_val)) {
&dbg1("pt_cos(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if ($x_pt == 0) {
# X is PT0, just call normal cosine routine.
$ans_unc = abs(sin($x_val)) * $x_unc;
return (0, &f_cos($x_val), $ans_unc);
}
# else, X is too big, just return 0
return (0, 0, 0);
}
sub pt_tan
{
my($x_pt, $x_val, $x_unc) = @_;
my($ans_unc, $v1);
if (&isnan($x_val)) {
&dbg1("pt_tan(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if ($x_pt == 0) {
# X is PT0, just call normal tangent routine.
# d(tan(u)) = sec^2(u) du { where sec(x) = 1/cos(x) }
$v1 = cos($x_val);
$ans_unc = $x_unc / ($v1 * $v1);
return (0, &f_tan($x_val), $ans_unc);
}
# else, X is too big, just return 0
return (0, 0, 0);
} # End of pt.tan
$unused1 = q|
# sinh(x) = (e^x - e^-x) / 2
# outside [-40..40] this reduces to - e^-x / 2 or e^x / 2
# and for PT1 and higher arguments it's just - e^-x (negative argument) or e^x
pt_real pt_sinh(pt_real x)
{
my($x_pt, $x_val) = @_;
return &pt_div(&pt_sub(&pt_exp(x), &pt_exp(&pt_negate(x))), (0, 2));
}
# cosh(x) = (e^x + e^-x) / 2
pt_real pt_cosh(pt_real x)
{
my($x_pt, $x_val) = @_;
return &pt_div(&pt_add(&pt_exp(x), &pt_exp(&pt_negate(x))), (0, 2));
}
# tanh(x) = (e^2x - 1) / (e^2x + 1). Outside the PT0 range it's always
# 1.0 or -1.0
sub pt_tanh
{
my($x_pt, $x_val) = @_;
my($ans_pt, $ans_val);
my($e2x);
# All PT1's and higher and PT0's outside the range [-20..20] have a tanh
# of -1 or 1.
if (&f_lt($x_val, -20.0)) {
return (0, $g_n1);
} elsif (&f_gt($x_val, 20.0)) {
return (0, $g_1);
}
# For the remaining values, we compute the function. The e^2x part is
# used twice in the formula for tanh. The answer is always PT0.
$e2x = &f_exp(&f_mul($g_2, $x_val));
$ans_pt = 0;
$ans_val = ($e2x - 1.0) / ($e2x + 1.0);
return ($ans_pt, $ans_val);
}
# arcsinh(x) = ln(x + sqrt(X^2 + 1))
pt_real pt_arcsinh(pt_real x)
{
my($x_pt, $x_val) = @_;
# Since we'd have to handle all the PT's separately and since arcsinh
# isn't used that much, we just don't bother to implement this function
# with discrete code.
return &pt_ln(&pt_add(x, &pt_sqrt(&pt_add(&pt_mul(x, x), (0, 1)))));
}
# arccosh(x) = ln(x + sqrt(X^2 - 1))
# undefined if argument is less than 1
pt_real pt_arccosh(pt_real x)
{
my($x_pt, $x_val) = @_;
if ($x_val < 1.0) { # catches all negative PTs as well
return (0, 0);
}
# We don't use discrete code for same reason as with arcsinh.
return &pt_ln(&pt_add(x, &pt_sqrt(&pt_sub(&pt_mul(x, x), (0, 1)))));
}
# arctanh(x) = 1/2 ln((-x-1) / (x-1))
# undefined outside [-1, 1]
sub pt_arctanh
{
my($x_pt, $x_val) = @_;
my($ans_pt, $ans_val);
if ($x_val < -1.0) { # automatically catches negative PT1's etc.
return (0, 0);
} elsif ($x_val > 1.0) { # automatically catches PT1's etc.
return (0, 0);
}
# The answer is always a PT0, because the biggest ratio we can have
# is approximately 1::2^54 (i.e. the precision ratio)
$ans_pt = 0;
$ans_val = &f_ln((-$x_val - 1.0) / ($x_val - 1.0)) / 2.0;
return ($ans_pt, $ans_val);
}
pt_real pt_arcsin(pt_real x)
{
my($x_pt, $x_val) = @_;
my($ans_pt, $ans_val);
if ($x_pt > 0) {
return (0, 0);
}
$ans_pt = 0;
$ans_val = f_arcsin($x_val);
return ($ans_pt, $ans_val);
}
pt_real pt_arccos(pt_real x)
{
my($x_pt, $x_val) = @_;
my($ans_pt, $ans_val);
if ($x_pt > 0) {
return (0, 0);
}
$ans_pt = 0;
$ans_val = f_arccos($x_val);
return ($ans_pt, $ans_val);
}
|;
# %%% Arctan doesn't do uncertainty yet
sub pt_arctan
{
my($x_pt, $x_val) = @_;
my($ans_pt, $ans_val);
if (&isnan($x_val)) {
&dbg1("pt_arctan(nan): nan", 32);
return ($g_pt_nan, $g_nan, 0);
}
if (&f_eq($x_val, $g_0)) {
return (0, $g_0);
} elsif (&f_gt($x_pt, $g_0)) {
$ans_pt = 0;
if (&f_gt($x_val, $g_0)) {
$ans_val = &f_div($g_pi, $g_2);
} else {
$ans_val = &f_div($g_pi, $g_n2);
}
return ($ans_pt, $ans_val);
}
$ans_pt = 0;
$ans_val = &f_arctan($x_val);
return ($ans_pt, $ans_val);
} # End of pt.arctan
# Perform rounding of e.g. "234.999992742" or "235.000007232" to "235",
# by adding an amount that is bug enough to make the 999's turn to 000's,
# but small enough so the resulting erroneous digits all get truncated.
# %%% This routine is only used by prnt1(), which only does the truncation
# in one place, so perhaps the code should be moved there.
sub pt_roundup
{
my($x_pt, $x_val) = @_;
my($a_val, $ptsave, $u1);
&dbg1("pt.roundup($x_pt, $x_val)", 32);
if (&isnan($x_val) || &isinf($x_val)) {
&dbg1("pt.roundup(nan): nan", 32);
return ($x_pt, $x_val);
}
if (&simple_int($x_val, $curscale)) {
&dbg1("pt.roundup(integer)", 32);
return ($x_pt, $x_val);
}
if ($doround) {
# Temporarily coerce to PT0
$ptsave = $x_pt;
$x_pt = 0;
# Calculate adjust amount. This is a cheat -- it correctly accounts for
# sign but does not set proper rounding amount for round-to-nearest.
# It should actually be sign(x_val) * 0.5 * 10^(x_exponent - curscale)
$a_val = &f_mul($x_val, $roundprec);
($x_pt, $x_val, $u1) = &pt_add($x_pt, $x_val, 0, 0, $a_val, 0);
# Add the saved PT back in (because the roundup might have increased
# the PT)
$x_pt += $ptsave;
}
return($x_pt, $x_val);
} # End of pt.roundup
# addfunc: new functions can go here
#------------------------------------------------- hypercalc.c
# comment for the Pilot version:
# f8_decomp1 takes a double and returns its sign, mantissa and exponent
# as separate quantities (integer, double, integer respectively). It is
# used for output formatting.
# test patterns
$unused1 = q@
-20 ^ 238.4999999999 -- currently gives 6.2e300 as a PT1. Don't know
what it *should* give, there's a lot of error because the complex
vector is close to the imaginary axis.
0.0001 * (10^151 * 10^151) -- Should give 1e298, as a PT0 not PT1
-- for testing formatting of small numbers in stack display
E ^ 30
E ^ 300
E ^ 3e3
E ^ 3e4
E ^ 3e5
E^(E^30)
E^(E^300)
E^(E^3e3)
E^(E^3e4)
E^(E^3e5)
E^(E^(E^30))
E^(E^(E^300))
E^(E^(E^3e3))
E^(E^(E^3e4))
E^(E^(E^3e5))
E^(E^(E^(E^30)))
(etc...)
A simple "iterative" way to keep generating larger values (for display
testing) is to repeat "%^ln(%)" over and over again.
-1*10^1e200 -- should give -1e2000
4 root % -- should give 7.07e500
1e10 ^ 5e299 -- should give 10^(5e300) as a PT2
20 ^ 238.4999999999 -- should give 1.975e310
-20 ^ 238.49999999999 -- should give 6.2e299, as a PT0 (NOT a PT1)
-- for testing infinities
1 ent 0 / -- should give Infinity
2 ent 1 logxY -- should give Infinity
0 ent 1 logxY -- could give -Infinity or Infinity, I don't care which
1 ent 0 / 1 x -- Infinity times 1 should be infinity
20 ent E299 x eX -- should give approx. 10^(10^300)
lnx -- should give 2e300
e200 eX +- eX -- should give 0
scale = 120
27^(10^1e100) -- should give 2 PT 1.000...000155e100, with about 103 zeros
scale=50
5^2*4 - gives 9.9999999, etc. Should round up and increment exponent
#Non-Intuitive Results When Working With Huge Numbers#
If you spend a while exploring the ranges of huge numbers HyperCalc
can handle, you will probably start noticing some paradoxical results
and might even start to think the calculator is giving wrong answers.
For example, try calculating 27 to the power of *googolplex* (a
googolplex is 10 to the power of *googol* and a googol is
10^{100}). Key in:
27^(10^(10^100))
and it prints
10^(10^(1.00 x 10^100))
so the calculator thinks that
27^(10^(10^100)) = 10^(10^(10^100))
This is clearly wrong -- and it doesn't even seem to be a good
approximation. What's going on?
Let's try calculating the correct answer ourselves. We need to
express the answer as 10 to the power of 10 to the power of something,
because that's the standard format the calculator is using, and we're
going to see how much of an error it made. So, we want to compute
27^{10^{10^{100}}}
as a tower of powers of 10. The first step is express the power of 27
as a power of 10 with a product in the exponent, using the formula
x^{y} = 10^{(log(x) ^{.} y)} :
27^{10^{10^{100}}} = 10^{(logv{10}27 ^{.} 10^{10^{100}})}
logv{10}27 is about 1.43, so we have
27^{10^{10^{100}}} = 10^{1.43 ^{.} 10^{10^{100}}}
Now we have a base of 10 but the exponent still needs work. The next
step is to express the product as a sum in the next-higher exponent;
this time the formula we use is x ^{.} y = 10^{(log(x) + log(y))} :
10^{1.43 ^{.} 10^{10^{100}}} = 10^{10^{(logv{10}1.43 + 10^{100})}}
logv{10}1.43 is about 0.155, and if we add this to 10^{100} we get
10^{10^{(0.155 + 10^{100})}}
= 10^{10^{1000...000.155}}
= 10^{10^{(1.000...000155 ^{.} 10^{100})}}
where there are 94 more 0's in place of each of the "...". So our
final answer is:
27^{10^{10^{100}}} = 10^{10^{(1.000...000155 ^{.} 10^{100})}}
Now that we've expressed the value of 27^*googolplex* precisely enough
to see the calculator's error -- look how small the error is! The
calculator would need to have at least 104 digits of precision to be
able to handle the value "1.000...000155" accurately -- but it only
has 16 digits of accuracy. Those 16 digits are taken up by the 1 and
the first 15 0's -- so when the calculator gets to the step where
we're adding 0.155 to 1.0^{.}10^{100}, it just rounds off the answer
to 1.0^{.}10^{100} -- and produces the answer we saw when we performed
the calculation:
10^(10^(1.00 x 10^{100}))
Even if it did have the precision, it wouldn't have room to print the
whole 104 digits on the screen, so the answer you *see* would look the
same. And no matter how many digits of accuracy we try to give the
calculator, there's always another even bigger number it wouldn't be
able to handle. For example, the calculator would need slightly over a
*million* digits of accuracy to distinguish
27^{10^{10^{1000000}}} from 10^{10^{10^{1000000}}}
and if we just add one more #10# to that tower of exponents, all hope
of avoiding roundoff is lost.
# 2^(2^(2+27)) = 2.048696 * 10^161614248
*/
more tests:
To check each of the special cases in output formatting:
143^143
%!
%!
%!
143!
%!
%!
%!
to check promotion of inlines:
10^23e456
@;
#----------------------------------------------- "p_" routines
#
# each of these routines does the same thing -- converts one
# or two arguments in string PT format to separate PT and VAL values,
# then calls the corresponding "pt_" routine, then converts
# the answer back to string PT format.
#
sub define_ir
{
my($pt, $val, $unc, $lvalue_key) = @_;
$ir_n++;
$ir_pt[$ir_n] = $pt; $ir_val[$ir_n] = $val; $ir_unc[$ir_n] = $unc;
$ir_lvk[$ir_n] = $lvalue_key;
&dbg1("I$ir_n := $pt PT $val($unc)", 2);
return "I$ir_n";
} # End of define.ir
sub define_R_hist
{
my($pt, $val, $unc) = @_;
$h_n++;
$h_pt[$h_n] = $pt; $h_val[$h_n] = $val; $h_unc[$h_n] = $unc;
&dbg1("R$h_n := $pt PT $val($unc)", 2);
return "R$h_n";
}
sub p_add
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
($x_pt, $x_val, $x_unc) = &pt_add($x_pt, $x_val, $x_unc,
$y_pt, $y_val, $y_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.add
sub p_sub
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
# print "p_sub $x $y\n";
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
($x_pt, $x_val, $x_unc) = &pt_sub($x_pt, $x_val, $x_unc,
$y_pt, $y_val, $y_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.sub
# This currently ignores uncertainty; if we wanted to handle uncertainty
# we would have to decide what to do when the result of a comparison is
# inconclusive (when comparing overlapping ranges). Alternatively, <= could
# be defined as in: Bohlender, Gerd, and Ulrich Kulisch. "Definition of
# the Arithmetic Operations and Comparison Relations for an Interval
# Arithmetic Standard." Reliable Computing 15 (2011): 37. which effectively
# considers either of a>=b and a<=b to be true unless one interval is a
# proper subset of the other.
sub p_op_compare
{
my($op, $x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
my($rv, $x_lvk, $y_lvk);
&dbg1("p.op_compare $x $op $y", 32);
($x_pt, $x_val, $x_unc, $x_lvk) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
$rv = &pt_compare($x_pt, $x_val, $y_pt, $y_val);
# print "$x <=> $y == $rv\n";
if ( (($op eq '<') && ($rv < 0))
|| (($op eq '<=') && ($rv <= 0)) # less or equal
|| (($op eq '>') && ($rv > 0))
|| (($op eq '>=') && ($rv >= 0)) # greater or equal
|| (($op eq '<>') && ($rv != 0)) # less or greater, i.e. not equal
|| (($op eq '==') && ($rv == 0)) # equal (official Hypercalc syntax)
|| (($op eq '=') && ($rv == 0)) # equal (BASIC compatibility syntax)
) {
$rv = 1;
} else {
$rv = 0;
}
# Set globals to indicate lvalue and assignment values, to be used by
# eval.2
$poc_isassign = 0;
if ($op eq '=') {
# possible assignment (if left side is an lvalue)
if ($x_lvk ne '') {
# This comparison could be interpreted as an assignment operator.
# We'll remember the lvalue's variable name (key) and the right
# side's pt/val/unc tuple
$poc_isassign = 1;
$poc_lvk = $x_lvk;
$poc_rv_pt = $y_pt;
$poc_rv_val = $y_val;
$poc_rv_unc = $y_unc;
&dbg1("p_oc1 vars[$poc_lvk] := ($poc_rv_pt, $poc_rv_val, $poc_rv_unc)", 2);
}
}
return &define_ir(0, $rv, 0, '');
} # End of p.op_compare
# Boolean AND. This is really a bitwise operation that only works on
# integer arguments, but meant to be used only on results from
# p.op_compare.
sub p_and
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc, $r);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
$r = ($x_val + 0) & ($y_val + 0);
return &define_ir(0, $r, 0, '');
} # End of p.and
# Boolean OR, just as limited as p.and
sub p_or
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc, $r);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
$r = ($x_val + 0) | ($y_val + 0);
return &define_ir(0, $r, 0, '');
} # End of p.or
# Boolean NOT. The same limitations apply as for p.and; also the result
# is always 0 or 1 so it is not useful as a bitwise (1's complement)
# operation.
sub p_not
{
my($x) = @_;
my($x_pt, $x_val, $x_unc, $r);
($x_pt, $x_val, $x_unc) = &splt($x);
$r = ($x_val + 0) ? 0 : 1;
return &define_ir(0, $r, 0, '');
} # End of p.not
# Integer-argument random number function. For compatibility with old BASICS
# if the argument is 0, it gives a real number in the range [0,1). Larger
# arguments give an integer result, for example rnd(6) picks an integer in
# [0..5].
sub p_rnd
{
my($x) = @_;
my($x_pt, $x_val, $x_unc, $r);
($x_pt, $x_val, $x_unc) = &splt($x);
$r = int($x_val + 0);
if ($r <= 0) {
$r = rand(1.0);
} else {
$r = int(rand($r));
}
return &define_ir(0, $r, 0, '');
} # End of p.rnd
# Entry point for modulo
sub p_mod
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
&dbg1("p_mod1 '$x' % '$y'", 32);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
&dbg1("p_mod2 ($x_pt, $x_val, $x_unc) % ($y_pt, $y_val, $y_unc)", 32);
($x_pt, $x_val, $x_unc) = &pt_mod($x_pt, $x_val, $x_unc,
$y_pt, $y_val, $y_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.mod
# Integer (round towards zero) function. Larger arguments are considered to
# be already integers; negatives are treated such that f(-x)=-f(x).
sub p_int
{
my($x) = @_;
my($x_pt, $x_val, $x_unc, $r);
($x_pt, $x_val, $x_unc) = &splt($x);
if ($x_pt > 0) {
return $x;
}
if ($x_val < 0) {
# Perl's int() function actually does what we want, but this code does
# not assume that: it would work if int() were a floor function
$r = &f_neg(&f_int(&f_neg($x_val)));
} else {
$r = &f_int($x_val);
}
return &define_ir(0, $r, 0, '');
} # End of p.int
# Entry point for multiply
sub p_mul
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
&dbg1("p_mul1 '$x' * '$y'", 32);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
&dbg1("p_mul2 ($x_pt, $x_val, $x_unc) * ($y_pt, $y_val, $y_unc)", 32);
($x_pt, $x_val, $x_unc) = &pt_mul($x_pt, $x_val, $x_unc,
$y_pt, $y_val, $y_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.mul
sub p_div
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
($x_pt, $x_val, $x_unc) = &pt_div($x_pt, $x_val, $x_unc,
$y_pt, $y_val, $y_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.div
sub p_negate
{
my($x) = @_;
my($x_pt, $x_val, $x_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($x_pt, $x_val) = &pt_negate($x_pt, $x_val, $x_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.negate
sub p_exp
{
my($x) = @_;
my($x_pt, $x_val, $x_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($x_pt, $x_val, $x_unc) = &pt_exp($x_pt, $x_val, $x_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.exp
sub p_ln
{
my($x) = @_;
my($x_pt, $x_val, $x_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($x_pt, $x_val, $x_unc) = &pt_ln($x_pt, $x_val, $x_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.ln
sub p_arctan
{
my($x) = @_;
my($x_pt, $x_val, $x_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($x_pt, $x_val) = &pt_arctan($x_pt, $x_val);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.arctan
sub p_log10
{
my($x) = @_;
my($x_pt, $x_val, $x_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($x_pt, $x_val, $x_unc) = &pt_log10($x_pt, $x_val, $x_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.log10
sub p_pow
{
my($x, $y) = @_;
my($x_pt, $x_val, $x_unc, $y_pt, $y_val, $y_unc);
($x_pt, $x_val, $x_unc) = &splt($x);
($y_pt, $y_val, $y_unc) = &splt($y);
($x_pt, $x_val, $x_unc) = &pt_pow($x_pt, $x_val, $x_unc,
$y_pt, $y_val, $y_unc);
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.pow
# %%% Accuracy is not good enough for scale>20. See notes in header.
sub p_fact
{
my($x) = @_;
my($x_pt, $x_val, $x_unc);
# Handle integer argument
if (&simple_int($x, $curscale) && ($x >= 0) && ($x < 167)) {
$x_pt = 0;
$x_val = &intgam($x+1);
&dbg1("p.fact($x): intgam -> $x_val", 32);
$x_unc = $g_0;
} else {
($x_pt, $x_val, $x_unc) = &splt($x);
($x_pt, $x_val, $x_unc) = &pt_gamma(&pt_add($x_pt, $x_val, $x_unc,
0, $g_1, 0));
}
return &define_ir($x_pt, $x_val, $x_unc, '');
} # End of p.fact
sub p_sqrt
{
my($n) = @_;
my($n_pt, $n_val, $n_unc);
($n_pt, $n_val, $n_unc) = &splt($n);
($n_pt, $n_val, $n_unc) = &pt_sqrt($n_pt, $n_val, $n_unc);
return &define_ir($n_pt, $n_val, $n_unc, '');
} # End of p.sqrt
sub p_sin
{
my($n) = @_;
my($n_pt, $n_val, $n_unc);
($n_pt, $n_val, $n_unc) = &splt($n);
($n_pt, $n_val, $n_unc) = &pt_sin($n_pt, $n_val, $n_unc);
return &define_ir($n_pt, $n_val, $n_unc, '');
} # End of p.sin
sub p_cos
{
my($n) = @_;
my($n_pt, $n_val, $n_unc);
($n_pt, $n_val, $n_unc) = &splt($n);
($n_pt, $n_val, $n_unc) = &pt_cos($n_pt, $n_val, $n_unc);
return &define_ir($n_pt, $n_val, $n_unc, '');
} # End of p.cos
sub p_tan
{
my($n) = @_;
my($n_pt, $n_val, $n_unc);
($n_pt, $n_val, $n_unc) = &splt($n);
($n_pt, $n_val, $n_unc) = &pt_tan($n_pt, $n_val, $n_unc);
return &define_ir($n_pt, $n_val, $n_unc, '');
} # End of p.tan
sub p_root
{
my($r, $n) = @_;
my($r_pt, $r_val, $r_unc, $n_pt, $n_val, $n_unc);
($r_pt, $r_val, $r_unc) = &splt($r);
($n_pt, $n_val, $n_unc) = &splt($n);
($r_pt, $r_val, $r_unc) = &pt_root($n_pt, $n_val, $n_unc,
$r_pt, $r_val, $r_unc);
return &define_ir($r_pt, $r_val, $r_unc, '');
} # End of p.root
sub p_1d_lookup
{
my($op, $a) = @_;
my($a_pt, $a_val, $a_unc);
my($rv, $lvk);
&dbg1("pal1 $op($a)", 32);
($a_pt, $a_val, $a_unc) = &splt($a);
# &dbg1("pal ($a_pt, $a_val, $a_unc)", 32);
$rv = '0';
if (&pvu_sint($a_pt, $a_val, $a_unc, 14)) {
# Create lvalue key
$lvk = "$op($a_val)";
if ($vars_pt{$lvk} ne "") {
$rv = &define_ir($vars_pt{$lvk}, $vars_val{$lvk},
$vars_unc{$lvk}, $lvk);
} else {
# not defined (yet) so we'll just use 0.
$rv = &define_ir(0, 0, 0, $lvk);
}
} else {
print STDERR ("*** Illegal array index '$a' in $op($a)"
. ( ($BASIC_running) ? " in line $g_BASIC_pc" : "")
. "\n");
}
return $rv;
} # End of p.1d.lookup
sub p_2d_lookup
{
my($op, $a, $b) = @_;
my($a_pt, $a_val, $a_unc, $b_pt, $b_val, $b_unc);
my($rv, $lvk);
&dbg1("pal $op($a,$b)", 32);
($a_pt, $a_val, $a_unc) = &splt($a);
($b_pt, $b_val, $b_unc) = &splt($b);
# &dbg1("pal ($a_pt, $a_val, $a_unc), ($b_pt, $b_val, $b_unc)", 32);
$rv = '0';
if (&pvu_sint($a_pt, $a_val, $a_unc, 14)) {
if (&pvu_sint($b_pt, $b_val, $b_unc, 14)) {
# Create lvalue key
$lvk = "$op($a_val,$b_val)";
if ($vars_pt{$lvk} ne "") {
$rv = &define_ir($vars_pt{$lvk}, $vars_val{$lvk},
$vars_unc{$lvk}, $lvk);
} else {
# not defined (yet) so we'll just use 0.
$rv = &define_ir(0, 0, 0, $lvk);
}
} else {
print STDERR ("*** Illegal array index '$b'"
. ( ($BASIC_running) ? " in line $g_BASIC_pc" : "")
. "\n");
}
} else {
print STDERR ("*** Illegal array index '$a'"
. ( ($BASIC_running) ? " in line $g_BASIC_pc" : "")
. "\n");
}
return $rv;
} # End of p.2d.lookup
sub p_logn
{
my($b, $n) = @_;
my($b_pt, $b_val, $b_unc, $n_pt, $n_val, $n_unc);
($b_pt, $b_val, $b_unc) = &splt($b);
($n_pt, $n_val, $n_unc) = &splt($n);
($n_pt, $n_val, $n_unc) = &pt_log_n($n_pt, $n_val, $n_unc,
$b_pt, $b_val, $b_unc);
return &define_ir($n_pt, $n_val, $n_unc, '');
} # End of p.logn
# addfunc: new functions can go here
<<=~&\|\(\',])($p_arg)([\%x*/])($p_arg)($p_nonarg.*)$|) {
$pre = $1; $a = $2; $op = $3; $b = $4; $post = $5;
&dbg1("ev2l $pre $a $op $b $post", 2);
if (($op eq "*") || ($op eq "x")) {
$e = $pre . &p_mul($a,$b) . $post;
} elsif ($op eq '/') {
$e = $pre . &p_div($a,$b) . $post;
} else {
$e = $pre . &p_mod($a,$b) . $post;
}
$e =~ s| ||g;
$g_possible_assign = 0;
# %%% integer divide (quotient) to be added here
# for bc_xxx see trunc() function in ~/bin/calc-in (I need to set
# scale to 0, then restore it)
} elsif
# addition and subtraction
($e =~ m|^(.*[><=~&\|\(\',])($p_arg)([-+])($p_arg)($p_nonarg.*)$|) {
$pre = $1; $a = $2; $op = $3; $b = $4; $post = $5;
&dbg1("ev2m $pre $a $op $b $post", 2);
if ($op eq "+") {
$e = $pre . &p_add($a,$b) . $post;
} else {
$e = $pre . &p_sub($a,$b) . $post;
}
$e =~ s| ||g;
$g_possible_assign = 0;
# %%% >> and << (bit shifts) would go here. Conveniently they go right
# before the other uses of '<' and '>' in precenence order.
# Their implementation in 300-digit precision is equivalent to multiply
# and quotient with an integer power of 2 as argument; and the right
# argument of a bit-shift operator needs to be converted to
# int(x) == x\1; so I should probably implement quotient n\d and
# integer exponent first.
} elsif
# comparison operators. Note that '=' and '==' both represent equality
($e =~ m/^(.*[~&\|\(\',])($p_arg)(<|>|<=|>=|<>|=|==)($p_arg)($p_nonarg.*)$/) {
$pre = $1; $a = $2; $op = $3; $b = $4; $post = $5;
&dbg1("ev2n $pre $a $op $b $post", 2);
$e = $pre . &p_op_compare($op, $a, $b) . $post;
$e =~ s| ||g;
$g_possible_assign = $poc_isassign;
$g_assign_lvk = $poc_lvk;
$g_assign_pt = $poc_rv_pt;
$g_assign_val = $poc_rv_val;
$g_assign_unc = $poc_rv_unc;
} elsif
# Boolean NOT
($e =~ m/^(.*$p_nonarg)([~!])($p_arg)($p_nonarg.*)$/) {
$pre = $1; $op = $2; $b = $3; $post = $4;
&dbg1("ev2o $pre $op $b $post", 2);
$e = $pre . &p_not($b) . $post;
$e =~ s| ||g;
$g_possible_assign = 0;
} elsif
# Boolean (and sort of bitwise) AND
($e =~ m/^(.*[\|\(\',])($p_arg)([&])($p_arg)($p_nonarg.*)$/) {
$pre = $1; $a = $2; $op = $3; $b = $4; $post = $5;
&dbg1("ev2p $pre $a $op $b $post", 2);
$e = $pre . &p_and($a, $b) . $post;
$e =~ s| ||g;
$g_possible_assign = 0;
} elsif
# Boolean (and sort of bitwise) OR
($e =~ m/^(.*[\(\',])($p_arg)(\|)($p_arg)($p_nonarg.*)$/) {
$pre = $1; $a = $2; $op = $3; $b = $4; $post = $5;
&dbg1("ev2q $pre $a $op $b $post", 2);
$e = $pre . &p_or($a, $b) . $post;
$e =~ s| ||g;
$g_possible_assign = 0;
# %%% all of the boolean operators (bitwise and logical) to be
# added here. Bitwise could be used for logical (as in Visual
# BASIC); but if I decide to make those separate there need to be
# more levels of precedence.
# ~ bitwise complement
# & bitwise AND
# | bitwise OR
# !/~~/NOT logical NOT
# &&/AND logical AND
# ||/OR logical OR
# Also note that 300-digit precision bitwise is nontrivial to implement;
# nothing useful is provided by #bc# so I'd have to do a series of
# operations in base 2^40 or something like that.
} elsif
# then reduce parentheses
($hf) {
&dbg1("ev2r restore: $holdleft $e $holdright", 2);
$e =~ s/\'//g;
# addfunc: add a symbol here
if ($holdleft =~ m|[_a-zA-Z0-9]$|) {
# The parentheses contained a function or array argument(s); make
# parens into {} for subsequent matching above
$e = $holdleft . "{" . $e . "}" . $holdright;
} else {
# The parentheses were to override precedence
$e = $holdleft . $e . $holdright;
}
$hf = 0;
} else {
$going = 0;
}
}
&dbg1("ev2s $e", 2);
if ($g_possible_assign) {
&dbg1("ev2t vars[$g_assign_lvk] := ($g_assign_pt, $g_assign_val, $g_assign_unc)", 2);
}
return $e;
} # End of eval.2
# addfunc: modify this string
$g_function_symbols = ":cos.C:inf.I:ln.K:logn.M:"
. "pt.P:sqrt.Q:sqr.Q:tan.T:root.V:exp.X:";
# eval.1 was the original main function for evaluating an input expression,
# before command history substitution was added.
sub eval_1
{
my($e) = @_;
my($s, $pct, $pre, $v, $post, $p1);
# init intermediate-result counter
$ir_n = $ir_in;
# Add an otherwise-unused single-quote character to both ends of the
# expression; these make regexp matching a bit simpler.
$e = "'" . $e . "'";
&dbg1("ev1a $e", 1);
# map all alphabetics to lower case. %% This is also done in BASIC.print
# and in do.cmd6, so it shouldn't be necessary here
if ($e =~ m/[A-Z]/) {
$e = lc($e);
&dbg1("e1.02 -> $e", 8);
}
# Substitute "%" with previous answer
# %%% to allow % as an operator (modulo) we'll need to not do this mapping
# here and instead allow '%' to be recognised as a variable name
if ($e =~ m|\%|) {
# (a2_pt, a2_val, a2_unc) hold result of last call to this eval.1 routine
$pct = &define_ir($a2_pt, $a2_val, $a2_unc, '');
&dbg1("e1.04 define pct = $pct", 1);
}
# Encode uncertainties in number literals using the reserved character 'U'.
# This needs to be done before variable substution because the
# parentheses around the uncertainty are too hard to deal with in
# the rest of the parser.
# The resulting pattern "3U7" will not occur because of the uppercase U
# (it is similar to part of a variable name like "v3U7", but that will
# have been mapped onto lowercase "v3u7" by now).
if ($e =~ m|([^_a-z0-9][0-9]*p?[.0-9]+)\(([0-9]+)\)|) {
$e =~ s|([^_a-z0-9][0-9]*p?[.0-9]+)\(([0-9]+)\)|$1."U".$2|ge;
&dbg1("e1.06 -> $e", 8);
}
&dbg1("e1.07 $e", 8);
# Substitute function names, variables and predefined constants
# e, pi and phi
while ($e =~ m|^(.*$p_nonvar)($p_vr2)($p_nonvar.*)$|) {
$pre = $1; $v = $2; $post = $3;
"$post'" =~ m/^ *([^ ])/; $p1 = $1;
&dbg1("e1.08 |$pre|$v|$post|", 1);
if (0) {
# function and array names must be followed by '('
} elsif (($p1 eq '(') && ($g_function_symbols =~ m|\:$v\.(.)\:|)) {
$s = $1;
} elsif (($p1 eq '(') && ($v eq 'rnd')) {
# random function - leave alone
$s = uc($v);
} elsif ($p1 eq '(') {
# Other newer functions like int end up being caught here
# # %%% any user-defined arrays and functions would fall here
# print STDERR ("*** Unrecognised function name $v"
# . ( ($BASIC_running) ? " in line $g_BASIC_pc" : "")
# . "\n");
# $s = " "; # Replace with something that won't match $p_var
$s = uc($v);
# variable-like tokens cannot be followed by '('
# Variables $s_e, $s_pi, etc. were created by &init_ir
} elsif (($p1 ne '(') && ($v eq "e")) {
$s = $s_e;
} elsif (($p1 ne '(') && ($v eq "false")) {
$s = 0;
} elsif (($p1 ne '(') && ($v eq "pi")) {
$s = $s_pi;
} elsif (($p1 ne '(') && ($v eq "phi")) {
$s = $s_phi;
} elsif (($p1 ne '(') && ($v eq "true")) {
$s = 1;
} elsif (($p1 ne '(') && ($v =~ m|^r([0-9]+)$|)) {
# result history item -- leave it alone
$s = "R" . $1;
} elsif (($p1 ne '(') && ($v eq '%')) {
$s = $pct;
# } elsif (($p1 ne '(') && ($vars_pt{$v} ne "")) {
} elsif ($p1 ne '(') {
$s = &define_ir($vars_pt{$v}, $vars_val{$v}, $vars_unc{$v}, $v);
# operators made of letters have already been converted to non-letter
# equivalents (this is done in expand.ih2() )
} else {
print STDERR ("*** Unrecognised name $v"
. ( ($BASIC_running) ? " in line $g_BASIC_pc" : "")
. "\n");
$s = " 0 "; # Replace with something that won't match $p_var
# $s = uc($v);
}
$e = $pre . $s . $post;
&dbg1("e1.09 -> |$e|", 1);
} # end of while (match special vars)
&dbg1("e1.10 $e", 1);
# now it's safe to eliminate spaces
if ($e =~ m| |) {
$e =~ s| ||g;
&dbg1("e1.12 -> $e", 8);
}
# put numbers in standard form
if ($e =~ m|([-\(\'\/%*xe+,^<>=])-|) { # ) balance
$e =~ s|([-\(\'\/%*xe+,^<>=])-|$1N|g; # ) balance
&dbg1("e1.14 -> $e", 8);
}
if ($e =~ m|([0-9.])e([N+0-9.])|) {
$e =~ s|([0-9.])e([N+0-9.])|$1E$2|g;
&dbg1("e1.15 -> $e", 8);
}
if ($e =~ m|([0-9])p([N+0-9.])|) {
$e =~ s|([0-9])p([N+0-9.])|$1P$2|g;
&dbg1("e1.16 -> $e", 8);
}
if ($e =~ m|([0-9])u([0-9])|) {
$e =~ s|([0-9])u([0-9])|$1U$2|g;
&dbg1("e1.17 -> $e", 8);
}
&dbg1("e1.20 $e", 1);
# do the actual parsing and evaluation
$e = &eval_2($e);
&dbg1("e1.22 $e", 1);
$e =~ s/\'//g;
&dbg1("e1.24 $e", 1);
if ($e =~ m|^I([0-9]+)$|) {
# result is a single IR
$e = $1;
$a2_pt = $ir_pt[$e];
$a2_val = $ir_val[$e];
$a2_unc = $ir_unc[$e];
dbg1("e1.26 res I$e = $a2_pt" . "P$a2_val", 2);
} elsif ($e =~ m|^I[0-9]|) {
# We have an IR followed by other unevaluated stuff
print STDERR ("*** Invalid syntax; I was left with '$e' (ev1) $inbline\n");
} else {
# result is a literal
($a2_pt, $a2_val, $a2_unc) = &splt($e);
dbg1("e1.28 lit $a2_pt" . "P$a2_val", 2);
}
dbg1("e1.30 $a2_pt" . "P$a2_val", 2);
return ($a2_pt, $a2_val, $a2_unc);
} # End of eval.1
#--------------------------------------------------- main
#
# initialization, main command loop, help, etc.
# Print a value that is in floating-point or scientific notation, converting
# if necessary to floating-point and adding or truncating mantissa digits
# to match the current scale.
sub prnt3
{
my($x, $paren, $force_ee, $suppress_1) = @_;
my($m1, $e, $s, $m2, $m3, $s2, $output, $e1, $e2);
print "prnt3 01($x, $paren)\n" if ($format_debug);
$output = "";
# Special cases
if ($x =~ m/inf/) {
print "prnt3 02: Infinity\n" if ($format_debug);
return((($x =~ m/-/) ? "- " : "") . "Infinity");
} elsif ($x =~ m/nan/) {
print "prnt3 02: NAN\n" if ($format_debug);
return("Undefined");
}
# Change "1.23e+21" to "1.23e21"
$x =~ s|\+||g;
print "prnt3 03a: $x\n" if ($format_debug);
# If it's in fixed format, but has more digits than the current precision
# allows, convert to floating-point
if (!($x =~ m|[eE]|)) {
# extract sign
if ($x =~ m/\-/) {
$s = "-";
$x =~ s/\-//;
} else {
$s = "";
}
print "prnt3 03b $x $g_ee_b4\n" if ($format_debug);
if ($force_ee || &f_gt($x, $g_ee_b4)) {
if ("$x.0" =~ m/^([0-9])([0-9]*)\.([0-9]+)/) {
$m1 = $1; $m2 = $2; $m3 = $3;
$e = length($m2);
if ($e > 0) {
$x = $m1 . "." . $m2 . $m3 . "e" . $e;
}
} else {
print "prnt3 error converting fixed to EE: |$x|\n";
}
}
$x = $s . $x;
print "prnt3 04: $x\n" if ($format_debug);
}
if ($x =~ m|[eE]|) {
# Translate scientific notation to something prettier
if ($x =~ m|^(-?)([0-9]+)\.([0-9]*)[eE]([-0-9]+)$|) {
$s = $1; $m1 = $2; $m2 = $3; $e = $4;
} elsif ($x =~ m|^(-?)([0-9]+)[eE]([-0-9]+)$|) {
$s = $1; $m1 = $2; $m2 = "0"; $e = $3;
} elsif ($x =~ m|[eE]|) {
print "error-0-01 Unhandled format: $x\n";
exit 0;
}
print "prnt3 05: |$s|$m1|$m2|$e|\n" if ($format_debug);
# Turn "e-05" into "e-5"
$e =~ s/-0/-/;
$s2 = $curscale - 1;
if ($m2 eq "") {
$m2 = "0";
}
# We should never have "12.34e56"
if (length($m1) > 1) {
print("error-0-02 Badformat mantissa: $s$m1.$m2" . "e$e\n");
exit 0;
}
# truncate mantissa
if (length($m2) > $s2) {
$m2 =~ m|^([0-9]{$s2})|;
$m2 = $1;
print "prnt3 06a: m2 '$m2'\n" if ($format_debug);
}
$output .= $p1_lstr if ($paren);
print "prnt3 06b: |$m1|$m2|$e| s2 $s2\n" if ($format_debug);
# Decide whether to print as a normal number
if (($force_ee == 0) && ($e >= 0) && ($e <= $s2)) {
# Take appropriate number of digits off of mantissa
$takedig = $e;
while(length($m2) < $takedig) {
$m2 .= "0";
}
$m2 =~ m|^([0-9]{$takedig})([0-9]*)$|;
$m1 = $m1 . $1; $m2 = $2;
$x = "$s$m1.$m2";
$x =~ s|0+$||;
$x =~ s|\.$||;
$output .= $x;
print "prnt3 07a: '$x'\n" if ($format_debug);
} elsif (($force_ee == 0) && ($e < 0) && ($e > -5)) {
# Small negative exponent. This happens when we use #bc#
while ($e < 0) {
$m2 = $m1 . $m2;
$m1 = '0';
$e++;
}
$x = "$s$m1.$m2";
$x =~ s|0+$||;
$x =~ s|\.$||;
$output .= $x;
print "prnt3 07b: '$x'\n" if ($format_debug);
} else {
# Format as mantissa x 10^ E
$x = "$s$m1.$m2";
$x =~ s|0+$||;
$x =~ s|\.$||;
print "prnt3 07c: '$x'\n" if ($format_debug);
if (($suppress_1 == 0) || ($x ne '1')) {
$output .= "$x$multstr";
}
$output .= "$k10$powstr$e_lstr$e$e_rstr";
}
$output .= $p1_rstr if ($paren);
} else {
# Just a plain number
# Extract sign
if ($x =~ m/\-/) {
$s = "-";
$x =~ s/\-//;
} else {
$s = "";
}
# Truncate digits to scale
if ("$x.0" =~ m/^([0-9]+)\.([0-9]+)/) {
$m1 = $1; $m2 = $2;
$e1 = length($m1);
$e2 = length($m2);
$e = $curscale - $e1;
if ($e2 > $e) {
if ($m2 =~ m/^(.{$e})/) {
$m2 = $1;
} else {
print "prnt3 error-2 truncating fixed to scale: |$x|\n";
}
$x = $m1 . "." . $m2;
}
} else {
print "prnt3 error-1 truncating fixed to scale: |$x|\n";
}
# Discard any trailing ".000"
if ($x =~ m|\.|) {
$x =~ s|0+$||;
$x =~ s|\.$||;
}
$output = $s . $x;
}
print "prnt3: $output\n" if ($format_debug);
return $output;
} # End of prnt3
# Convert a floating-point value to the properly formatted form (for the
# current print format), then insert the uncertainty digits if needed.
sub prnt2
{
my($x_val, $x_unc, $paren, $force_ee, $suppress_1) = @_;
my($rv);
print "prnt2($x_val, $x_unc, $paren, $force_ee, $suppress_1)\n" if ($format_debug);
if ($x_unc != 0) {
$suppress_1 = 0;
}
$rv = &prnt3($x_val, $paren, $force_ee, $suppress_1);
if ($x_unc != 0) {
$rv = &insert_uncertainty($rv, $x_val, $x_unc);
}
return $rv;
}
# Compute pi using the Baily-Plouffe hexadecimal series, which
# converges quite quickly
sub init_pi_1
{
my($f1, $i, $d);
&dbg1("init_pi_1", 2);
$g_pi = $g_0;
$f1 = $g_1;
$i = $g_0;
$d = $g_1;
while (&f_gt($f1, $prec)) {
$i_f = $i; $i_f = &f_mul($g_8, $i_f);
$f1 = &f_div($g_4, &f_add($i_f, $g_1));
$f1 = &f_sub($f1, &f_div($g_2, &f_add($i_f, $g_4)));
$f1 = &f_sub($f1, &f_div($g_1, &f_add($i_f, $g_5)));
$f1 = &f_sub($f1, &f_div($g_1, &f_add($i_f, $g_6)));
$f1 = &f_div($f1, $d);
$d = &f_mul($d, $g_16);
&dbg1("init.pi_1 g_pi term", 1);
$g_pi = &f_add($g_pi, $f1);
$i = &f_add($i, $g_1);
}
print "init_pi_1 pr $g_pi\n" if ($format_debug);
$d = &prnt2($g_pi, 0, 0, 0, 0);
&dbg1("init_pi_1 done: $d", 2);
} # End of init_pi_1
# Compute pi using a formula based on the Gauss arithmetic-geometric mean
# method. Number of digits doubles with each loop.
sub init_pi_2
{
my($x, $y, $a, $b, $c, $ct, $i);
&dbg1("init_pi_2", 2);
$a = $x = $g_1; $b = &f_div($g_1, &f_sqrt($g_2)); $c = &f_div($g_1, $g_4);
&dbg1("init_pi_2 a=$a, b=$b, c=$c", 2);
for($i=1; $i<($curscale+2); $i*=2) {
$y = $a;
&dbg1("init_pi_2 y=$y", 2);
$a = &f_div(&f_add($a, $b), $g_2);
&dbg1("init_pi_2 a=$a", 2);
$b = &f_sqrt(&f_mul($b, $y));
&dbg1("init_pi_2 b=$b", 2);
$ct = &f_sub($a, $y);
&dbg1("init_pi_2 ct=$ct", 2);
$c = &f_sub($c, &f_mul($x, &f_mul($ct, $ct)));
&dbg1("init_pi_2 c=$c", 2);
$x = &f_mul($x, $g_2);
&dbg1("init_pi_2 x=$x", 2);
}
$ct = &f_add($a, $b);
&dbg1("init_pi_2 ct=$ct", 2);
$a = &f_mul($ct, $ct);
&dbg1("init_pi_2 a=$a", 2);
$b = &f_mul($g_4, $c);
&dbg1("init_pi_2 b=$b", 2);
$g_pi = &f_div($a, $b);
&dbg1("init_pi_2 g_pi=$g_pi", 2);
print "init_pi_2 pr $g_pi\n" if ($format_debug);
$c = &prnt2($g_pi, 0, 0, 0, 0);
&dbg1("init_pi_2 done: $c", 2);
} # End of init_pi_2
# Define intermediate results for predefined constants e, pi and phi
sub init_ir
{
$ir_n = 0;
$s_e = &define_ir(0, $g_e, 0, '');
$s_pi = &define_ir(0, $g_pi, 0, '');
$s_phi = &define_ir(0, $g_phi, 0, '');
$ir_in = $ir_n;
}
sub set_var
{
my ($vname, $lv_pt, $lv_val, $lv_unc) = @_;
$vars_pt{$vname} = $lv_pt;
$vars_val{$vname} = $lv_val;
$vars_unc{$vname} = $lv_unc;
} # End of set.var
# This part of initialization is the same for both types of primitives.
sub hi_init
{
my($prflag) = @_;
my($i, $f);
my($prec);
my($f1, $i_f, $d);
$g_ln_10 = &f_ln($g_10);
$g_log10_e = &f_div($g_1, $g_ln_10);
&dbg1("hi.init prec", 1);
$prec = &f_div($g_1, &f_pow10($curscale + 2));
$roundprec = &f_div($g_0_5, &f_pow10($curscale));
# Constants used for handling roundoff, overflow, boundary
# conditions, etc.
$g_ovr1 = "+1.0E300";
$g_log10_ovr1 = "300.0";
# ln(10^300) = ln(10) * 300
$g_ln_ovr1 = &f_mul($g_ln_10, $g_log10_ovr1);
# Compute how big the argument of Gamma needs to be in order to guarantee
# that we get the requested precision. The first neglected term in the
# Stirling series is "+ 657931/(300 N^25)". For D digits of precision, we
# want this to be smaller than 10^-D (this is a bit conservative, because
# the largest term in the series is the initial "N ln(N)" and N will always
# be at least as big as the value we're about to compute). Setting
# 657931/(300 N^25) equal to 10^-D, we get:
# 2193.10333.../N^25 = 1/10^D
# 2193.10333... = N^25/10^D
# 2193.10333... * 10^D = N^25
# N = (2193.10333... * 10^D)^(1/25)
# = 2193.10333...^(1/25) * 10^(D/25)
# = 1.360324... * 10^(D/25)
$gamma_minarg = &f_mul("1.360324", &f_pow10(&f_div($curscale, "25.0")));
&dbg1("hi.init gamma_minarg=" . sprintf("%f", $gamma_minarg), 32);
# However, we don't want to be looping too many times in the iteration that
# makes N bigger than gamma_minarg. So, if it's bigger than a "tolerable"
# level, we report it.
# %%% For possible fixes to the Gamma precision issue, see notes in header.
$g_gammalim = "101.0";
if (&f_gt($gamma_minarg, $g_gammalim)) {
# We will set $gamma_minarg to $g_gammalim
# So now we compute that first neglected term explicitly from the
# minarg value we will actually use...
$gammadig = &f_int(&f_neg(&f_log10(
&f_div("2193.10333", &f_pow($g_gammalim, "25.0"))
)));
# And if this turns out to be less precise than they asked for, we
# can complain about it...
if ($gammadig < $curscale) {
if ($warned_gdsc != $curscale) {
$msg1 = "Note: For all values less than " . ($g_gammalim-1)
. ", factorial will give only " . &prnt2($gammadig, 0, 0, 0, 0)
. " digits\n"
. " of accuracy; and for values less than "
. sprintf("%.0f", $gamma_minarg + 1.0)
. ", it will give fewer\n"
. " than the requested $curscale digits.\n";
print $msg1 if ($prflag);
$warned_gdsc = $curscale;
}
}
$gamma_minarg = $g_gammalim;
}
# high-level constants
# compute constant e
$g_e = $g_1; $f = 1;
for($i=1; ($i < 172) && (&f_gt($f, $prec)); $i++) {
$f = &f_div($f, $i); $g_e = &f_add($g_e, $f);
}
# Compute pi using the Baily-Plouffe hexadecimal series, which
# converges quite quickly
&init_pi_2();
$g_sin_limit = &f_mul($g_2p32m1, $g_pi);
$g_phi = &f_div(&f_add(&f_sqrt($g_5), $g_1), $g_2);
&dbg1("hi.init g_05_ln_2pi", 1);
$g_05_ln_2pi = &f_mul($g_0_5, &f_ln(&f_mul($g_2, $g_pi)));
$g_log10_log10_e = &f_log10($g_log10_e);
$g_log10_2 = &f_log10($g_2);
# recreate the native floating point infinities usinf f_mul. These count
# on f_mul generating the infinite values.
$g_inf = inf;
$g_ninf = -inf;
# PT Infinities have a native (double) infinity in the PT field
$g_pt_inf = inf;
$g_pt_nan = nan;
# constants used for output formatting
$g_ee_b4 = &f_pow10($curscale - $g_min_mant_digits);
&init_ir();
# A few of the popular big numbers are pre-defined as variables.
# These PT-Val tuples should work in all scales because the most
# complex mantissa is the "3.000003" for milli-millillion.
&set_var("million", 0, "1.0E6", 0);
&set_var("billion", 0, "1.0E9", 0);
&set_var("trillion", 0, "1.0E12", 0);
&set_var("quadrillion", 0, "1.0E15", 0);
&set_var("quintillion", 0, "1.0E18", 0);
&set_var("sextillion", 0, "1.0E21", 0);
&set_var("septillion", 0, "1.0E24", 0);
&set_var("octillion", 0, "1.0E27", 0);
&set_var("nonillion", 0, "1.0E30", 0);
&set_var("decillion", 0, "1.0E33", 0);
&set_var("vigintillion", 0, "1.0E63", 0);
&set_var("googol", 0, "1.0E100", 0);
&set_var("centillion", 1, "3.03E2", 0);
&set_var("millillion", 1, "3.003E3", 0); # Conway-Wechsler's "millinillion"
&set_var("milli_millillion", 1, "3.000003E6", 0);
&set_var("sand_reckoner", 1, "1.0E16", 0);
&set_var("googolplex", 1, "1.0E100", 0);
&set_var("skewes", 2, "1.0E34", 0);
} # End of hi.init
# This routine inserts the uncertainty digits into the proper point in the
# mantissa portion of a text string representing a formatted value. The text
# string can have lots of extra formatting around the mantissa.
#
# text val unc output
# '21.34' 21.34 1.0e-1 '21.34(10)'
# '21.34' 2.134e1 1.0e-1 '21.34(10)'
# '[ 1.23 x 10^{123} ]' 1.23e123 1e121 '[ 1.230(10) x 10^{123} ]'
sub insert_uncertainty
{
my($text, $val, $unc) = @_;
my($xs, $xm, $xe, $us, $um, $ue, $lead);
my($c, $state, $numdig, $dot, $rd);
# The number of mantissa digits we want to emit is the difference between
# the exponents
($xs, $xm, $xe) = &fbc_split($val);
($us, $um, $ue) = &fbc_split($unc);
$lead = $xe + 2 - $ue;
# Convert uncertainty mantissa to standard form (two digits, no decimal,
# with rounding)
$um .= "00"; # Make sure there are at least 3 digits
$um =~ s/[^0-9]//g; # Remove all non-digits
if ($um =~ m/^([1-9][0-9])([0-9])/) {
# Get 1st 2 digits plus rounding digit
$um = $1; $rd = $2;
if (($rd >= 5) && ($um < 99)) {
$um++;
}
} else {
# Perhaps first digit was a 0?
print STDERR "*** Uncertainty parse error '$unc'\n";
$um = '';
}
&dbg1("xs $xs xm $xm xe $xe um $um ue $ue lead $lead", 8);
$rv = '';
$state = $numdig = $dot = 0;
$text .= ' ';
foreach $c (split('', $text)) {
# Advance state machine to recognize mantissa digits
if ($c =~ m/[.0-9]/) {
if ($state == 0) {
$state = 1;
}
} else {
if ($state == 1) {
$state = 2;
}
}
# Output the wanted significant digits
if ($state == 1) {
# If we get here, it is a mantissa digit or '.'
if ($numdig && ($c =~ m/[0-9]/)) {
$numdig++;
} elsif ($c =~ m/[1-9]/) {
# First nonzero digit
$numdig++;
} elsif ($c eq '.') {
$dot = 1;
}
if ($numdig <= $lead) {
$rv .= $c;
} else {
# This digit is beyond what we want -- but if we haven't seen the
# decimal point yet we need to keep it
if ($dot) {
# don't output the digit
} else {
# Output the digit and add another uncertainty digit
$rv .= $c;
$um .= '0';
$lead++;
}
}
$c = '';
}
# If we got into state 2 without enough digits, add 0s
if (($state == 2) && ($numdig < $lead)) {
if (!($dot)) {
$rv .= '.';
$dot = 1;
}
while (($state == 2) && ($numdig < $lead)) {
$rv .= '0';
$numdig++;
}
}
# Detect when it's time to add the uncertainty digits
if (($state == 2) && ($um ne '')) {
$rv = $rv . '(' . $um . ')';
$um = ''; # prevent this from being done twice
}
# Now add the original character
$rv .= $c;
}
# Remove any trailing space(s), including the one we added at the
# beginning of the uncertainty handling.
$rv =~ s/ +$//;
return $rv;
} # End of insert.uncertainty
# Constants and functions used by the label formatting routines
$k_1e5 = 100000.0;
$k_1e6 = $k_million = 1000000.0;
$k_1e7 = 10000000.0;
$k_1e10 = 1.0e10;
# Constants used in uncertainty calculations
$k_ln_10 = log(10.0);
# Truncates a string to a given number of characters. Before doing so,
# pads the end with some character if desired.
sub trunc2
{
my($x, $n, $extend) = @_;
if ($extend ne "") {
$x = $x . ($extend x $n);
}
if ($x =~ m/^(.{$n})/) {
$x = $1;
}
return $x;
}
# This routine generates the old-style label for numbers.rhtf
#
# Examples:
#
# label Internal form Value
# l5390en44 0-pt-5.39e-44 5.390 x 10^-44
# l0_2078 0-pt-0.2078 0.2078
# l1_4142 0-pt-1.4142 1.4142
# l15_154 0-pt-15.154 15.154
# l127 0-pt-127 127
# l720720 0-pt-720720 720720
# l1419_c 0-pt-1419869 1419869
# l_1234e10 0-pt-1.234e10 12345654321 or 1.24 x 10^10
# l_1010e38 0-pt-1.010e38 1.010 x 10^38
# l_p1_340_792 1-pt-340.792 6.2 x 10^340
# l_p1_2098e6 1-pt-2.0989e6 4 x 10^2098959
# l_p1_1000e166 1-pt-1.0e166 10 ^ 10 ^ 166
# l_p2_4342e12 2-pt-4.342e12 10 ^ 10 ^ 4342944819032
# l_p3_36305 4-pt-36305.315 10 ^ 10 ^ 10 ^ 2.069 x 10^36305
sub format4
{
my($apt, $av) = @_;
my($rv, $as, $am, $ae);
($as, $am, $ae) = &fbc_split($av);
if ($apt > 0) {
if ($av >= $k_million) {
# l_p1_1000e166
$rv = "l_p" . $apt . "_" . &trunc2($am, 5, "0") . "e" . $ae;
$rv =~ s/\.//;
} else {
# l_p1_340_792
$rv = "l_p" . $apt . "_" . &trunc2($av, 7, "");
$rv =~ s/\./_/;
}
} elsif ($av >= $k_1e10) {
# l_1701e38
$rv = $am;
$rv =~ s/\.//;
$rv = &trunc2($rv, 4, "0");
$rv = "l_" . $rv . "e" . $ae;
} elsif ($av >= $k_million) {
# l1419_c
$am =~ s/\.//;
$rv = &trunc2($am, 4, "");
$ae =~ tr/6789/cdef/;
$rv = "l_" . $rv . "_" . $ae;
} elsif ($av >= 0.1) {
# l0_2078, l1_4142, l15_154, l127, l720720
$rv = "l" . &trunc2($av, 6, "");
if ($rv =~ m/\./) {
$rv =~ s/\./_/;
$rv = &trunc2($rv, 7, "0");
}
} else {
$rv = $am;
$rv =~ s/\.//;
$rv = &trunc2($rv, 4, "0");
$ae =~ s/-/n/;
$rv = "l" . $rv . "e" . $ae;
}
return $rv;
} # End of format4
# Formats a real number with a given number of significant figures,
# with no scientific notation; then substitutes "." (if any) with "_".
sub canon_real
{
my($v, $p) = @_;
if ($v =~ m/^\./) { $v = "0" . $v; } # Ensure digit before decimal point
$p++; if ($v =~ m/^([.0-9]{$p})/) { $v = $1; } # If too long, truncate
$v =~ s/\./_/;
$v =~ s/_0+$//; # Remove needless trailing 0's
$t = $v . "_";
if ($t =~ m/^[0-9][0-9]_/) { $v = "a" . $v; }
if ($t =~ m/^[0-9]{3}_/) { $v = "b" . $v; }
if ($t =~ m/^[0-9]{4}_/) { $v = "c" . $v; }
if ($t =~ m/^[0-9]{5}_/) { $v = "d" . $v; }
return $v;
} # End of canon_real
# Formats a scientific-notation number with a given number of
# exponent and mantissa digits for use within for.mat5 labels.
# Arguments:
#
# $e exponent
# $pe precision for exponent
# $m mantissa
# $pm precision for mantissa
#
# For example, if you want to format 6.02x10^23 with a precision of 3 in
# both parts, you would call &canon_sci("23", 3, "6.020", 3), which
# becomes 023_602
sub canon_sci
{
my($e, $pe, $m, $pm) = @_;
my($ra);
print "canon_sci($e, $pe, $m, $pm)...\n" if ($format_debug);
# Check for special cases
if ($e eq 'inf') {
print "$e\n" if ($format_debug);
return($e);
} elsif ($e eq 'nan') {
print "$e\n" if ($format_debug);
return($e);
}
# Handle leading 0's e.g. "01.2345..." in mantissa
if ($m =~ m/^0+[1-9]/) {
$m =~ s/^0+//;
}
# Handle exponent too small for desired field width (left-pad exponent with 0)
if (!($e =~ m/[0-9]{$pe}/)) {
$e = ("0" x $pe) . $e;
if ($e =~ m/([0-9]{$pe})$/) {
$e = $1;
}
}
# truncate precision of mantissa to one leading digit plus $pm trailing
# digits. (The extra digit will be removed during rounding)
$m = $m . ("0" x $pm);
if ($m =~ m/^([1-9][.][0-9]{$pm})/) {
$m = $1;
} else {
print STDERR "canon_sci (e1): Cannot parse mantissa '$m' $inbline\n";
exit(-1);
}
# Perform rounding
$ra = "0." . ("0" x ($pm-1)) . "5"; # If $pm is 3, this will be "0.005"
$ra += 0;
if ($m + $ra < 10.0) {
$m = $m + $ra;
} else {
# We leave "9.99998" alone so it is distinguishable from "10.00002"
}
# Truncate again to remove the rounding digit
if ($m =~ m/^([1-9][0-9.]{$pm})/) {
$m = $1;
} elsif (($pm == 3) && ($m =~ m/^([1-9]\.[0-9])/)) {
$m = $1;
} else {
print STDERR (
"canon_sci (e2): Cannot re-truncate mantissa '$m'\n"
. " by pattern '^([1-9][0-9.]{$pm})'; consider adding another\n"
. " special case.\n");
exit(-1);
}
# remove mantissa embedded '.'
$m =~ s/\.//;
# remove mantissa trailing 0's
$m =~ s/0+$//;
print ($e. "_$m\n") if ($format_debug);
return ($e . "_" . $m);
} # End of canon_sci
# format5 implements the new-style labels that are completely alphabetizable:
#
# l_234em34 2.34 x 10^-34
# l0_1 0.1
# l9_34 9.34
# la10_2 10.2
# lb256 256
# lc1729 1729
# ld19683 19683
# le005_1 100000 = 1x10^5
# le023_602 6.02 x 10^23
# le299_9 9 x 10^299
# lp1_b300_0 1 PT 300 = 1x10^300
# lp1_c2345_6 1 PT 2345.6 {~=} 4x10^2345
# lp1_e009_345 1 PT 3.45e9 = 10^(3.45*10^9)
# lp1_e299_999 1 PT 9.99e299 = 10^(9.99x10^299)
# lp2_b300_0 2 PT 300 = 10^(10^300)
# lpa10_xxx 10 PT xxx
# lpa99_xxx 99 PT xxx
# lpb100_xxx 100 PT xxx
sub format5
{
my($apt, $av) = @_;
my($rv, $as, $am, $ae);
print "format5($apt, $av)...\n" if ($format_debug);
if ($av eq 'inf') {
print "format5: = lz_inf\n" if ($format_debug);
return("lz_inf");
}
($as, $am, $ae) = &fbc_split($av);
if ($apt > 9) {
if ($av >= $k_1e5) {
# l_pa12_e009_345
$rv = "lp" . &canon_real($apt, 6) . "_e" . &canon_sci($ae, 3, $am, 3);
} else {
# l_pa12_c2345_6
$rv = "lp" . &canon_real($apt, 6) . "_" . &canon_real($av, 6);
}
} elsif ($apt > 0) {
# for now I'll skip the test for PT > 9
if ($av >= $k_1e5) {
# l_p1_e009_345
$rv = "lp" . $apt . "_e" . &canon_sci($ae, 3, $am, 3);
} else {
# l_p1_c2345_6
$rv = "lp" . $apt . "_" . &canon_real($av, 6);
}
} elsif ($av >= 1.0e10) {
# le032_602
$rv = "le" . &canon_sci($ae, 3, $am, 3);
} elsif ($av >= 1.0e8) {
# Use 4 digits of mantissa for 9-digit numbers; for example
# 535252535 -> le008_5353
$rv = "le" . &canon_sci($ae, 3, $am, 4);
} elsif ($av >= 1.0e5) {
# Use 5 digits of mantissa for 6-, 7- and 8-digit numbers; for example
# 196560 -> le005_19656
$rv = "le" . &canon_sci($ae, 3, $am, 5);
} elsif ($av >= 0.1) {
# l0_1, l9_34, la10_2, lb256, ld19683
$av = &prnt3($av, 0, 0, 0);
$rv = "l" . &canon_real($av, 6);
} else {
# Smaller than 0.1 -- not handling alphabetization yet
$am =~ s/\.//; $ae =~ s/-//;
$rv = "l_" . &trunc2($am, 3, "") . "em" . $ae;
}
print "format5: = $rv\n" if ($format_debug);
return $rv;
} # End of format5
# format 6 just prints the internal representation of a number (PT and Val)
sub format6
{
my($apt, $av, $au) = @_;
my($rv);
$rv = "|PT${apt}V${av}U$au|";
return $rv;
} # End of format6
# Convert a (suitably small) number to HH:MM:SS.ff format
sub cv_10_60
{
my($av) = @_;
my($rv, $h, $m, $s, $f, $sign);
# Add 0 to convert "1.3034E2" into "130.34"
$av += 0;
# If it still has an "E", it is too big (1.2e27) or too small (1.2e-5)
if ($av =~ m/[eE]/) {
return $av;
}
# Extract sign
if ($av =~ m/^-/) {
$av = - $av;
$sign = '-';
} else {
$sign = '';
}
# If there is still a sign, WTF
if ($av =~ m/-/) {
print STDERR "*** cv_10_60 parse error '$av'\n";
return "$sign$av";
}
# Extract fraction, preferably without using int function
if ($av =~ m/^([0-9]+)\.([0-9]+)$/) {
# Integer and a fraction
$av = $1; $f = $2;
} elsif ($av =~ m/^([0-9]+)$/) {
# All integer
$av = $1; $f = 0;
} else {
# This is the fallback, but i don't like to use it because it often
# results in something like "0:02:10.340000000000003"
$f = $av - int($av); $av = int($av);
}
# Convert integer portion to base 60
$rv = '';
while($av > 0) {
$m = int($av / 60); $s = $av - (60 * $m);
$rv = ":" . sprintf("%02d", $s) . $rv;
$av = $m;
}
# Remove initial ":", make it "00" if it's 0
$rv =~ s/^://;
if ($rv == 0) {
$rv = "00";
}
# Add the part of the fraction following a leading '0.'
if ($f > 0) {
$f =~ s/^ *0*\.//;
$rv .= ".$f";
}
return "$sign$rv";
} # End of cv_10_60
# format 7 outputs small numbers in HH:MM:SS.ff format
sub format7
{
my($apt, $av, $au) = @_;
my($rv, $h, $m, $s, $f);
# Anything more than 9 digits is printed in raw format.
if (($apt > 0) || (abs($av) >= 1.0e10)) {
return(&format6($apt, $av, $au));
}
if ($au == 0) {
$rv = &cv_10_60($av);
} elsif ($au < 60) {
$m = int($av / 60); $s = $av - (60 * $m);
$s = &prnt2(100+$s, $au, 0, 0, 0);
if ($s =~ m/^1([0-9][0-9].*)$/) {
$s = $1;
} else {
print STDERR "format7 cnnot recover seconds '$s'\n";
}
if ($m) {
$rv = &cv_10_60($m) . ":" . $s
} else {
$rv = $s;
}
} else {
$rv = &cv_10_60(int($av)) . "(" . &cv_10_60(int($au)) . ")";
}
<< 'COMMENTED_OUT';
# Add 0 to convert "1.3034E2" into "130.34"
$av += 0;
if ($av =~ m/^([0-9]+)\.([0-9]+)$/) {
# Integer and a fraction
$av = $1; $f = $2;
} elsif ($av =~ m/^([0-9]+)$/) {
# All integer
$av = $1; $f = 0;
} else {
# This is the fallback, but i don't like to use it because it often
# results in something like "0:02:10.340000000000003"
$f = $av - int($av); $av = int($av);
}
$m = int($av / 60); $s = $av - (60 * $m);
$h = int($m / 60); $m = $m - (60 * $h);
$h = sprintf("%02d", $h);
$m = sprintf("%02d", $m);
$s = sprintf("%02d", $s);
# Include hours only if they aren't "00"
if ($h > 0) {
$rv = "$h:$m:$s";
} else {
$rv = "$m:$s";
}
# Add the part of the fraction following a leading '0.'
if ($f > 0) {
$f =~ s/^ *0*\.//;
$rv .= ".$f";
}
COMMENTED_OUT
return $rv;
} # End of format7
# print a PT, given its pt and val as separate arguments.
sub prnt1
{
my($apt, $av, $au, $newline) = @_;
my($avi, $avf, $avl, $avl2, $output);
my($f5_lbl, $afm);
print "prnt1($apt, $av, $au, $newline)...\n" if ($format_debug);
if ($fmt_callproc eq "format4") {
print(&format4($apt, $av) . ":: ");
} elsif ($fmt_callproc eq "format5") {
$f5_lbl = &format5($apt, $av);
print("\n\n" . $f5_lbl . ":: #");
} elsif ($fmt_callproc eq "format6") {
print(&format6($apt, $av, $au) . " ");
} elsif ($fmt_callproc eq "format7") {
print(&format7($apt, $av, $au) . " ");
}
if ($doround) {
# round up
print "($apt, $av) -> round" if ($format_debug);
($apt, $av) = &pt_roundup($apt, $av);
print " -> ($apt, $av)\n" if ($format_debug);
}
$output = "";
if (&isninf($av)) {
$output = "- Infinity";
} elsif (&isinf($av)) {
$output = "Infinity";
} elsif (&isnan($av)) {
$output = "Undefined";
} elsif ($apt == 0) {
# Format a PT0.
print "prnt1 case la-le\n" if ($format_debug);
$output = &prnt2($av, $au, 0, 0, 1);
} else {
# We need to print either "$apt PT $av" or "($apt-1) PT (10^$av)"
# depending on whether or not $av is less than 10^scale
if (&f_lt($av, $g_ee_b4)) {
print "prnt1 n $av, $g_ee_b4\n" if ($format_debug);
# $av is less than 10^scale -- we have a value like 4.677*10^2345,
# which will arrive here with the representation 1P2345.67 or
# 1P2.34567e3. We have to subtract 1 from pt and compute mantissa
# of 10 ^ val
# %%% 20190327: Note that this shares a lot of concepts with prnt3
# and duplicates some functions; it might be nice to figure out how
# to combine these.
# get exponent
$avi = &f_int($av);
# figure out how many digits of mantissa we can print
# %%% formerly: &f_int(&f_log10($avi)) + 1; - 20190408
$avl = int(log($avi)/log(10)) + 1;
$avl = $curscale - $avl;
$avl =~ s| ||g;
$avl2 = $avl + 1; # 1 extra for decimal point (we'll remove sign)
$avl2 =~ s| ||g;
# compute mantissa
&dbg1("prnt1a mant $av - $avi", 32);
$avf = &f_pow10(&f_sub($av, $avi));
&dbg1("prnt1b -> $avf", 32);
# remove leading + sign if any (present if fbc_xxx maths is being used)
$avf =~ s/^[+]//;
print "prnt1 avf $avf avi $avi avl2 $avl2\n" if ($format_debug);
# discard insignificant digits of mantissa. We know $avf is
# of the form D.DDDDDDDDD where D is a digit; and pt.roundup was already
# used to deal with trailing "999"s so truncation is suitable here.
if (length($avf) > $avl2) {
$avf =~ m|^(.{$avl2})|;
$avf = $1;
}
# Discard any trailing ".000"
if ($avf =~ m|\.|) {
$avf =~ s|0+$||;
$avf =~ s|\.$||;
}
# Discard leading '+'
$avf =~ s|^\+||;
# Handle the "suppress_1" option
$afm = "$avf$multstr";
if ($afm eq "1$multstr") {
$afm = "";
}
# Handle first three PT classes separately.
if ($apt == 1) {
# 10^1001
print "prnt1 case p1_a\n" if ($format_debug);
$output .= "$afm$k10$powstr$e_lstr"
. &prnt2($avi, 0, 0, 0, 0) . $e_rstr;
} elsif ($apt == 2) {
# 10 ^ ( 10 ^ 1001 )
print "prnt1 case p2_a\n" if ($format_debug);
$output .= "$k10$powstr$e_lstr$p1_lstr$afm$k10$powstr$e_lstr"
. &prnt2($avi, 0, 0, 0, 0) . "$e_rstr$p1_rstr$e_rstr";
} elsif ($apt == 3) {
print "prnt1 case p3_a\n" if ($format_debug);
$output .= "$k10$powstr$e_lstr$p2_lstr$k10$powstr$e_lstr$p1_lstr"
. "$afm$k10$powstr$e_lstr" . &prnt2($avi, 0, 0, 0, 0)
. "$e_rstr$p1_rstr$e_rstr$p2_rstr$e_rstr";
} else {
print "prnt1 case p4_a\n" if ($format_debug);
$apt -= 1;
$output .= "$apt$pt_str$p1_lstr$afm$k10$powstr$e_lstr"
. &prnt2($avi, 0, 0, 0, 0) . "$e_rstr$p1_rstr";
}
} else {
print "prnt1 case _e $av\n" if ($format_debug);
$afm = &prnt2($av, 0, 1, 1, 1);
if ($apt == 1) {
print "prnt1 case p1_e\n" if ($format_debug);
$output .= "$k10$powstr$e_lstr$afm$e_rstr";
} elsif ($apt == 2) {
print "prnt1 case p2_e\n" if ($format_debug);
$output .= "$k10$powstr$e_lstr$p2_lstr$k10$powstr$e_lstr"
. "$afm$e_rstr$p2_rstr$e_rstr";
} else {
print "prnt1 case p3_e\n" if ($format_debug);
$output .= "$apt$pt_str$afm";
}
}
}
$output =~ s| +| |g;
while(length($output) > $lleft) {
$output =~ m/^(.{$lleft})(.*)$/;
print $1;
print "\\\n";
$lleft = 78;
$output = $2;
}
print $output;
if ($fmt_callproc eq "format5") {
print("# = $lastc6in\n");
print(" See also [$output|#$f5_lbl].");
}
if ($newline) {
print "\n"; $lleft = 78;
}
} # End of prnt1
# Sets the strings used to format numbers
sub format1
{
my($fmt) = @_;
$k10 = "10";
# Handle the special types
if ($fmt == 4) {
$fmt_callproc = "format4";
$fmt = 2;
} elsif ($fmt == 5) {
$fmt_callproc = "format5";
$fmt = 2;
} elsif ($fmt == 6) {
$fmt_callproc = "format6";
$fmt = 1;
} elsif ($fmt == 7) {
$fmt_callproc = "format7";
$fmt = 1;
} else {
$fmt_callproc = "";
}
# 20100113: I decided to make p1_lstr, etc. null because () and []
# seem superfluous when there is or {}
if ($fmt == 3) {
$multstr = "×";
$powstr = "";
$e_lstr = "";
$e_rstr = "";
$p1_lstr = ""; # "(";
$p1_rstr = ""; # ")";
$p2_lstr = ""; # "[";
$p2_rstr = ""; # "]";
$pt_str = " PT ";
} elsif ($fmt == 2) {
$multstr = "{x}";
$powstr = "^";
$e_lstr = "{";
$e_rstr = "}";
$p1_lstr = ""; # "(";
$p1_rstr = ""; # ")";
$p2_lstr = ""; # "[";
$p2_rstr = ""; # "]";
$pt_str = "\@pt\@";
} else {
$multstr = " x ";
$powstr = " ^ ";
$e_lstr = "";
$e_rstr = "";
$p1_lstr = " ( ";
$p1_rstr = " ) ";
$p2_lstr = " [ ";
$p2_rstr = " ] ";
$pt_str = " PT ";
}
} # End of format1
# sub expand.ihist
# {
# my($cmd) = @_;
# my($rv);
#
# $rv = $cmd;
# while(" $rv " =~ m|^(.+)[cC]([0-9]+)([^0-9].*)$|) {
# $rv = $1 . "(" . $input_history[$2] . ")" . $3;
# }
# return $rv;
# }
sub expand_C_hist
{
my($e) = @_;
my(%h);
my($pre, $post, $tn, $te, $tv, $rv, $t2);
$rv = $e;
while(" $rv " =~ m|^(.*[^a-zA-Z_0-9])[cC]([0-9]+)([^0-9].*)$|) {
$pre = $1; $tn = $2; $post = $3;
if ($h{$tn} ne "") {
$tv = $h{$tn};
} else {
$te = $input_history[$tn];
$tv = &expand_C_hist($te);
# Here we would want to actually evaluate $tv and get a string
# numeric result, but that's too hard to do right now.
$h{$tn} = $tv;
# print "tn $tn te $te tv $tv\n";
}
if (!($BASIC_running)) {
print "C$tn: $tv\n";
}
$lleft = 78;
if ($tv =~ m/^\(.*\)$/) {
# We already have parens
} else {
# Add parens
$tv = "(" . $tv . ")";
}
$rv = $pre . $tv . $post;
if (!($BASIC_running)) {
$t2 = $rv;
$t2 =~ s/^ +//;
print "C$ihnum: $t2\n";
}
}
return $rv;
} # End of expand.C_hist
# expand.ih2 interpolates commands like "c3" into the input string,
# and changes 'x' to '*' when appropriate.
sub expand_ih2
{
my($e) = @_;
my($rv);
$rv = &expand_C_hist($e);
# Pre-translate 'x' for multiplication when next to parentheses. These
# are the cases where it is clearly not a variable 'x'.
$rv =~ s| x\(| *\(|g;
$rv =~ s|\)x\(|\)*\(|g;
$rv =~ s|\)x |\)* |g;
# Allow them to use 'x' to multiply if padded with spaces and next to
# a variable.
$rv =~ s|($p_var) x |$1 * |g;
$rv =~ s| x ($p_var)| * $1|g;
# Any operator made of letters needs to be remapped now
$rv =~ s|\band\b|\&|g;
$rv =~ s|\bmod\b|\%|g;
$rv =~ s|\bnot\b|\~|g;
$rv =~ s|\bor\b|\||g;
# Now remove all remaining spaces
$rv =~ s| +||g;
return $rv;
} # End of expand.ih2
# Add a command to the command-history list.
sub define_Chist
{
my($cmd) = @_;
return if ($BASIC_running);
$input_history[$ihnum] = $cmd;
$ihnum++;
}
$pragma_any_next = 1;
$g_BASIC_trace = 0;
# do.cmd3 handles 'C[0-9]' substitution and '='; the rest
# is done by eval.1
#
# If $BASIC.running is true, $prflag controls printing:
# 0 for no printing and don't add to chist
# 1 print just the number
# 2 print the number and newline
# 3 print surrounding formatting "Rn = val" or "V: val"
sub docmd3
{
my($cmd, $prflag, $allow_assign) = @_;
my($pr, $sverr);
my($lv_pt, $lv_val, $lv_unc);
&dbg1("dc3.01 cmd '$cmd' $prflag $allow_assign", 16);
if ($cmd eq "") {
&dbg1("dc3.02 '$cmd'", 16);
return;
}
if ($BASIC_running) {
$pr = $prflag;
} else {
$pr = 3;
}
&dbg1("dc3.03 $cmd", 16);
# First, save input string into input history
# Expand first, to prevent infinite recursion
$cmd = &expand_ih2($cmd);
&dbg1("dc3.04 $cmd", 16);
if ($pr) {
&define_Chist($cmd);
}
&dbg1("dc3.05 $cmd", 16);
# # Parse out variable assignment syntax, if any
# # %%% most of this can go away, and be replaced by looking at
# # $g_possible_assign and associated variables set by eval.2
# $setvar = "";
# if ($allow_assign == 0) {
# # We're an rvalue expression, do not attempt to assign anything
# } elsif ($cmd =~ m|^($p_var) *=([^=].*)$|) {
# # var = expr
# $setvar = $1;
# $cmd = $2;
# &dbg1("dc3.06 setvar2 $setvar $cmd", 16);
# } elsif ($cmd =~ m|^\(($p_var) *=([^=].*)\)$|) {
# # (var = expr) (This form is seen after !! substitution)
# $setvar = $1;
# $cmd = $2;
# &dbg1("dc3.07 setvar3 $setvar $cmd", 16);
# }
# $sverr = "";
# if (($setvar eq 'pi') || ($setvar eq 'e') || ($setvar eq 'phi')) {
# $sverr = "Attempt to set builtin constant";
# } elsif ($setvar =~ m/^[cr][0-9]+$/) {
# # %%% NOTE: command variables like "c1" have just been parsed out
# # by the call to expand.ih2 above, so this currently only catches
# # result vars like "r1"
# $sverr = "Attempt to set history value";
# }
# if ($sverr ne "") {
# if ($BASIC_running) {
# print "*** ";
# }
# print "ERROR: $sverr '$setvar'\n";
# $setvar = ""; $BASIC_running = 0;
# }
# Evaluate, and store result in LV (last value) registers.
($lv_pt, $lv_val, $lv_unc) = &eval_1($cmd);
if ($allow_assign && $g_possible_assign) {
# We just evaluated a valid assignment.
($lv_pt, $lv_val, $lv_unc) = ($g_assign_pt, $g_assign_val, $g_assign_unc);
&set_var($g_assign_lvk, $lv_pt, $lv_val, $lv_unc);
}
if ($pr>2) {
print "\n"; $lleft = 78;
}
if ($pr>2) {
$hname = &define_R_hist($lv_pt, $lv_val, $lv_unc);
print "$hname = ";
$lleft -= length("$hname = ");
}
# if ($setvar ne "") {
# &set_var($setvar, $lv_pt, $lv_val, $lv_unc);
# if ($pr>2) {
# print "$setvar: ";
# $lleft -= length("$setvar: ");
# }
# }
if (($pr>2) && $allow_assign && $g_possible_assign) {
print "$g_assign_lvk: ";
$lleft -= length("$g_assign_lvk: ");
}
if ($pr) {
&prnt1($lv_pt, $lv_val, $lv_unc, ($pr>2));
# 20080204: The following seems to produce lots of superfluous
# blank lines, so I removed it and added a newline just before
# the command prompt in the main loop.
# print "\n"; $lleft = 78;
}
if ($pr == 2) { print "\n"; $lleft = 78; }
} # End of do.cmd3
# po.override provides a way for the 'tron' command and pragma settings
# to alter how much gets printed out. It is used to remap the value of
# $punc in relevant places.
$g_por_from = ":;";
$g_por_to = ":;";
sub pr_override
{
my($p) = @_;
eval "\$p =~ tr/$g_por_from/$g_por_to/;";
return $p;
} # End of pr.override
sub cmd_pragma
{
my($args) = @_;
my($arg, $rest, $pr);
$pr = !($BASIC_running);
$args =~ s/ //g;
$args .= ',';
while($args =~ m/^([^,]+),(.*)$/) {
$arg = $1; $rest = $2;
if (0) {
} elsif ($arg eq 'notrace') {
# Suppress printing line numbers and assignments (TROFF command)
$g_por_to = ":;";
$g_BASIC_trace = 0;
print "pragma: trace disabled, g_por_to = '$g_por_to'\n" if($pr);
} elsif ($arg eq 'printassign') {
# Print assignments, (including from READ and FOR..NEXT) unless
# there is a ';'
$g_por_to = ':;';
print "pragma: normal mode, g_por_to = '$g_por_to'\n" if($pr);
} elsif ($arg eq 'quiet') {
# Never print assignments (normal behaviour for mose BASICs)
$g_por_to = ';;';
print "pragma: quiet mode, g_por_to = '$g_por_to'\n" if($pr);
} elsif ($arg eq 'trace') {
# Print all assignments (useful for a TRON command)
$g_por_to = '::';
$g_BASIC_trace = 1;
print "pragma: trace enabled, g_por_to = '$g_por_to'\n" if($pr);
}
$args = $rest;
}
} # End of cmd.pragma
# Set the scale (precision) to the indicated number of digits; set the default
# value if the argument is 0 or negative.
sub setscale
{
my($newscale, $prflag) = @_;
undef @igv;
if ($newscale <= 0) {
$newscale = $default_scale;
}
if ($inunix) {
# Limit the scale. This is important -- lots of fundamental
# assumptions in the PT algorithms fail if the number of
# significant digits in the mantissa is greater than the
# exponent value at which promotion occurs. Since promotion
# happens at 1.0e300, we have to limit the scale to a bit less
# than this.
if ($newscale > 295) {
print "I am setting scale to 295, the maximum allowed.\n" if ($prflag);
$newscale = 295;
}
$newprim = ($newscale > 14) ? "bc" : "nat";
if ($newprim ne $curprim) {
# close old functions
if ($curprim eq "bc") {
&fbc_close();
} else {
&f64_close();
}
# and open new ones
if ($newprim eq "bc") {
&fbc_init($newscale);
} else {
&f64_init();
}
} elsif ($newprim eq "bc") {
# re-scale
&fbc_rescale($newscale);
}
$curprim = $newprim;
$curscale = $newscale;
&hi_init($prflag);
} else {
print "Sorry, precision (scale) can only be changed in Cygwin or UNIX.\n" if ($prflag);
}
} # End of set.scale
# do.cmd4 handles special-variable setting (scale, debug, format) and passes
# everything else along to do.cmd3
#
# If $BASIC.running and $prflag is 0, printing is suppressed.
sub docmd4
{
my($cmd, $prflag) = @_;
my($cmdre, $a);
&dbg1("dc4a: $cmd", 16);
$cmdre = $cmd;
$cmdre =~ s|[^0-9a-zA-Z]||g;
# Special commands like #pragma# and #scale# go here
if (0) {
} elsif ($cmd =~ m|^pragma +(.+)$|) {
$a = $1;
&cmd_pragma($a);
} elsif ($cmd =~ m|^scale *\= *([0-9]+)|) {
$newscale = $1;
&setscale($newscale, $prflag);
} elsif ((length($cmdre) > 0) && ("quit" =~ m|^$cmdre|i)) {
$g_mainl_going = 0;
} else {
&docmd3($cmd, $prflag, 1);
}
} # End of do.cmd4
# Convert a number with ":" characters (sexagesimal integer) into a normal
# number
sub cv_60_10
{
my($e) = @_;
my($h, $m, $s);
if ($e =~ m|^([0-9]+):([0-9]+):([0-9.]+)$|) {
$h = $1; $m = $2; $s = $3;
$e = ($h * 60 + $m) * 60 + $s;
} elsif ($e =~ m|^([0-9]+):([0-9.]+)$|) {
$m = $1; $s = $2;
$e = $m * 60 + $s;
}
return $e;
} # End of cv.60_10
# do.cmd6 handles:
# '!!' (double exclamation point) repeat last typed input
# '[ ]' (square brackets) same as parentheses
# ':' (colon) command separator, or sexagesimal fraction
# ';' (semicolon) suppress printing the result
# and passes the rest to do.cmd4
sub docmd6
{
my($cmd) = @_;
my($c1, $c2, $c3, $punc, $prflag);
# Map [] onto () (Note: [] are used internally within the eval
# routines)
$cmd =~ tr|\[\]|\(\)|;
# map all alphabetics to lower case, so that we can use uppercase letters
# as symbols for functions etc.
$cmd = lc($cmd);
# handle ":" within numbers (sexagesimal fraction)
# NOTE: we only convert the integer portion, any fraction can be left as-is
# This allows uncertainty to work, e.g. "1:23.4(12)" is equivalent to
# "83.4(12)" which is 83.4 +- 1.2
$cmd =~ s/([0-9]+:[0-9]+:[0-9]+:[0-9]+)/&cv_60_10($1)/ge;
$cmd =~ s/([0-9]+:[0-9]+:[0-9]+)/&cv_60_10($1)/ge;
$cmd =~ s/([0-9]+:[0-9:]+)/&cv_60_10($1)/ge;
# Replace '!!' with previous typed command
&dbg1("dc6a: $cmd", 16);
$cmd = " " . $cmd;
$didit = 0;
while($cmd =~ m|^(.*)([- +*x/^(;:])\!\!(.*)$|) { # ) balance
$c1 = $1; $c2 = $2; $c3 = $3;
$cmd = "$c1$c2$lastc6in$c3";
if (!($BASIC_running)) {
if ($didit == 0) {
print "!!: $lastc6in\n";
$didit = 1;
}
print "C$ihnum: $cmd\n";
}
}
$cmd =~ s/^ +//;
$lastc6in = $cmd;
&dbg1("dc6b: $cmd", 16);
# Execute commands separated by ':' and/or ';'. A ';' separator prevents
# printing, but ";:' prints just the value
# (note that 'print' statements were already parsed out in do.cmd10)
while($cmd =~ m|^([^:;]*)([:;]+)(.*)$|) {
$c1 = $1;
$punc = $2;
$cmd = $3;
$punc = &pr_override($punc);
if (!($c1 =~ m/^ *$/)) {
$prflag = ($punc eq ':') ? 3 # print "V: val \n"
: (($punc eq ';:') ? 2 # print "val \n"
: 0); # print nothing
# print "dc6>> '$c1' with '$punc' $prflag\n";
&docmd4($c1, $prflag);
}
}
# Do the last one
if (!($cmd =~ m/^ *$/)) {
$punc = ':';
if ($cmd =~ m|([;:]+)|) {
$punc = $1;
}
$punc = &pr_override($punc);
$prflag = ($punc eq ':') ? 3 # print "V: val \n"
: (($punc eq ';:') ? 2 # print "val \n"
: 0); # print nothing
&docmd4($cmd, $prflag);
}
} # End of do.cmd6
# PRINT: Loop on arguments, evaluate and print
sub BASIC_print
{
my($c2) = @_;
my($f1, $f2, $v);
$c2 =~ s/ +$//;
# Make sure the arguments end with a formatting-separator: if ',' or ';'
# is not present, add "'" to signify a normal CRLF ending
if (!($c2 =~ m/[,;]$/)) {
$c2 .= "'";
}
$f2 = "";
while($c2 ne "") {
# print "Bpr1 c2 '$c2'\n";
# Get atoms separated by , or ; or final single-quote which means the
# print should end with a newline
$v = $f2;
if ($c2 =~ m|^ *"([^"]*)" *([,;'])(.*)$|) {
# literal string
$f1 = $1; $f2 = $2; $c2 = $3;
# print "Bpr2 f1 '$f1' f2 '$f2' c2 '$c2'\n";
print $f1;
if ($f2 eq ',') {
print "\t";
} elsif ($f2 eq "'") {
# We're at the end of the arguments and we want a CRLF.
print "\n";
}
} elsif ($c2 =~ m|^($p_var\([^\)]+\))([,;'])(.*)$|i) {
# Function or array reference with one set of parentheses
$f1 = $1; $f2 = $2; $c2 = $3;
# print "Bpr3 f1 '$f1' f2 '$f2' c2 '$c2'\n";
$f1 =~ s|^ +||; $f1 =~ s| +$||;
if ($f1 ne "") {
# It's an expression
# Map to lower case so we can use uppercase for functions, etc.
$f1 = lc($f1);
&docmd3($f1, 1, 0);
}
if ($f2 eq ',') {
print "\t";
} elsif ($f2 eq "'") {
# We're at the end of the arguments and we want a CRLF.
print "\n";
}
} elsif ($c2 =~ m|^([^,;']*)([,;'])(.*)$|) {
# Something else, hopefully an expression
$f1 = $1; $f2 = $2; $c2 = $3;
# print "Bpr4 f1 '$f1' f2 '$f2' c2 '$c2'\n";
$f1 =~ s|^ +||; $f1 =~ s| +$||;
if ($f1 ne "") {
# It's an expression
# Map to lower case so we can use uppercase for functions, etc.
$f1 = lc($f1);
&docmd3($f1, 1, 0);
}
if ($f2 eq ',') {
print "\t";
} elsif ($f2 eq "'") {
# We're at the end of the arguments and we want a CRLF.
print "\n";
}
} else {
$c2 = "";
}
}
} # End of BASIC.print
# Perform simple variable substitution for FOR loop limits. Only regular
# PT0 values with no uncertainty are allowed.
sub subst_var
{
my($v) = @_;
if ($vars_pt{$v} ne "") {
if (($vars_pt{$v} == 0) && ($vars_unc{$v} == 0)) {
$v = $vars_val{$v};
} else {
print "*** Illegal value '$v', in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
}
}
return $v;
} # End of subst.var
sub remove_comments
{
my($in) = @_;
my($out, $p1, $p2);
# print "remove_comments: '$in'\n";
$out = '';
while ($in ne '') {
if ($in =~ m/^([^'"]+)(['"].*)$/) {
# A piece, followed by ' or "
$p1 = $1; $p2 = $2;
# print " rc1 '$p1'$p2'\n";
$out .= $p1;
$in = $p2;
} elsif ($in =~ m/^' *pragma (.*)$/i) {
# The rest is a pragma
$in = $1;
# print " rc2 '$in\n";
&cmd_pragma($in);
$in = '';
} elsif ($in =~ m/^'/) {
# The rest is a comment
# print " rc3 '$in\n";
$in = '';
} elsif ($in =~ m/^("[^"]*")(.*)$/) {
# Quoted string
$p1 = $1; $p2 = $2;
# print " rc4 '$p1'$p2'\n";
$out .= $p1; $in = $p2;
} else {
# Shouldn't happen, but we do this just in case
$out .= $in;
# print " rcdflt '$in'\n";
$in = '';
}
}
return $out;
} # End of remove.comments
# Get one data item from a DATA statement and execute the assignment
# statement to put it into a variable
sub get_datum
{
my($v, $punc) = @_;
my($c2);
$punc = &pr_override($punc);
if ($BASIC_datum < $BASIC_ndat) {
$c2 = "$v = $BASIC_data[$BASIC_datum] $punc"; $BASIC_datum++;
&docmd6(lc($c2));
} else {
print "*** Out of data in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return -1;
}
} # End of get.datum
# This routine handles the runtime BASIC commands, e.g. the no-ops like
# PRINT and LET and flow control ops like FOR..NEXT
sub docmd10
{
my($cmd, $pc) = @_;
my($c2, $v, $f1, $f2, $prflag, $nv, $punc);
&dbg1("dc10a: $cmd", 16);
$cmd = &remove_comments($cmd);
if (0) {
} elsif ($cmd =~ m/^ *$/) {
# no-op; skip
} elsif ($cmd =~ m/^ *rem\b/i) {
# remark -- ignore
} elsif ($cmd =~ m/^ *'/) {
# remark -- ignore
} elsif ($cmd =~ m/^ *data /i) {
# DATA statements are not directly executed
} elsif ($cmd =~ m/^def +fn($p_var)\(($p_var)\) *= *(.*)$/i) {
$f1 = $1; $v = $2; $f2 = $3;
$v = lc($v);
# %%% for now this is simply ignored
} elsif ($cmd =~ m/^dim +$p_var\( *[0-9]+ *\) *$/i) {
# %%% for now we ignore, but we could theoretically remember the limits
# and enforce
} elsif ($cmd =~ m/^dim +$p_var\( *[0-9]+ *, *[0-9]+ *\) *$/i) {
} elsif ($cmd =~ m/^ *end *$/i) {
$BASIC_running = 0;
$inbline = '';
} elsif ($cmd =~ m/^for +($p_var) *= *([0-9_a-z]+) +to +([0-9_a-z]+)(;?)/i) {
$v = $1; $f1 = $2; $f2 = $3; $punc = $4;
$v = lc($v);
if ($punc eq '') { $punc = ':'; }
$punc = &pr_override($punc);
$prflag = ($punc eq ';') ? 0 : 2;
$f1 = &subst_var($f1);
$f2 = &subst_var($f2);
# print "FOR $v $f1 $f2\n";
$for_level++;
$next_var[$for_level] = $v;
# print "line $g_BASIC_pc next_var[$for_level] = '$v'\n";
$next_limit[$for_level] = $f2; $for_var_limit{$v} = $f2;
$next_loopto[$for_level] = $pc; $for_var_loopto{$v} = $pc;
$next_prflag[$for_level] = $prflag; $for_var_prflag{$v} = $prflag;
# convert to LET
$c2 = "$v = $f1";
&docmd6("$c2$punc");
} elsif ($cmd =~ m/^go ?to +([0-9]+) *$/i) {
$f1 = $1;
if ($BASIC{$f1} ne '') {
return $f1;
} else {
print "*** Undefined line number in GOTO\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
} elsif ($cmd =~ m/^gosub +([0-9]+) *$/i) {
$f1 = $1;
if ($BASIC{$f1} ne '') {
$substack[$gosub_level++] = $NEXT{$g_BASIC_pc};
return $f1;
} else {
print "*** Undefined line number in GOSUB in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
} elsif ($cmd =~ m/^if +(.+) +then +([0-9]+)[ :;]*$/i) {
# Original IF..THEN format (one-line, implied GOTO)
$f1 = $1; $f2 = $2;
# Evaluate the expression
&docmd6("if_result = ($f1);");
if (&subst_var("if_result")) {
# Take the branch
if ($BASIC{$f2} ne '') {
return $f2;
} else {
print "*** Undefined line number in IF..THEN in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
}
} elsif (($cmd =~ m/^(if) +(.+) +then[ :;]*$/i)
|| ($cmd =~ m/^(else) +if +(.+) +then[ :;]*$/i)
) {
# Structured multi-line IF
$f1 = $1; $f2 = $2;
if (lc($f1) eq 'if') {
# This is the first block
$IF_level++;
$IF_didit[$IF_level] = 0;
}
if ($IF_didit[$IF_level]) {
# We have already executed a section of this IF .. ELSE .. structure.
# jump to the next (we'll keep doing to until we reach the END IF)
$f1 = 0;
} else {
# Evaluate the expression
&docmd6("if_result = ($f2);");
$f1 = &subst_var("if_result");
}
if ($f1) {
# We want to execute this block. Treat this line as a comment and
# the caller (BASIC.run) goes to the following line.
$IF_didit[$IF_level] = 1;
} else {
# Jump to the next ELSE or to the END IF
$f2 = $IF_next{$pc};
if ($BASIC{$f2} ne '') {
return $f2;
} else {
print "*** IF clause at line $g_BASIC_pc lacks branch target (ELSE or END IF)\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
}
} elsif ($cmd =~ m/^ *else *$/i) {
# a bare ELSE clause always gets executed (unless we've already done a
# block of the current IF..THEN)
if ($IF_didit[$IF_level]) {
# Jump to the next condition, or to END IF
$f2 = $IF_next{$pc};
if ($BASIC{$f2} ne '') {
return $f2;
} else {
print "*** ELSE clause at line $g_BASIC_pc lacks END IF\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
}
# Else we'll just drop through and execute this block
$IF_didit[$IF_level] = 1;
} elsif ($cmd =~ m/^ *end +if *$/i) {
# END IF: If we got here we just treat it like a REM and the next line
# will get executed
if ($IF_level > 0) {
$IF_level--;
}
} elsif ($cmd =~ m/^ *input +($p_multvars)(;?) *$/i) {
# Input number(s) from TTY into variable(s). All input must be
# valid Hypercalc-format numbers
$f1 = $1 . ", "; $punc = $2; $f2 = '';
if ($punc eq '') { $punc = ':'; }
while ($f1 =~ m/^ *($p_var),($p_multvars)$/i) {
$v = $1; $f1 = $2;
if ($f2 eq '') {
if ($punc ne ';') {
# Normal INPUT prompt
print "? ";
}
# We need to get more numbers from the user
$f2 = <>; chomp $f2; $f2 .= ", ";
}
if ($f2 =~ m/^ *($p_rawarg),(.*)$/i) {
$c2 = "$v = $1;"; $f2 = $2;
&docmd6(lc($c2));
} else {
print "*** Bad format for INPUT in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
$f2 =~ s/^ *//;
}
$f1 =~ s/ +//;
if ($f1 ne '') {
print "*** Syntax error in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
if ($f2 ne '') {
print "*** Too much input -- excess ignored\n";
}
} elsif ($cmd =~ m/^ *let +(.*)([:;]?) *$/i) {
$c2 = $1; $punc = $2;
# if ($punc eq '') { $punc = ':'; }
$punc = &pr_override($punc);
$c2 = lc($c2);
# print ">> let '$c2' with '$punc'\n";
&docmd6("$c2$punc");
} elsif ($cmd =~ m/^ *next +($p_var) *([:;]?) *$/i) {
# convert to LET
$nv = $1; $punc = $2;
$nv = lc($nv);
if ($punc eq '') { $punc = ':'; }
$punc = &pr_override($punc);
$v = $next_var[$for_level];
# print " next $v (to $for_var_limit{$v})\n";
if (($nv ne $v) && $pragma_any_next) {
# We can do any NEXT that is currently pending...
$v = $nv;
if ($for_var_limit{$v} eq '') {
# ... but we can't use this $v either, no loop is defined!
print "*** NEXT without FOR in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
} elsif ($nv ne $v) {
print "*** NEXT using wrong variable ($nv but expected $v) in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
# Check printing choice given in the FOR line
$prflag = $for_var_prflag{$v};
# %%% we now allow the NEXT's punctuation to govern. Formerly we
# had: $punc = &pr_override($prflag ? ':' : ';'); - 20190505
$c2 = "$v = $v + 1";
&docmd6("$c2$punc");
# Evaluate the loop limit
# %%% this will require a PRAGMA, some BASICs only evaluate the loop
# limit once
&docmd6("next_limit = ($for_var_limit{$v});");
$f1 = &subst_var("next_limit");
# test for overflow
if ($vars_val{$v} > $f1) {
# we're done
$for_level--;
$for_var_prflag{$v} = '';
} else {
# jump back to loop
return $NEXT{$for_var_loopto{$v}};
}
} elsif ($cmd =~ m/^ *next +/i) {
print "*** NEXT without variable, line $g_BASIC_pc: '$cmd'\n";
$BASIC_running = 0;
$inbline = '';
return 0;
} elsif ($cmd =~ m/^ *print +(.*)$/i) {
$c2 = $1;
&BASIC_print($c2);
# %%% RANDOMIZE function
} elsif ($cmd =~ m/^ *read +(.+) *$/i) {
$f1 = $1;
if ($f1 =~ m/^(.+);$/) {
$f1 = $1; $punc = ';';
} else {
$punc = ':';
}
$punc = &pr_override($punc);
$f1 = $1 . ", "; $gg = 1;
while($gg) {
$gg = 0;
if ($f1 =~ m/^ *([^,\(]+),(.+)$/i) {
$v = $1; $f1 = $2; $gg = 1;
if (&get_datum($v, $punc) < 0) { return 0; }
} elsif ($f1 =~ m/^ *($p_var\([^\)]+\)),(.+)$/i) {
$v = $1; $f1 = $2; $gg = 1;
if (&get_datum($v, $punc) < 0) { return 0; }
}
$f1 =~ s/ +//;
}
if ($f1 ne '') {
print "*** Syntax error in line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
return 0;
}
} elsif ($cmd =~ m/^ *restore *$/i) {
# Restore DATA pointer, next READ statement will get the first datum
$BASIC_datum = 0;
# %%% RESTORE also had a variant that accepted a line number parameter
} elsif ($cmd =~ m/^ *return *$/i) {
if ($gosub_level <= 0) {
print "*** RETURN without GOSUB\n";
$BASIC_running = 0;
$inbline = '';
return 0;
} else {
$f1 = $substack[--$gosub_level];
return $f1;
}
} elsif ($cmd =~ m/^ *stop */i) {
# STOP is the same as END
$BASIC_running = 0;
$inbline = '';
} else {
# Anything else is taken as being a normal command.
$cmd = lc($cmd);
&docmd6($cmd);
}
return 0;
} # End of do.cmd10
# This routine scans through the BASIC program, setting up the pointers
# used for the flow control operators, indenting for LIST command, and a
# few simple error checks (like NEXT without FOR)
# %%% NOTE: We parse IF .. THEN and ELSE IF, but these are not yet
# handled by do.cmd10
sub BASIC_parse
{
my($ln, $prev, $cmd, $indent, $next_indent);
my($v, $f1, $f2);
# First we have to set up the next pointers
$lleft = 78;
$prev = "";
$next_indent = 0;
$IF_level = 0;
undef %IF_next;
@BASIC_data = ();
$BASIC_ndat = 0;
foreach $ln (sort {$a <=> $b} (keys %BASIC)) {
$indent = $next_indent;
if ($prev eq "") {
$BASIC_first = $ln;
} else {
$NEXT{$prev} = $ln;
}
$prev = $ln;
$cmd = $BASIC{$ln};
$cmd =~ s/^ *//;
$BASIC{$ln} = $cmd;
if ($cmd eq "") {
# no-op; skip
} elsif ($cmd =~ m/^data +($p_rawargs)$/i) {
# DATA statement: Parse and store values
$f1 = $1 . ", ";
while($f1 =~ m/^ *($p_rawarg),(.*)$/i) {
$v = $1; $f1 = $2;
$BASIC_data[$BASIC_ndat++] = $v;
}
$f1 =~ s/ +//;
if ($f1 ne '') {
print "*** Syntax error in DATA statement, line $ln\n";
return $ln;
}
} elsif ($cmd =~ m/^data /i) {
print "*** Syntax error in DATA statement, line $ln\n";
return $ln;
} elsif ($cmd =~ m/^for +($p_var) *= *([0-9a-z]+) +to +([0-9a-z]+)/i) {
$next_indent++;
$indtype[$next_indent] = "for";
$forline[$next_indent] = $ln;
} elsif ($cmd =~ m/^for /i) {
print "*** Syntax error in FOR statement, line $ln\n";
return $ln;
} elsif ($cmd =~ m/^ *next /i) {
if ($indent > 0) {
$indent--;
} else {
print "*** NEXT without FOR in line $ln\n";
return $ln;
}
$next_indent = $indent;
} elsif ($cmd =~ m/^if +.* +then +[0-9]+[ :;]*$/i) {
# One-liner IF..THEN, no change to indentation
} elsif ($cmd =~ m/^if +/i) {
$next_indent++;
$indtype[$next_indent] = "if";
$forline[$next_indent] = $ln;
$IF_level++;
$IF_ln[$IF_level] = $ln;
$IF_matched[$IF_level] = 0;
} elsif (($cmd =~ m/^else +if /i) || ($cmd =~ m/^else *$/i)) {
if ($IF_level > 0) {
$indent--; # Just this line will be unindented
} else {
print "*** ELSE without IF in line $ln\n";
return $ln;
}
$IF_next{$IF_ln[$IF_level]} = $ln;
$IF_ln[$IF_level] = $ln;
} elsif ($cmd =~ m/^else/i) {
print "*** ELSE should be bare or followed by IF in line $ln\n";
return $ln;
} elsif ($cmd =~ m/^end +if *$/i) {
if ($IF_level > 0) {
$indent--;
} else {
print "*** END IF without IF in line $ln\n";
return $ln;
}
$next_indent = $indent;
$IF_next{$IF_ln[$IF_level]} = $ln;
$IF_next{$ln} = 0;
$IF_level--;
} elsif ($cmd =~ m/^ *rem +/i) {
# remark -- ignore
} elsif ($cmd =~ m/^ *'/) {
# remark -- ignore
}
$BASIC_indent{$ln} = $indent;
}
if ($next_indent > 0) {
if ($indtype[$next_indent] eq "for") {
print "*** FOR without NEXT\n";
$listerrs .= "*** FOR in line $forline[$next_indent] without NEXT\n";
} else {
print "*** IF in line $forline[$next_indent] without END IF\n";
$listerrs .= "*** IF in line $forline[$next_indent] without END IF\n";
}
return $prev;
}
return 0;
} # End of BASIC.parse
my $use_hires = 0;
$module = "Time::HiRes";
eval {
eval "require Time::HiRes" or die;
Time::HiRes->import;
$use_hires = 1;
};
# $@ now contains error message text if any, e.g.
# "Can't locate Time/HiRes.pm in @INC ..."
if ($@ && $@ =~ /Time::HiRes/) {
$use_hires = 0;
}
# print "use_hires == $use_hires\n";
sub gtod
{
my($rv);
if ($use_hires) {
$rv = Time::HiRes::gettimeofday();
} else {
$rv = time;
}
# print "gtod->$rv\n";
return $rv;
}
sub BASIC_run
{
my($cmd, $err, $next);
my($t_start, $t_end, $n_cmds);
# Scan through the program to establish line numbering, target jump
# addresses for IF..THEN blocks, etc.
$err = &BASIC_parse();
if ($err) {
return;
}
# We'll trap ^C so the user can get out of an infinite loop
# NOTE: This does not work when we're using the #bc# module
$SIG{'INT'} = sub {
if ($BASIC_running) {
print "\n\n*** BREAK by user at line $g_BASIC_pc\n";
$BASIC_running = -1;
$inbline = '';
}
if ((time - $sigint_time) < 3) {
# They did it two times quickly, so we should exit
exit(-1);
} else {
print "(to force-quit Hypercalc, hit ^C again quickly)\n";
}
$sigint_time = time;
};
$g_BASIC_pc = $BASIC_first;
$BASIC_running = 1;
$n_cmds = 0; $t_start = >od();
$BASIC_datum = 0;
while($BASIC_running > 0) {
$inbline = "in BASIC line $g_BASIC_pc";
if ($g_BASIC_trace) {
print "$inbline\n";
}
# Fetch instruction
$cmd = $BASIC{$g_BASIC_pc};
# Execute command
$next = &docmd10($cmd, $g_BASIC_pc);
$n_cmds++;
# If we're still running, test for end of file
if ($BASIC_running) {
if ($next != 0) {
# docmd10 has explicitly selected a line to run next
$g_BASIC_pc = $next;
} else {
$g_BASIC_pc = $NEXT{$g_BASIC_pc};
}
if ($g_BASIC_pc eq "") {
print "*** Reached end of file without 'END' statement\n";
$BASIC_running = 0;
$inbline = "";
}
}
}
$t_end = >od();
if ($BASIC_running < 0) {
print "*** Break by user at line $g_BASIC_pc\n";
$BASIC_running = 0;
$inbline = '';
}
print sprintf("Time: %.3f secs.\n", $t_end - $t_start);
print "Steps: $n_cmds\n";
}
sub make_dir
{
my($dir) = @_;
my($d1, $d2);
# print "make_dir : '$dir'\n";
if (-d $dir) {
# all set
# print "'$dir' exists.\n";
} elsif (-e $dir) {
print STDERR "ERR03: Can't create directory $dir, file already there\n";
exit -1;
} else {
if ($dir =~ m|^(.+)/([^/]+)$|) {
$d1 = $1; $d2 = $2;
# print "recursive md '$d1'\n";
&make_dir($d1);
$dir = $d2;
}
# print "mkdir $dir\n";
system("mkdir $dir");
}
# At this point either the directory already exists or we just created it.
# print "chdir '$dir'\n";
chdir($dir);
} # End of make.dir
# Strip leading and trailing spaces from $pn, and if it is empty, prompt
# for a program name.
sub getprogname
{
my($pn, $cmdname) = @_;
$pn =~ s/^ *//; $pn =~ s/ *$//;
if ($pn eq "") {
print $cmdname; $pn = <>; chop $pn;
$pn =~ s/^ *//; $pn =~ s/ *$//;
if ($pn =~ m/^$p_progname$/i) {
# okay
} elsif ($pn eq '') {
# They're just cancelling.
return $pn;
} else {
print "*** Program name must be like 'a-bc_123'\n";
return "";
}
}
return $pn;
} # End of get.progname
sub go_basic_dir
{
my($bd, $ans);
$bd = "shared/proj/hypercalc";
chdir;
if (-d $bd) {
chdir($bd);
} else {
print "In order to use this feature I need to create a directory at '$hd/$bd'.\n";
print "Is this okay? (Y/N) : ";
$ans = <>; chomp $ans; $ans =~ s/^[ \t]*//;
if ($ans =~ m/^[yY]/) {
&make_dir($bd);
} else {
print STDERR "Exiting.\n";
exit(1);
}
}
} # End of go.basic.dir
# Save current BASIC program (prompt for filename if current file
# doesn't have a name)
sub BASIC_save
{
my($pn) = @_;
my($ln, $nl);
if ($pn eq "") {
$pn = $PROGNAME;
}
$pn = &getprogname($pn, "Name to save as: ");
if ($pn eq "") {
print "Not saving.\n";
return;
}
$PROGNAME = $pn;
&go_basic_dir();
open(SAVE, "> $pn.basic");
$nl = 0;
foreach $ln (sort {$a <=> $b} (keys %BASIC)) {
print SAVE "$ln $BASIC{$ln}\n";
$nl++;
}
close SAVE;
print "Saved $nl lines to $pn.basic\n";
$g_BASIC_unsaved = 0;
} # End of BASIC.save
# Catalog (list all saved BASIC programs)
sub BASIC_cat
{
my($l);
my(@a);
my($i, $n, $ml, $cols, $ch, $c, $j);
&go_basic_dir();
open(CAT, "ls |");
$n = 0; $ml = 0;
while ($l = ) {
chop $l;
if ($l =~ m/^(.+)\.basic$/) {
$l = $1;
$a[$n++] = $l;
if (length($l) > $ml) { $ml = length($l); }
}
}
close CAT;
for ($cols=1; ($ml+2)*($cols+1)<=80; $cols++) { }
$ch = int(($n+$cols+1)/$cols); # column height
# print " n=$n ml=$ml ch=$ch cols=$cols fmt:%-${ml}s\n";
for ($i=0; $i<$ch; $i++) {
for ($c=0; $c<$cols; $c++) {
$j = $c*$ch + $i;
if (($j+$ch) < $n) {
print sprintf("%-${ml}s ", $a[$j]);
} elsif ($j<$n) {
print $a[$j];
}
if ($c+1 == $cols) {
print "\n";
}
}
}
} # End of BASIC.cat
# List the BASIC program
sub BASIC_list
{
my($err, $ln);
$listerrs = '';
$err = &BASIC_parse();
foreach $ln (sort {$a <=> $b} (keys %BASIC)) {
print ( (($ln == $err) ? ">> " : "")
. sprintf("%4d ", $ln) . (" " x $BASIC_indent{$ln})
. "$BASIC{$ln}\n");
}
if ($listerrs ne '') {
print $listerrs;
$listerrs = '';
}
}
# Load a BASIC program and set the current file name
sub BASIC_old
{
my($pn) = @_;
my($l, $ln, $cmd, $nl);
if ($pn eq "") {
$pn = $PROGNAME;
}
$pn = &getprogname($pn, "Name of program to load: ");
if ($pn eq "") {
print "Not loading";
$nl = scalar(keys(%BASIC));
if ($nl) {
print "; current program unchanged";
}
print ".\n";
return;
}
&go_basic_dir();
if ((-e $pn) && ($pn =~ m/^(.+)\.basic$/)) {
$PROGNAME = $1;
} elsif (-e "$pn.basic") {
$PROGNAME = $pn;
$pn = "$pn.basic";
}
if (-e $pn) {
undef %BASIC; $nl = 0;
open(my $OLD, $pn);
while($l = <$OLD>) {
chomp $l;
$l =~ s/\t/ /g;
$l =~ s/^ +//;
$l =~ s/ +$//;
if ($l eq '') {
} elsif ($l =~ m/^([0-9]+) (.*)$/) {
$ln = $1; $cmd = $2;
$ln += 0;
$BASIC{$ln} = $cmd; $nl++;
} else {
print "*** syntax error: '$l'\n";
}
}
close $OLD;
print "Loaded $nl lines from $pn\n";
} else {
print "*** OLD: '$pn' not found.\n";
}
} # End of BASIC.old
sub mainloop
{
my($err);
$lleft = 78;
print "$GPLNOTICE$tagline\n";
$a2_pt = 0; $a2_val = 0; $a2_unc = 0;
$dbg1flag = 0;
$g_mainl_going = 1;
$ihnum = 1;
while($g_mainl_going) {
$g_logfile->flush;
print "\nC$ihnum = ";
if ($cmdline eq "") {
$cmd = ; chop $cmd;
} else {
$cmd = $cmdline;
$cmdline = "";
print "$cmd\n";
}
if ($inunix) {
$cmd = &fixerase($cmd);
}
&dbg1("ml.1: I$ihnum = $cmd", 16);
$cmd =~ s|\t| |g;
$cmd =~ s|^ +||;
if ($cmd eq '') {
$cmd = $default_command;
$default_command = '';
}
$cmd =~ s|^\?|help |;
$cmdre = $cmd;
$cmdre =~ s|[+*?\(\)\`\'\[\]\{\}\\\|]||g;
$not_yet_handled = 0;
if ($cmdre eq "") {
# nothing
} elsif ($cmd =~ m/^ *help +gpl/i) {
open(my $HELPOUT, "| more");
print $HELPOUT $GPL_VERSION_3;
close $HELPOUT;
} elsif ($cmd =~ m/^ *help +manpage/i) {
print $HC_MANPAGE;
} elsif ($cmd =~ m/^ *help +syn/i) {
print "
Hypercalc syntax
:= [ ';' ]
:= '!!'
| [ '=' ]
|
|
| 'quit'
:=
|
|
|
|
| '(' ')'
| '(' ')'
:= 'c'
| 'r'
| ('a'-'z') [ ('a'-'z', '0'-'9') ... ]
| '.' | '%' | 'e' | 'pi' | 'phi'
:= [ '-' ] [ 'p' ]
[ '.' ('0'-'9') ... ]
[ '(' ('0'-'9') ... ')' ]
[ 'e' ]
:= '!'
:= '-'
:= '+' | '-' | '*' | 'x' | '/' | '^' | '**'
Here's an example. This computes the solution of x^x=10 using Newton's
Method. The labels 'c1:', 'c2:' etc. are assigned by HyperCalc, you
don't type those:
c1: x = 2
c2: t = 10
c3: y = x^x
c4: dy = y * (1 + ln(x))
c5: x = x + (t-y)/dy
c6: c3
c7: c4;c5
c9: c3;c4;c5
c12: !!
c15: !!;!!
Type 'help ops' for details on operators, 'help fun' for details on
functions, and 'help var' for details on the special variables..
";
$default_command = "help numbers";
} elsif ($cmd =~ m/^ *help +num/i) {
print "
There are several syntaxes for specifying numbers in Hypercalc.
Large numbers can be entered concisely using 'scientific notation':
1.2e3456
Which means 1.2 times 10 to the power of 3456, as if you had typed:
1.2 x 10 ^ 3456
Even larger numbers like googolplex and Skewes number can be entered
like this:
2P100
3p34
which are equivalent to:
10 ^ 10 ^ 100
10 ^ 10 ^ 10 ^ 34
The 'p' representing 'powers of 10' is used to insert multiple
instances of '10 ^' at the beginning of the number. Exponentiation is
right-associative, so when you input these numbers you get:
10 ^ ( 1 x 10 ^ 100 )
10 ^ [ 10 ^ ( 1 x 10 ^ 34 ) ]
You can also input numbers in base-60 (sexagesimal) using colons ':'
to separate the base-60 digits.
12:34:56.78
This is equivalent to ((12*60)+34)*60+56.78 = 45296.78. For base-60
output, type 'format=7'.
";
$default_command = "help op";
} elsif ($cmd =~ m/^ *help +op/i) {
print "
Hypercalc operators, listed in precedence order:
op assoc description
----- ----- ---------------------------------
() parentheses around subexpression
! factorial or gamma(x+1)
- negation, as in 'sin(-x)'
** ^ right exponent (** = ^)
x * / left multiply (x = *) and divide
% left modulo (remainder)
+ - left add and subtract
< <= left less than, less or equal
> >= left greater than, greater or equal
<> == left unequal, equal
not logical negation
and logical conjunction
or logical disjunction
= assign value to variable
^ is right-associative: a^b^c is equivalent to a^(b^c)
others are left-associative: a-b-c is equivalent to (a-b)-c
The comparison operators (<, <=, >, >=, <>, and ==) produce a value
of 0 or 1 according as the comparison is false or true. For example,
''3>2'' gives the value 1 because 3 is greater than 2, and
''1p1e3>1.0e2000'' gives the value of 0 because 10^10^3 is not greater than
10^2000.
";
$default_command = "help fun";
} elsif ($cmd =~ m/^ *help +fun/i) {
print "
Hypercalc functions:
sqrt(x) square root of x
exp(x) e to the power of x
int(x) integer part of x
ln(x) log base e of x
log(x) log base 10 of x
logn(b,x) log base b of x
root(r,x) r-th root of x
sin(x) sine of x (radians)
cos(x) cosine of x
arctan(x) arctangent of x
See also 'help ops' (operator symbols)
";
$default_command = "help var";
} elsif ($cmd =~ m/^ *help +var/i) {
print qq@
Hypercalc has three predefined irrational constants:
pi 3.141592653589...
e 2.718281828459...
phi (1+sqrt(5))/2, the 'golden ratio'
and several names for huge powers of 10:
decillion 10^33
vigintillion 10^63
googol 10^100
centillion 10^303
millillion 10^3003
milli_millillion 10^3000003
sand_reckoner 10^(8*10^16)
googolplex 10^(10^100)
skewes 10^(10^(10^34))
To define your own constants, type something like this:
c = 2.99 * 10^8
Constants can be changed, and used as variables in iterated commands
(like the Newton's method example, see 'help syn') and in BASIC
programs (see 'help basic')
Hypercalc automatically defines 'history variables' every time you type
a command and get a result. Type 'help hist' for details about these.
The following special variables can be set:
scale Number of digits to use in calculations (normally 14). The digits
are shared between the mantissa and exponent.
Example: scale = 100
format Printout format (several options, 'help format' for a list)
Example: format = 2
The effect of setting a special variable inside a BASIC program
is undefined.
@;
$default_command = "help history";
} elsif ($cmd =~ m/^ *help +hist/i) {
print "
History variables:
Commands are assigned to the variables 'c1', 'c2', 'c3', etc. in the
order you give them. A 'command' is a single assignment or calculation,
and note there can be multiple commands in a line of input separated
by ':' and ';'. Thus if you type
t = 7 ; t ^ t
Hypercalc will define 'c1' to be 't = 27' and 'c2' to be 't ^ t'.
This input computes the value of 7^7. If you then type
t = 27 ; c2
It changes the value of t to 27, then computes and computes 27^27,
which is approximate (4.4342648824304 x 10 ^ 38)
Results are also assigned to variables. The first part of the previous
example causes Hypercalc to print 'r2 = 823543' indicating that the
variable r2 holds the result 823543. Note that results have a fixed
precision corresponding to the scale setting at the time the calculation
was made, but after changing scale you can repeat a command with command
history and get a more precise result. Thus:
scale = 50 ; r4 ; c2
will re-display the last result as '443426488243042000000000000000000000000'
exposing the limited precision of the original calculation, but the 'c2'
command then repeats the calculation of t^t to 50 digits of precision,
giving the precise answer 443426488243037769948249630619149892803.
There are also a few special history variables, that get redefined with each
input:
% The last result (like in Maxima, but same as '.').
. The last result (like in bc, but same as '%').
!! The last input line (which may be more than one command).
";
$default_command = "help format";
} elsif ($cmd =~ m/^ *help +for/i) {
print "
Values of the 'format' special variable:
1 human-readable ASCII (the default at startup)
2 RHTF, the RILYBOT HyperText Format
3 HTML
4 Old-style numbers.rhtf label
5 Current numbers.rhtf label and link
6 Internal PT-VAL-UNC representation
7 Base 60 (HH:MM:SS.FRAC)
";
$default_command = "help unc";
} elsif ($cmd =~ m/^ *help +unc/i) {
print "
Uncertainty Calculations:
Hypercalc can calculate with values that include an uncertainty or
'error' quantity. To specify the amount of uncertainty, add digits
within parentheses, for example:
123(5) 123 +- 5
12.3(11) 12.3 +- 1.1
1.231(143)e10 1.231e10 +- 1.43e9
When values with uncertainty are used in a calculation, the result
of the calculation includes the correct uncertainty amount.
Examples:
10.0(1) + 10.0 = 20.0(10)
10.0(1) * 2.0 = 20.0(20)
10.0(1) * 10.0(1) = 100.0(20)
";
$default_command = "help basic";
} elsif ($cmd =~ m/^ *help +basic/i) {
print qq@
BASIC programming commands:
10 Any input line starting with a number and a word, with no intervening
symbol, is taken to be a BASIC input line and is added to the current
program. A line can be any valid expression or variable assignment
command, and the result will be printed.
To make a variable assignment without printing the value, start the
line with 'let' or end it with ';'.
To insert a BASIC line that evaluates and prints an expression
starting with a number or symbol, make it a PRINT statement.
To REMOVE a line that you no longer want, type just the line number
followed by a single space. You might also choose to re-type the line
with a ' character just after the line number, which makes the line
into a comment, and allows you to remember what code you had there
in case you want to change it back later.
let Assign a value to a variable, without printing the result
print
Give one or more expressions and/or literal strings separated by
',' or ';'.
for..next loops may be nested in a program. Put a ';' at the end of the
for statement to prevent it from printing its value each time through.
if x+2>3 then 70
Go to line 70 if x+2 is greater than 3. This is currently the only
type of IF..THEN that is supported.
if x+3>7 then
Beginning of a multi-line IF..THEN..ELSE..END IF statement. See below
for an example.
else
Marks the second section of an IF..THEN..ELSE..END IF
end if
Marks the end of an IF..THEN..ELSE..END IF; see example below.
data 1, 2, -3, 7
Provide values for use by READ statements
read a1, a2
Take the next two numbers in DATA statements and assign them to a1
and a2. Put a ';' at the end of the READ statement to prevent it from
printing "R1 = a1: 1", etc. for each item assigned.
rem Comment (must be on a line by itself)
' Comment (must be on a line by itself)
end Stop program execution
Here is an example:
5 ' Calculate Hugo Steinhaus' number "Mega"
10 let mega=256;
20 for n=1 to 256;
40 let mega = mega ^ mega;
80 next n
160 print "Mega = "; mega
320 end
Here is a program that tells you if your number is odd or even:
10 print "What is your number";
20 input x
30 if x mod 2 = 1 then
40 print "Your number is odd."
50 else
60 print "Your number is even."
70 end if
99 end
There are currently no subroutines or user-defined functions.
list List the current program (will automatically indent loop bodies)
cat List saved files
new Erase the current program
run Run the program
save Save the current file
old Load a saved program
@;
$default_command = "help debug";
} elsif ($cmd =~ m/^ *help +debug/i) {
print "
Debug bitmask fields:
1 function calls; eval\_1; expand; docmd
2 I and R history; eval\_2
4 I/O to child #bc# process
8 expression parsing
16 High-level flow control and history substitution
32 Infinity and NAN handling
Add the values to choose multiple options; for example 'debug 5'
enables options 1 and 4.
";
# This is the last page of help
# If adding another, should set $default_command here.
} elsif ($cmd =~ m|^help|) {
# This test must be after all the specific help pages above
print "
Example Description
--------------- -----------------------------------------------
3 * 4 + 2 Multiply and add (answer 14)
2+3x4 Same answer, spaces are optional, x = *
% + 2 Add 2 to previous answer
4-3-2 Gives -1 (left-to-right)
2^2^3 Gives 256 (right-to-left)
27! Factorial of 27
27!!! Same as ((27!)!)!
1.2 x 10 ^ 3456 Entering a large number
1.2e3456 Same number in scientific notation
2P100 Googolplex (P = 'powers of 10', '2P' -> 10^10^)
6.022(1)e23 6.022x10^23 with an uncertainty of 1 in the 4th digit
c5 Repeat command C5 (as if you retyped it)
c6;c7;c8 Repeat three typed commands in a row
!! Repeat last typed line
q quit
debug 17 set debug bitmask
For more help:
help syn Detailed description of command syntax
help num Number formats in Hypercalc
help ops Complete list of operators (symbols)
help fun Complete list of named functions
help var Complete list of special variables
help hist History variables
help format Output formats available through the format=N command
help unc Computing with values that have a known uncertainty
help basic Hypercalc's built-in BASIC interpreter
help debug Available debugging options
You can see all of these help pages by just hitting a few times...
";
$default_command = "help syntax";
} else {
# If they didn't give a help command, we clear the default command.
$not_yet_handled = 1;
$default_command = '';
}
if ($not_yet_handled == 0) {
} elsif ($cmd =~ m|^debug ([0-9]+)|) {
$dbg1flag = $1;
print "Debug flag set to $dbg1flag.\n";
} elsif ($cmd =~ m|^format *\= *([0-9]+)|) {
print "Setting format.\n";
&format1($1);
print "\n";
} elsif ($cmd =~ m/^ *([0-9]+) +(x *\=.*)$/) {
# A number followed by "x =..." : This is a BASIC program line
$ln = $1; $text = $2; $BASIC{$ln} = $text; $g_BASIC_unsaved++;
} elsif ($cmd =~ m/^ *([0-9]+) +x[^a-z]/) {
# A number followed by 'x': This is a calculation
&docmd6($cmd);
} elsif ($cmd =~ m/^ *([0-9]+) +([a-z].*)$/i) {
# A number followed by a word: This is a BASIC program line
$ln = $1; $text = $2; $BASIC{$ln} = $text; $g_BASIC_unsaved++;
} elsif ($cmd =~ m/^ *([0-9]+) +('.*)$/i) {
# A number followed by ': This is a BASIC program line containing
# a comment
$ln = $1; $text = $2; $BASIC{$ln} = $text; $g_BASIC_unsaved++;
} elsif ($cmd =~ m/^ *([0-9]+) $/) {
# A number only followed by a single space: Remove a line from
# the BASIC program
$ln = $1;
delete $BASIC{$ln}; $g_BASIC_unsaved++;
} elsif ($cmd =~ m/^ *[.0-9]+/) {
# Anything else starting with a number is a calculation
&docmd6($cmd);
} elsif ($cmd =~ m/^ *([0-9]+) +(.*)$/) {
# Input (or modify) a line for a BASIC program
$ln = $1; $text = $2;
$BASIC{$ln} = $text; $g_BASIC_unsaved++;
} elsif ($cmd =~ m/^ *list *$/i) {
&BASIC_list()
} elsif ($cmd =~ m/^ *cat *$/i) {
# Catalog
&BASIC_cat();
} elsif ($cmd =~ m/^ *troff *$/i) {
# TRace OFF
&cmd_pragma("notrace");
} elsif ($cmd =~ m/^ *tron *$/i) {
# TRace ON
&cmd_pragma("trace");
} elsif ($cmd =~ m/^ *new *$/i) {
# NEW without name
if (($g_BASIC_unsaved == 0) ||
&yorn("There are $g_BASIC_unsaved unsaved BASIC changes."
. " Confirm NEW command: ") )
{
$PROGNAME = "";
undef %BASIC;
$g_BASIC_unsaved = 0;
}
} elsif ($cmd =~ m/^ *new +($p_var) *$/i) {
# NEW with name
if (($g_BASIC_unsaved == 0) ||
&yorn("There are $g_BASIC_unsaved unsaved BASIC changes."
. " Confirm NEW command: ") )
{
$PROGNAME = $1;
undef %BASIC;
$g_BASIC_unsaved = 0;
}
} elsif ($cmd =~ m/^ *new/i) {
# NEW with illegal parameter
print "*** NEW: Program name must be alphanumeric\n";
} elsif ($cmd =~ m/^ *run *$/i) {
# Run the BASIC program
&BASIC_run();
} elsif ($cmd =~ m/^ *save (.*)$/i) {
&BASIC_save($1);
} elsif ($cmd =~ m/^ *save *$/i) {
&BASIC_save("");
} elsif ($cmd =~ m/^ *(old|run) ([^ ]*)$/i) {
$cmd = $1;
$ln = $2;
if (($g_BASIC_unsaved == 0) ||
&yorn("There are $g_BASIC_unsaved unsaved BASIC changes."
. " Confirm OLD command: ") )
{
&BASIC_old($ln);
$g_BASIC_unsaved = 0;
}
if ((lc($cmd) eq 'run') && ($g_BASIC_unsaved == 0)) {
&BASIC_run();
}
} elsif ($cmd =~ m/^ *old *$/i) {
if (($g_BASIC_unsaved == 0) ||
&yorn("There are $g_BASIC_unsaved unsaved BASIC changes."
. " Confirm OLD command: ") )
{
&BASIC_old('');
$g_BASIC_unsaved = 0;
}
} else {
# assignment and/or expression
&docmd6($cmd);
}
}
} # End of main.loop
$| = 1;
$0 = "Hypercalc";
$dbg1flag = 0;
# Special case for -h or --help argument
$a0 = $ARGV[0]; if ($a0 =~ m/^-[-]?h(elp)?$/) { print $HC_MANPAGE; exit(0); }
# Any more arguments get catenated together into a single line of input
# to the interpreter. This could be an expression, initial variable assignment,
# or something like "run foobar.basic"
$cmdline = "";
while ($arg = shift) {
$cmdline .= "$arg ";
}
$cmdline =~ s/ $//;
$inunix = (-d "/usr") + 0;
$inwindows = (exists $ENV{"WINDIR"}) ? 1 : 0;
$incygwin = (`uname` =~ m/CYGWIN/) + 0;
$invms = (lc($^O) eq "vms") + 0;
if ($incygwin) {
# Cygwin is UNIX-y enough for our needs.
$inunix = 1; $inwindows = 0;
}
# Check to make sure that we detected exactly one operating system
$osdetect_total = $inunix + $inwindows + $invms;
if ($osdetect_total != 1) {
print (
"Warning: Hypercalc cannot determine operating system (U=$inunix,W=$inwindows,C=$incygwin,V=$invms).\n"
. "Please email the author, 'mrob at mrob.com', include the full text of this\nmessage, and he'll try to fix this.\n\n"
);
}
if ($inunix) {
&seterase();
}
chdir;
# If the user has a ~/tmp directory we put our log there; otherwise no log.
if (-d "tmp") {
open($g_logfile, ">", "tmp/hc_log.txt");
$loghdr = qq@# Hypercalc log file -- ~/tmp/hc_log.txt
# This file contains debugging information generated by Hypercalc. It can
# be used to find errors in Hypercalc's parsing or evaluation algorithms.
#
@;
print $g_logfile $loghdr;
} else {
open($g_logfile, ">", "/dev/null");
}
$bc_running = 0;
# We start with a scale of 14 and use native floating-point primitives.
$default_scale = 14;
$curscale = $default_scale;
$doround = 1;
$curprim = "nat";
$g_inf = 1.0e300; $g_inf=$g_inf*$g_inf; $g_inf=$g_inf*$g_inf;
$g_inf=$g_inf*$g_inf; $g_inf=$g_inf*$g_inf; $g_inf=$g_inf*$g_inf;
&f64_init();
&hi_init(2);
# Init the printing/formatting routines
&format1(1);
&mainloop();
if ($curprim eq "bc") {
&fbc_close();
} else {
&f64_close();
}
close $g_logfile;