commit d445e3d9060ce770b8f8decc2e6587a7b9aefd8b Author: cutefishd Date: Tue Mar 16 13:42:05 2021 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98bda1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +build/* +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c46c8cd --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.5) + +set(PROJECT_NAME cutefish-calculator) +project(${PROJECT_NAME}) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(QT Core Gui Quick QuickControls2 LinguistTools) +find_package(Qt5 REQUIRED ${QT}) + +set(SRCS + main.cpp + calcengine.cpp + engine/constants.cpp + engine/evaluator.cpp + engine/functions.cpp + engine/hmath.cpp + engine/number.c +) + +set(RESOURCES + qml.qrc +) + +add_executable(${PROJECT_NAME} ${SRCS} ${RESOURCES}) +target_link_libraries(${PROJECT_NAME} + Qt5::Core + Qt5::Quick + Qt5::QuickControls2 +) + +file(GLOB TS_FILES translations/*.ts) +qt5_create_translation(QM_FILES ${TS_FILES}) +add_custom_target(translations DEPENDS ${QM_FILES} SOURCES ${TS_FILES}) +add_dependencies(${PROJECT_NAME} translations) + +install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION /usr/bin) + +install(FILES + cutefish-calculator.desktop + DESTINATION /usr/share/applications/ + COMPONENT Runtime +) + +install(FILES ${QM_FILES} DESTINATION /usr/share/${PROJECT_NAME}/translations) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + 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 + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc2f639 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Calculator + +CutefishOS Calculator + +![](screenshots/screenshot.png) + +## Dependencies + +On ArchLinux: + +```shell +sudo apt-get install cmake gcc qtbase5-dev qtdeclarative5-dev qml-module-qtquick2 qml-module-qtquick-controls2 +``` + +## Build and Install + +``` +mkdir build +cd build +cmake .. +make +sudo make install +``` + +## License + +This project has been licensed by GPLv3. \ No newline at end of file diff --git a/calcengine.cpp b/calcengine.cpp new file mode 100644 index 0000000..ee2e417 --- /dev/null +++ b/calcengine.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 CutefishOS. + * + * Author: revenmartin + * + * 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 + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "calcengine.h" +#include + +CalcEngine::CalcEngine(QObject *parent) + : QObject(parent), + m_evaluator(new Evaluator) +{ +} + +QString CalcEngine::eval(const QString &expr) +{ + m_evaluator->setExpression(expr); + m_evaluator->eval(); + + if (m_evaluator->error().isEmpty()) { + return QString(HMath::formatFixed(m_evaluator->eval())); + } else { + qDebug() << m_evaluator->error(); + emit failed(QObject::tr("Error: %1").arg(m_evaluator->error())); + } + + return ""; +} diff --git a/calcengine.h b/calcengine.h new file mode 100644 index 0000000..3822185 --- /dev/null +++ b/calcengine.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 CutefishOS. + * + * Author: revenmartin + * + * 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 + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CALCENGINE_H +#define CALCENGINE_H + +#include +#include "engine/evaluator.h" + +class CalcEngine : public QObject +{ + Q_OBJECT + +public: + explicit CalcEngine(QObject *parent = nullptr); + + Q_INVOKABLE QString eval(const QString &expr); + +signals: + void failed(const QString &errorString); + +private: + Evaluator *m_evaluator; +}; + +#endif // CALCENGINE_H diff --git a/cutefish-calculator.desktop b/cutefish-calculator.desktop new file mode 100644 index 0000000..8d9a0ca --- /dev/null +++ b/cutefish-calculator.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Calculator +Name[zh_CN]=计算器 +Exec=cutefish-calculator +Icon=calculator +Terminal=false +Type=Application +StartupNotify=true +Categories=Calculator; diff --git a/engine/constants.cpp b/engine/constants.cpp new file mode 100644 index 0000000..acb0323 --- /dev/null +++ b/engine/constants.cpp @@ -0,0 +1,59 @@ +/* Copyright (C) 2007 Ariya Hidayat + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "constants.h" + +Constants::Constants(QObject* parent): QObject(parent) +{ + setObjectName("Constants"); + + // http://en.wikipedia.org/wiki/Physical_constant#Table_of_atomic_and_nuclear_constants + constantList += Constant(QString("Bohr radius"), "0.5291772108e-10", "m", QString("Atomic & Nuclear")); + constantList += Constant(QString("Fermi coupling constant"), "1.16639e-5", "Ge/V^2", QString("Atomic & Nuclear")); + constantList += Constant(QString("fine-structure constant"), "7.297352568e-3", QString(), QString("Atomic & Nuclear")); + constantList += Constant(QString("Hartree energy"), "4.35974417e-18", "J", QString("Atomic & Nuclear")); + constantList += Constant(QString("quantum of circulation"), "3.636947550e-4", "m^2/s", QString("Atomic & Nuclear")); + constantList += Constant(QString("Rydberg constant"), "10973731.568525", "/m", QString("Atomic & Nuclear")); + constantList += Constant(QString("Thomson cross section"), "0.665245873e-28", "m^2", QString("Atomic & Nuclear")); + constantList += Constant(QString("weak mixing angle"), "0.22215", QString(), QString("Atomic & Nuclear")); + + // http://www.astronomynotes.com/tables/tablesa.htm + constantList += Constant(QString("astronomical unit"), "149597870.691", "km", QString("Astronomy")); + constantList += Constant(QString("light year"), "9.460536207e12", "km", QString("Astronomy")); + constantList += Constant(QString("parsec"), "3.08567802e13", "km", QString("Astronomy")); + constantList += Constant(QString("sidereal year"), "365.2564", "days", QString("Astronomy")); + constantList += Constant(QString("tropical year"), "365.2422", "days", QString("Astronomy")); + constantList += Constant(QString("Gregorian year"), "365.2425", "days", QString("Astronomy")); + constantList += Constant(QString("Earth mass"), "5.9736e24", "kg", QString("Astronomy")); + constantList += Constant(QString("Sun mass"), "1.9891e30", "kg", QString("Astronomy")); + constantList += Constant(QString("mean Earth radius"), "6371", "km", QString("Astronomy")); + constantList += Constant(QString("Sun radius"), "6.96265e5", "km", QString("Astronomy")); + constantList += Constant(QString("Sun luminosity"), "3.827e26", "W", QString("Astronomy")); + + for (int k = 0; k < constantList.count(); k++) { + QStringList cats = constantList[k].categories; + + for (int i = 0; i < cats.count(); i++) { + if (!categoryList.contains(cats[i])) { + categoryList += cats[i]; + } + } + } + + categoryList.sort(); +} diff --git a/engine/constants.h b/engine/constants.h new file mode 100644 index 0000000..32ea689 --- /dev/null +++ b/engine/constants.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2007 Ariya Hidayat + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef CONSTANTS +#define CONSTANTS + +#include +#include +#include +#include + +class Constant +{ +public: + QString name; + QString value; + QString unit; + QStringList categories; + + Constant(const QString& n, const QString& v, const QString& u, const QStringList& cat): + name(n), value(v), unit(u), categories(cat) {} + + Constant(const QString& n, const QString& v, const QString& u, const QString& cat): + name(n), value(v), unit(u) { categories << cat; } + + Constant(const QString& n, const QString& v, const QString& u, const QString& cat1, const QString& cat2): + name(n), value(v), unit(u) { categories << cat1; categories << cat2; } + +}; + +// Dummy placeholder to hold all constants + +class Constants : public QObject +{ + Q_OBJECT + +public: + explicit Constants(QObject* parent); + + QList constantList; + QStringList categoryList; +}; + +#endif // CONSTANTS diff --git a/engine/evaluator.cpp b/engine/evaluator.cpp new file mode 100644 index 0000000..c5be373 --- /dev/null +++ b/engine/evaluator.cpp @@ -0,0 +1,1469 @@ +/* Copyright (C) 2004 Ariya Hidayat + 2005-2006 Johan Thelin + 2007 Helder Correia + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "evaluator.h" +#include "functions.h" +#include "opcode.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define qApp QCoreApplication::instance() + +class EvaluatorPrivate +{ +public: + Evaluator *evaluator; + bool dirty; + bool valid; + QString expression; + + QString error; + QMap variables; + Evaluator::AngleMode angleMode; + QString decimalPoint; + + QString assignId; + QVector codes; + QStringList identifiers; + QVector constants; +}; + +class TokenStack : public QVector +{ +public: + TokenStack(); + bool isEmpty() const; + unsigned itemCount() const; + void push(const Token& token); + Token pop(); + const Token& top(); + const Token& top(unsigned index); + +private: + void ensureSpace(); + int topIndex; +}; + +// for null token +const Token Token::null; + +// helper function: return operator of given token text +// e.g. "*" yields Operator::Asterisk, and so on +static Token::Op matchOperator(const QString& text) +{ + Token::Op result = Token::InvalidOp; + + if (text.length() == 1) { + QChar p = text[0]; + switch (p.unicode()) + { + case '+': result = Token::Plus ; break; + case '-': result = Token::Minus ; break; + case '*': result = Token::Asterisk ; break; + case '/': result = Token::Slash ; break; + case '^': result = Token::Caret ; break; + case ';': result = Token::Semicolon ; break; + case '(': result = Token::LeftPar ; break; + case ')': result = Token::RightPar ; break; + case '%': result = Token::Percent ; break; + case '!': result = Token::Exclamation; break; + case '=': result = Token::Equal ; break; + default : result = Token::InvalidOp ; break; + } + } + + else if (text.length() == 2) + { + if (text == "**") result = Token::Caret; + } +#if 0 + else if (text.length() == 3) + { + if (text == "mod") result = Token::Modulo; + else if (text == "div") result = Token::Div; + } +#endif + + return result; +} + +// helper function: give operator precedence +// e.g. "+" is 1 while "*" is 3 +static int opPrecedence(Token::Op op) +{ + int prec = -1; + switch (op) + { + case Token::Exclamation : prec = 8; break; + case Token::Percent : prec = 8; break; + case Token::Caret : prec = 7; break; + case Token::Asterisk : prec = 5; break; + case Token::Slash : prec = 6; break; + case Token::Modulo : prec = 6; break; + case Token::Div : prec = 6; break; + case Token::Plus : prec = 3; break; + case Token::Minus : prec = 3; break; + case Token::RightPar : prec = 0; break; + case Token::LeftPar : prec = -1; break; + case Token::Semicolon : prec = 0; break; + default : prec = -1; break; + } + return prec; +} + +// creates a token +Token::Token(Type type, const QString& text, int pos) +{ + m_type = type; + m_text = text; + m_pos = pos; +} + +// copy constructor +Token::Token(const Token& token) +{ + m_type = token.m_type; + m_text = token.m_text; + m_pos = token.m_pos; +} + +// assignment operator +Token& Token::operator=(const Token& token) +{ + m_type = token.m_type; + m_text = token.m_text; + m_pos = token.m_pos; + return *this; +} + +HNumber Token::asNumber() const +{ + if (isNumber()) + return HNumber((const char*) m_text.toLatin1()); + else + return HNumber(0); +} + +Token::Op Token::asOperator() const +{ + if (isOperator()) + return matchOperator(m_text); + else + return InvalidOp; +} + +QString Token::description() const +{ + QString desc; + + switch (m_type) + { + case Number : desc = "Number" ; break; + case Identifier : desc = "Identifier"; break; + case Operator : desc = "Operator" ; break; + default : desc = "Unknown" ; break; + } + + while (desc.length() < 10) + desc.prepend(' '); + desc.prepend(" "); + desc.prepend(QString::number(m_pos)); + desc.append(" : ").append(m_text); + + return desc; +} + + +TokenStack::TokenStack(): QVector() +{ + topIndex = 0; + ensureSpace(); +} + +bool TokenStack::isEmpty() const +{ + return topIndex == 0; +} + +unsigned TokenStack::itemCount() const +{ + return topIndex; +} + +void TokenStack::push(const Token& token) +{ + ensureSpace(); + (*this)[ topIndex++ ] = token; +} + +Token TokenStack::pop() +{ + return (topIndex > 0) ? Token(at(--topIndex)) : Token(); +} + +const Token& TokenStack::top() +{ + return top(0); +} + +const Token& TokenStack::top(unsigned index) +{ + if (topIndex > (int)index) + return at(topIndex-index-1); + return Token::null; +} + +void TokenStack::ensureSpace() +{ + while (topIndex >= size()) + resize(size() + 10); +} + + +// helper function: return true for valid identifier character +static bool isIdentifier(QChar ch) +{ + return (ch.unicode() == '_') || (ch.unicode() == '$') || (ch.isLetter()); +} + + +// Constructor + +Evaluator::Evaluator() +{ + d = new EvaluatorPrivate; + clear(); +} + +// Destructor + +Evaluator::~Evaluator() +{ + delete d; +} + +// Sets a new expression +// note that both the real lex and parse processes will happen later on +// when needed (i.e. "lazy parse"), for example during evaluation. + +void Evaluator::setExpression(const QString& expr) +{ + d->expression = expr; + d->dirty = true; + d->valid = false; + d->error = QString(); +} + +// Returns the expression + +QString Evaluator::expression() const +{ + return d->expression; +} + +// Returns the validity +// note: empty expression is always invalid. + +bool Evaluator::isValid() const +{ + if (d->dirty) + { + Tokens tokens = scan(d->expression, d->decimalPoint); + if (!tokens.valid()) + compile(tokens); + else + d->valid = false; + } + return d->valid; +} + +// Clears everything, also mark it as invalid. + +void Evaluator::clear() +{ + d->expression = QString(); + d->dirty = true; + d->valid = false; + + d->error = QString(); + d->angleMode = Degree; + d->decimalPoint = QString(); + + d->constants.clear(); + d->codes.clear(); + d->assignId = QString(); + + clearVariables(); +} + +// Returns error message + +QString Evaluator::error() const +{ + return d->error; +} + +// Returns list of token for the expression. +// this triggers again the lexical analysis step. it is however preferable +// (even when there's small performance penalty) because otherwise we need to +// store parsed tokens all the time which serves no good purpose. + +Tokens Evaluator::tokens() const +{ + return scan(d->expression, d->decimalPoint); +} + +Tokens Evaluator::scan(const QString& expr, const QString& settingsDecimal) +{ + // to hold the result + Tokens tokens; + + // auto-detect, always dot, or always comma + + QChar decimalPoint; + QChar wrongDecimalPoint; + + if (settingsDecimal.length() == 1) + decimalPoint = settingsDecimal[0]; + else + decimalPoint = QLocale().decimalPoint(); + + // sanity check for wrong decimal separator usage + + if (decimalPoint == ',') + wrongDecimalPoint = '.'; + else + wrongDecimalPoint = ','; + + int size = expr.size(); + for (int i = 0; i < size; i++) + if (expr[i] == wrongDecimalPoint) + return tokens; + + // parsing state + enum { Start, Finish, Bad, InNumber, InHexa, InOctal, InBinary, InDecimal, InExpIndicator, + InExponent, InIdentifier } state; + + // initialize variables + state = Start; + int i = 0; + QString ex = expr; + QString tokenText; + int tokenStart = 0; + + // force a terminator + ex.append(QChar()); + + // main loop + while ((state != Bad) && (state != Finish) && (i < ex.length())) + { + QChar ch = ex.at(i); + + switch (state) + { + + case Start: + + tokenStart = i; + + // skip any whitespaces + if (ch.isSpace()) i++; + + // check for number + else if (ch.isDigit()) + { + state = InNumber; + } + else if (ch == '#') // simple hexadec notation + { + tokenText.append("0x"); + state = InHexa; + i++; + } + + // decimal dot ? + else if (ch == decimalPoint) + { + tokenText.append(ex.at(i++)); + state = InDecimal; + } + + // terminator character + else if (ch.isNull()) + state = Finish; + + // look for operator match + else + { + int op; + QString s; + +#if 0 + // check for three-chars operator + s.append(ch).append(ex.at(i+1)).append(ex.at(i+2)); + op = matchOperator(s); + + // check for two-chars operator + if (op == Token::InvalidOp) +#endif + { + + s = QString(ch).append(ex.at(i+1)); + op = matchOperator(s); + } + + // check for one-char operator + if (op == Token::InvalidOp) + { + s = QString(ch); + op = matchOperator(s); + } + + // any matched operator ? + if (op != Token::InvalidOp) + { + int len = s.length(); + i += len; + tokens.append(Token(Token::Operator, s.left(len), tokenStart)); + } + else state = Bad; + } + + // beginning with unknown alphanumeric ? + // could be identifier, or function... + if (state == Bad && isIdentifier(ch)) + { + state = InIdentifier; + } + break; + + case InIdentifier: + + // consume as long as alpha, dollar sign, underscore, or digit + if (isIdentifier(ch) || ch.isDigit()) + tokenText.append(ex.at(i++)); + + // we're done with identifier + else + { + tokens.append(Token(Token::Identifier, tokenText, tokenStart)); + tokenStart = i; + tokenText = ""; + state = Start; + } + break; + + + case InNumber: + + // consume as long as it's digit + if (ch.isDigit()) tokenText.append(ex.at(i++)); + + // convert decimal separator to '.' + else if (ch == decimalPoint) + { + tokenText.append('.'); + i++; + state = InDecimal; + } + + // exponent ? + else if (ch.toUpper() == 'E') + { + tokenText.append('E'); + i++; + state = InExpIndicator; + } + else if (ch.toUpper() == 'X' && tokenText == "0") // normal hexadec notation + { + tokenText.append('x'); i++; state = InHexa; + } + else if (ch.toUpper() == 'B' && tokenText == "0") // binary notation + { + tokenText.append('b'); i++; state = InBinary; + } + else if (ch.toUpper() == 'O' && tokenText == "0") // octal notation + { + tokenText.append('o'); i++; state = InOctal; + } + else if (ch.toUpper() == 'D' && tokenText == "0") // explicit decimal notation + { + // we also need to get rid of the leading zero + tokenText = ""; i++; + } + + // we're done with integer number + else + { + tokens.append(Token(Token::Number, tokenText, tokenStart)); + tokenText = ""; state = Start; + } + break; + + case InHexa: + if (ch.isDigit() || (ch >= 'A' && ch < 'G') || (ch >= 'a' && ch < 'g')) + tokenText.append(ex.at(i++).toUpper()); + else // we're done with hexa number + { + tokens.append(Token(Token::Number, tokenText, tokenStart)); + tokenText = ""; state = Start; + } + break; + case InBinary: + if (ch == '0' || ch == '1') // very strict rule ;) + tokenText.append(ex.at(i++)); + else // we're done with binary number + { + tokens.append(Token(Token::Number, tokenText, tokenStart)); + tokenText = ""; + state = Start; + } + break; + case InOctal: + if (ch >= '0' && ch < '8') // octal has only 8 digits, 8 & 9 are invalid + tokenText.append(ex.at(i++)); + else // we're done with octal number + { + tokens.append(Token(Token::Number, tokenText, tokenStart)); + tokenText = ""; state = Start; + } + break; + + case InDecimal: + + // consume as long as it's digit + if (ch.isDigit()) tokenText.append(ex.at(i++)); + + // exponent ? + else if (ch.toUpper() == 'E') + { + tokenText.append('E'); + i++; + state = InExpIndicator; + } + + // we're done with floating-point number + else + { + tokens.append(Token(Token::Number, tokenText, tokenStart)); + tokenText = ""; + state = Start; + }; + break; + + case InExpIndicator: + + // possible + or - right after E, e.g 1.23E+12 or 4.67E-8 + if ((ch == '+') || (ch == '-')) tokenText.append(ex.at(i++)); + + // consume as long as it's digit + else if (ch.isDigit()) state = InExponent; + + // invalid thing here + else state = Bad; + + break; + + case InExponent: + + // consume as long as it's digit + if (ch.isDigit()) tokenText.append(ex.at(i++)); + + // we're done with floating-point number + else + { + tokens.append(Token(Token::Number, tokenText, tokenStart)); + tokenText = ""; + state = Start; + }; + break; + + case Bad: // bad bad bad + tokens.setValid(false); + break; + + default: + break; + }; + }; + + return tokens; +} + + +void Evaluator::compile(const Tokens& tokens) const +{ + // initialize variables + d->dirty = false; + d->valid = false; + d->codes.clear(); + d->constants.clear(); + d->identifiers.clear(); + d->error = QString(); + + // sanity check + if (tokens.count() == 0) return; + + TokenStack syntaxStack; + QStack argStack; + unsigned argCount = 1; + + for (int i = 0; i <= tokens.count(); i++) + { + // helper token: InvalidOp is end-of-expression + Token token = (i < tokens.count()) ? tokens[i] : Token(Token::Operator); + Token::Type tokenType = token.type(); + + // unknown token is invalid + if (tokenType == Token::Unknown) break; + + // for constants, push immediately to stack + // generate code to load from a constant + if (tokenType == Token::Number) + { + syntaxStack.push(token); + d->constants.append(token.asNumber()); + d->codes.append(Opcode(Opcode::Load, d->constants.count()-1)); + } + + // for identifier, push immediately to stack + // generate code to load from reference + if (tokenType == Token::Identifier) + { + syntaxStack.push(token); + d->identifiers.append(token.text()); + d->codes.append(Opcode(Opcode::Ref, d->identifiers.count()-1)); + } + + // special case for percentage + if (tokenType == Token::Operator) + if (token.asOperator() == Token::Percent) + if (syntaxStack.itemCount() >= 1) + if (!syntaxStack.top().isOperator()) + { + d->constants.append(HNumber("0.01")); + d->codes.append(Opcode(Opcode::Load, d->constants.count()-1)); + d->codes.append(Opcode(Opcode::Mul)); + } + + // for any other operator, try to apply all parsing rules + if (tokenType == Token::Operator) + if (token.asOperator() != Token::Percent) + { + // repeat until no more rule applies + bool argHandled = false; + for (; ;) + { + bool ruleFound = false; + + // rule for function last argument: + // id (arg) -> arg + if (!ruleFound) + if (syntaxStack.itemCount() >= 4) + { + Token par2 = syntaxStack.top(); + Token arg = syntaxStack.top(1); + Token par1 = syntaxStack.top(2); + Token id = syntaxStack.top(3); + if (par2.asOperator() == Token::RightPar) + if (!arg.isOperator()) + if (par1.asOperator() == Token::LeftPar) + if (id.isIdentifier()) + { + ruleFound = true; + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(arg); + d->codes.append(Opcode(Opcode::Function, argCount)); + argCount = argStack.empty() ? 0 : argStack.pop(); + } + } + + // are we entering a function ? + // if token is operator, and stack already has: id (arg + if (!ruleFound) + if (!argHandled) + if (tokenType == Token::Operator) + if (syntaxStack.itemCount() >= 3) + { + Token arg = syntaxStack.top(); + Token par = syntaxStack.top(1); + Token id = syntaxStack.top(2); + if (!arg.isOperator()) + if (par.asOperator() == Token::LeftPar) + if (id.isIdentifier()) + { + ruleFound = true; + argStack.push(argCount); + argCount = 1; + break; + } + } + + + // rule for simplified syntax for function e.g. "sin pi" or "cos 1.2" + // i.e no need for parentheses like "sin(pi)" or "cos(1.2)" + if (!ruleFound) + if (syntaxStack.itemCount() >= 2) + { + Token arg = syntaxStack.top(); + Token id = syntaxStack.top(1); + if (!arg.isOperator()) + if (id.isIdentifier()) + if (FunctionRepository::self()->function(id.text())) + { + ruleFound = true; + d->codes.append(Opcode(Opcode::Function, 1)); + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(arg); + } + } + + // rule for unary operator in simplified function syntax + // this handles case like "sin -90" + if (!ruleFound) + if (syntaxStack.itemCount() >= 3) + { + Token x = syntaxStack.top(); + Token op = syntaxStack.top(1); + Token id = syntaxStack.top(2); + if (!x.isOperator()) + if (op.isOperator()) + if (id.isIdentifier()) + if (FunctionRepository::self()->function(id.text())) + if ((op.asOperator() == Token::Plus) || + (op.asOperator() == Token::Minus)) + { + ruleFound = true; + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(x); + if (op.asOperator() == Token::Minus) + d->codes.append(Opcode(Opcode::Neg)); + } + } + + // rule for unary postfix operator in simplified function syntax + // this handles case like "sin 90!" + if (!ruleFound) + if (syntaxStack.itemCount() >= 3) + { + Token op = syntaxStack.top(); + Token x = syntaxStack.top(1); + Token id = syntaxStack.top(2); + if (id.isIdentifier() && FunctionRepository::self()->function(id.text())) + { + if (!x.isOperator() && op.isOperator() && + op.asOperator() == Token::Exclamation) + { + ruleFound = true; + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(x); + d->codes.append(Opcode(Opcode::Fact)); + } + } + } + + + // rule for function arguments, if token is , or) + // id (arg1 ; arg2 -> id (arg + if (!ruleFound) + if (syntaxStack.itemCount() >= 5) + if ((token.asOperator() == Token::RightPar) || + (token.asOperator() == Token::Semicolon)) + { + Token arg2 = syntaxStack.top(); + Token sep = syntaxStack.top(1); + Token arg1 = syntaxStack.top(2); + Token par = syntaxStack.top(3); + Token id = syntaxStack.top(4); + if (!arg2.isOperator()) + if (sep.asOperator() == Token::Semicolon) + if (!arg1.isOperator()) + if (par.asOperator() == Token::LeftPar) + if (id.isIdentifier()) + { + ruleFound = true; + argHandled = true; + syntaxStack.pop(); + syntaxStack.pop(); + argCount++; + } + } + + // rule for function call with parentheses, but without argument + // e.g. "2*PI()" + if (!ruleFound) + if (syntaxStack.itemCount() >= 3) + { + Token par2 = syntaxStack.top(); + Token par1 = syntaxStack.top(1); + Token id = syntaxStack.top(2); + if (par2.asOperator() == Token::RightPar) + if (par1.asOperator() == Token::LeftPar) + if (id.isIdentifier()) + { + ruleFound = true; + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(Token(Token::Number)); + d->codes.append(Opcode(Opcode::Function, 0)); + } + } + + // rule for parenthesis: (Y) -> Y + if (!ruleFound) + if (syntaxStack.itemCount() >= 3) + { + Token right = syntaxStack.top(); + Token y = syntaxStack.top(1); + Token left = syntaxStack.top(2); + if (right.isOperator()) + if (!y.isOperator()) + if (left.isOperator()) + if (right.asOperator() == Token::RightPar) + if (left.asOperator() == Token::LeftPar) + { + ruleFound = true; + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(y); + } + } + + // rule for binary operator: A (op) B -> A + // conditions: precedence of op >= precedence of token + // action: push (op) to result + // e.g. "A * B" becomes "A" if token is operator "+" + // exception: for caret (power operator), if op is another caret + // then the rule doesn't apply, e.g. "2^3^2" is evaluated as "2^(3^2)" + if (!ruleFound) + if (syntaxStack.itemCount() >= 3) + { + Token b = syntaxStack.top(); + Token op = syntaxStack.top(1); + Token a = syntaxStack.top(2); + if (!a.isOperator()) + if (!b.isOperator()) + if (op.isOperator()) + if (token.asOperator() != Token::LeftPar) + if (token.asOperator() != Token::Caret) + if (opPrecedence(op.asOperator()) >= opPrecedence(token.asOperator())) + { + ruleFound = true; + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(b); + switch (op.asOperator()) + { + // simple binary operations + case Token::Plus: d->codes.append(Opcode::Add); break; + case Token::Minus: d->codes.append(Opcode::Sub); break; + case Token::Asterisk: d->codes.append(Opcode::Mul); break; + case Token::Slash: d->codes.append(Opcode::Div); break; + case Token::Caret: d->codes.append(Opcode::Pow); break; + case Token::Modulo: d->codes.append(Opcode::Modulo); break; + case Token::Div: d->codes.append(Opcode::IntDiv); break; + default: break; + }; + } + } + + // rule for unary operator: (op1) (op2) X -> (op1) X + // conditions: op2 is unary, token is not '(' + // action: push (op2) to result + // e.g. "* - 2" becomes "*" + if (!ruleFound) + if (token.asOperator() != Token::LeftPar) + if (syntaxStack.itemCount() >= 3) + { + Token x = syntaxStack.top(); + Token op2 = syntaxStack.top(1); + Token op1 = syntaxStack.top(2); + if (!x.isOperator() && op1.isOperator() && op2.isOperator() && + (op2.asOperator() == Token::Plus || op2.asOperator() == Token::Minus)) + { + ruleFound = true; + if (op2.asOperator() == Token::Minus) + d->codes.append(Opcode(Opcode::Neg)); + } + else // maybe postfix + { + x = op2; op2 = syntaxStack.top(); + if (!x.isOperator() && op1.isOperator() && op2.isOperator() && + op2.asOperator() == Token::Exclamation) + { + ruleFound = true; + d->codes.append(Opcode(Opcode::Fact)); + } + } + if (ruleFound) + { + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(x); + } + } + + // auxiliary rule for unary operator: (op) X -> X + // conditions: op is unary, op is first in syntax stack, token is not '(' or '^' or '!' + // action: push (op) to result + if (!ruleFound) + if (token.asOperator() != Token::LeftPar) + if (token.asOperator() != Token::Caret) + if (token.asOperator() != Token::Exclamation) + if (syntaxStack.itemCount() == 2) + { + Token x = syntaxStack.top(); + Token op = syntaxStack.top(1); + if (!x.isOperator() && op.isOperator() && + (op.asOperator() == Token::Plus || op.asOperator() == Token::Minus)) + { + ruleFound = true; + if (op.asOperator() == Token::Minus) + d->codes.append(Opcode(Opcode::Neg)); + } + else + { + x = op; op = syntaxStack.top(); + if (!x.isOperator() && op.isOperator() && + (op.asOperator() == Token::Exclamation)) + { + ruleFound = true; + d->codes.append(Opcode(Opcode::Fact)); + } + } + if (ruleFound) + { + syntaxStack.pop(); + syntaxStack.pop(); + syntaxStack.push(x); + } + } + + if (!ruleFound) break; + } + + // can't apply rules anymore, push the token + if (token.asOperator() != Token::Percent) + syntaxStack.push(token); + } + } + + // syntaxStack must left only one operand and end-of-expression (i.e. InvalidOp) + d->valid = false; + if (syntaxStack.itemCount() == 2) + if (syntaxStack.top().isOperator()) + if (syntaxStack.top().asOperator() == Token::InvalidOp) + if (!syntaxStack.top(1).isOperator()) + d->valid = true; + + // bad parsing ? clean-up everything + if (!d->valid) + { + d->constants.clear(); + d->codes.clear(); + d->identifiers.clear(); + } +} + +Evaluator::AngleMode Evaluator::angleMode() const +{ + return d->angleMode; +} + +void Evaluator::setAngleMode(AngleMode am) +{ + d->angleMode = am; +} + +QString Evaluator::decimalPoint() const +{ + return d->decimalPoint; +} + +void Evaluator::setDecimalPoint(const QString& dp) +{ + d->decimalPoint = dp; +} + +HNumber Evaluator::eval() +{ + QStack stack; + QStack refs; + int index; + HNumber val1, val2; + QVector args; + QString fname; + Function* function; + + if (d->dirty) + { + Tokens tokens = scan(d->expression, d->decimalPoint); + + // invalid expression? + if (!tokens.valid()) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + + // variable assignment? + d->assignId = QString(); + if (tokens.count() > 2) + if (tokens[0].isIdentifier()) + if (tokens[1].asOperator() == Token::Equal) + { + d->assignId = tokens[0].text(); + tokens.erase(tokens.begin()); + tokens.erase(tokens.begin()); + } + + compile(tokens); + } + + // can not overwrite pi + if (d->assignId == QString("pi")) + { + d->error = d->assignId + ": " + QObject::tr("Variable cannot be overwritten."); + return HNumber(0); + } + // can not overwrite phi + if (d->assignId == QString("phi")) + { + d->error = d->assignId + ": " + QObject::tr("Variable cannot be overwritten."); + return HNumber(0); + } + // can not overwrite ans + if (d->assignId == QString("ans")) + { + d->error = d->assignId + ": " + QObject::tr("Variable cannot be overwritten."); + return HNumber(0); + } + + // variable can't have the same name as function + if (FunctionRepository::self()->function(d->assignId)) + { + d->error = d->assignId + ": " + QObject::tr("Identifier matches an existing function name."); + return HNumber(0); + } + + // magic: always set here to avoid being overwritten by user + set(QString("pi"), HMath::pi()); + set(QString("phi"), HMath::phi()); + + for (int pc = 0; pc < d->codes.count(); pc++) + { + Opcode& opcode = d->codes[pc]; + index = opcode.index; + switch (opcode.type) + { + // no operation + case Opcode::Nop: + break; + + // load a constant, push to stack + case Opcode::Load: + val1 = d->constants[index]; + stack.push(val1); + break; + + // unary operation + case Opcode::Neg: + if (stack.count() < 1) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val1 = HMath::negate(val1); + stack.push(val1); + break; + + // binary operation: take two values from stack, do the operation, + // push the result to stack + case Opcode::Add: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + val2 += val1; + stack.push(val2); + break; + + case Opcode::Sub: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + val2 -= val1; + stack.push(val2); + break; + + case Opcode::Mul: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + val2 *= val1; + stack.push(val2); + break; + + case Opcode::Div: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + if (val1.isZero()) + { + d->error = QObject::tr("Division by zero."); + return HNumber(0); + } + val2 /= val1; + stack.push(val2); + break; + + case Opcode::Pow: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + val2 = HMath::raise(val2, val1); + stack.push(val2); + break; + + case Opcode::Fact: + if (stack.count() < 1) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val1 = HMath::factorial(val1); + stack.push(val1); + break; + + case Opcode::Modulo: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + if (val1.isZero()) + { + d->error = QObject::tr("Division by zero."); + return HNumber(0); + } + val2 = val2 % val1; + stack.push(val2); + break; + + case Opcode::IntDiv: + if (stack.count() < 2) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + val1 = stack.pop(); + val2 = stack.pop(); + if (val1.isZero()) + { + d->error = QObject::tr("Division by zero."); + return HNumber(0); + } + val2 /= val1; + stack.push(HMath::integer(val2)); + break; + + // reference + case Opcode::Ref: + fname = d->identifiers[index]; + if (has(fname)) + { + // variable + stack.push(get(fname)); + } + else + { + // function + function = FunctionRepository::self()->function(fname); + if (function) + refs.push(fname); + else + { + d->error = fname + ": " + QObject::tr("Unknown function or variable."); + return HNumber(0); + } + } + break; + + // calling function + case Opcode::Function: + // must do this first to avoid crash when using vars like functions + if (refs.isEmpty()) + break; + + fname = refs.pop(); + function = FunctionRepository::self()->function(fname); + if (!function) + { + d->error = fname + ": " + QObject::tr("Unknown function or variable."); + return HNumber(0); + } + + if (stack.count() < index) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + + args.clear(); + for (; index; index--) + args.insert(args.begin(), stack.pop()); + + stack.push(function->exec(this, args)); + if (!function->error().isEmpty()) + { + d->error = function->error(); + return HNumber(0); + } + + break; + + default: + break; + } + } + + // more than one value in stack ? unsuccesfull execution... + if (stack.count() != 1) + { + d->error = QObject::tr("Invalid expression."); + return HNumber(0); + } + + HNumber result = stack.pop(); + + // handle variable assignment, e.g. "x=2*4" + if (!d->assignId.isEmpty()) + set(d->assignId, result); + + // "ans" is default variable to hold calculation result + set(QString("ans"), result); + + return result; +} + +void Evaluator::set(const QString& id, HNumber value) +{ + if (!id.isEmpty()) + { + d->variables[ id ].name = id; + d->variables[ id ].value = value; + } +} + +HNumber Evaluator::get(const QString& id) +{ + if (id.isEmpty()) return HNumber(0); + + if (!d->variables.contains(id)) + set(id, HNumber(0)); + return d->variables[ id ].value; +} + +bool Evaluator::has(const QString& id) +{ + return id.isEmpty() ? false : d->variables.contains(id); +} + +void Evaluator::remove(const QString& id) +{ + d->variables.remove(id); +} + +QVector Evaluator::variables() const +{ + QVector result; + + QMap::Iterator it; + for (it = d->variables.begin(); it != d->variables.end(); ++it) + { + Variable var; + var.name = it.value().name; + var.value = it.value().value; + result.append(var); + } + + return result; +} + +void Evaluator::clearVariables() +{ + d->variables.clear(); + set(QString("pi"), HMath::pi() ); + set(QString("phi"), HMath::phi()); + set(QString("ans"), HNumber(0) ); +} + +QString Evaluator::autoFix(const QString& expr, const QString& decimalPoint) +{ + int par = 0; + QString result; + + // strip off all funny characters + for (int c = 0; c < expr.length(); c++) + if (expr[c] >= QChar(32)) + result.append(expr[c]); + + // no extra whitespaces at the beginning and at the end + result = result.trimmed(); + + // strip trailing equal sign (=) + while (result.endsWith("=")) + result = result.left(result.length()-1); + + // automagically close all parenthesis + Tokens tokens = Evaluator::scan(result, decimalPoint); + if (tokens.count()) + { + for (int i=0; i= result.length()) + while (par--) + result.append(')'); + } + + // special treatment for simple function + // e.g. "cos" is regarded as "cos(ans)" + if (!result.isEmpty()) + { + Tokens tokens = Evaluator::scan(result, decimalPoint); + if (tokens.count() == 1) + { + if (tokens[0].isIdentifier()) + { + Function* f = FunctionRepository::self()->function(tokens[0].text()); + if (f) result.append("(ans)"); + } + } + } + + return result; +} + +// Debugging aid + +QString Evaluator::dump() const +{ + QString result; + int c; + + if (d->dirty) + { + Tokens tokens = scan(d->expression, d->decimalPoint); + compile(tokens); + } + + result = QString("Expression: [%1]\n").arg(d->expression); + + result.append(" Constants:\n"); + for (c = 0; c < d->constants.count(); c++) + { + QString vtext; + HNumber val = d->constants[c]; + char* ss = HMath::formatFixed(val); + result += QString(" #%1 = %2\n").arg(c).arg(ss); + free(ss); + } + + result.append("\n"); + result.append(" Identifiers:\n"); + for (c = 0; c < d->identifiers.count(); c++) + { + QString vtext; + result += QString(" #%1 = %2\n").arg(c).arg(d->identifiers[c]); + } + + result.append("\n"); + result.append(" Code:\n"); + for (int i = 0; i < d->codes.count(); i++) + { + QString ctext; + switch (d->codes[i].type) + { + case Opcode::Load : ctext = QString("Load #%1" ).arg(d->codes[i].index); break; + case Opcode::Ref : ctext = QString("Ref #%1" ).arg(d->codes[i].index); break; + case Opcode::Function: ctext = QString("Function (%1)").arg(d->codes[i].index); break; + case Opcode::Add : ctext = "Add" ; break; + case Opcode::Sub : ctext = "Sub" ; break; + case Opcode::Mul : ctext = "Mul" ; break; + case Opcode::Div : ctext = "Div" ; break; + case Opcode::Neg : ctext = "Neg" ; break; + case Opcode::Pow : ctext = "Pow" ; break; + case Opcode::Fact : ctext = "Fact" ; break; + default : ctext = "Unknown"; break; + } + result.append(" ").append(ctext).append("\n"); + } + + return result; +} diff --git a/engine/evaluator.h b/engine/evaluator.h new file mode 100644 index 0000000..3b2b399 --- /dev/null +++ b/engine/evaluator.h @@ -0,0 +1,143 @@ +/* Copyright (C) 2004 Ariya Hidayat + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef EVALUATOR +#define EVALUATOR + +#include +#include + +#include "hmath.h" + +class Token +{ +public: + typedef enum { + Unknown, + Number, + Operator, + Identifier + } Type; + + typedef enum { + InvalidOp = 0, + Plus, // + (addition) + Minus, // - (substraction, negation) + Asterisk, // * (multiplication) + Slash, // / (division) + Caret, // ^ (power) + LeftPar, // ( + RightPar, // ) + Semicolon, // argument separator + Percent, // % + Exclamation, // ! (factorial) + Equal, // variable assignment + Modulo, // integer rest division + Div // integer division + } Op; + + Token(Type type = Unknown, const QString &text = QString(), int pos = -1); + + Token(const Token &); + Token& operator=(const Token &); + + Type type() const { return m_type; } + QString text() const { return m_text; } + int pos() const { return m_pos; }; + bool isNumber() const { return m_type == Number; } + bool isOperator() const { return m_type == Operator; } + bool isIdentifier() const { return m_type == Identifier; } + HNumber asNumber() const; + Op asOperator() const; + QString description() const; + + static const Token null; + +protected: + Type m_type; + QString m_text; + int m_pos; + +}; + + +class Tokens: public QVector +{ +public: + Tokens(): QVector(), m_valid(true) {}; + bool valid() const { return m_valid; } + void setValid(bool v){ m_valid = v; } +protected: + bool m_valid; +}; + +class Variable +{ +public: + QString name; + HNumber value; +}; + +class EvaluatorPrivate; + +class Evaluator +{ +public: + + typedef enum { Degree, Radian } AngleMode; + + Evaluator(); + ~Evaluator(); + void setExpression(const QString &expr); + QString expression() const; + void clear(); + bool isValid() const; + Tokens tokens() const; + static Tokens scan(const QString &expr, const QString &decimalPoint); + QString error() const; + + void setAngleMode(AngleMode am); + AngleMode angleMode() const; + HNumber eval(); + + void setDecimalPoint(const QString &d); + QString decimalPoint() const; + + void set(const QString &id, HNumber value); + HNumber get(const QString &id); + bool has(const QString &id); + void remove(const QString &id); + QVector variables() const; + void clearVariables(); + + static QString autoFix(const QString &expr, const QString &decimalPoint); + + QString dump() const; + +protected: + + void compile(const Tokens &tokens) const; + +private: + EvaluatorPrivate *d; + Evaluator(const Evaluator &); + Evaluator& operator=(const Evaluator &); +}; + + +#endif // EVALUATOR diff --git a/engine/functions.cpp b/engine/functions.cpp new file mode 100644 index 0000000..c0bad50 --- /dev/null +++ b/engine/functions.cpp @@ -0,0 +1,1110 @@ +/* Copyright (C) 2004-2006 Ariya Hidayat + 2007 Helder Correia + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "evaluator.h" +#include "functions.h" +#include "hmath.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define qApp QCoreApplication::instance() + +HNumber deg2rad( HNumber x ) +{ + return x * HMath::pi() / HNumber(180); +} + +HNumber rad2deg( HNumber x ) +{ + return HNumber(180) * x / HMath::pi(); +} + +HNumber function_abs( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + return HMath::abs( num ); +} + +HNumber function_int( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + return HMath::integer( num ); +} + +HNumber function_trunc( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + int nArgs = arguments.count(); + + if( nArgs != 1 && nArgs != 2 ) + { + function->setError( function->name(), QString( "function requires 1 or 2 arguments" ) ); + return HNumber::nan(); + } + + HNumber num = arguments[0]; + HNumber prec; + HNumber zero(0); + if( nArgs == 2) + prec = arguments[1]; + else prec = zero; + + if( !prec.isInteger() ) + { + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + return HNumber::nan(); + } + + HNumber limit(150); + if( prec > limit ) + prec = limit; + else if( prec < zero ) + prec = zero; + + if( nArgs == 1 ) + return HMath::trunc( num ); + else // nArgs == 2 + return HMath::trunc( num, prec.toInt() ); +} + +HNumber function_frac( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 1 ) + return HNumber::nan(); + + HNumber x = arguments[0]; + return HMath::frac( x ); +} + +HNumber function_floor( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + return HMath::floor( num ); +} + +HNumber function_ceil( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + return HMath::ceil( num ); +} + +HNumber function_gcd( const Evaluator*, Function* fn, const FunctionArguments& args ) +{ + int nArgs = args.count(); + + if ( nArgs < 2 ) + { + fn->setError( fn->name(), QString( "function requires at least 2 arguments" ) ); + return HNumber::nan(); + } + + for ( int i = 0; i < args.count(); i++ ) + if ( !args[i].isInteger() ) + { + fn->setError( fn->name(), QString( "function requires integer arguments" ) ); + return HNumber::nan(); + } + + HNumber result = HMath::gcd( args[0], args[1] ); + for ( int i = 2; i < nArgs; i++ ) + { + result = HMath::gcd( result, args[i] ); + } + return result; +} + +HNumber function_round( const Evaluator*, Function* fn, const FunctionArguments& args ) +{ + int nArgs = args.count(); + + if( nArgs != 1 && nArgs != 2 ) + { + fn->setError( fn->name(), QString( "function requires 1 or 2 arguments" ) ); + return HNumber::nan(); + } + + HNumber num = args[0]; + HNumber prec; + HNumber zero(0); + if( nArgs == 2) + prec = args[1]; + else + prec = zero; + + if( !prec.isInteger() ) + { + fn->setError( fn->name(), QString( "function requires integer P2" ) ); + return HNumber::nan(); + } + + HNumber limit(150); + if( prec > limit ) + prec = limit; + else if( prec < zero ) + prec = zero; + + if( nArgs == 1 ) + return HMath::round( num ); + else // nArgs == 2 + return HMath::round( num, prec.toInt() ); +} + +HNumber function_sqrt( const Evaluator*, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + if( num < HNumber(0) ) + { + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + return HMath::sqrt( num ); +} + +HNumber function_cbrt( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + return HMath::cbrt( num ); +} + +HNumber function_exp( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + return HMath::exp( num ); +} + +HNumber function_ln( const Evaluator*, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber x = args[0]; + HNumber result = HMath::ln( x ); + + if( result.isNan() ) + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + + return result; +} + +HNumber function_log( const Evaluator*, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber x = args[0]; + HNumber result = HMath::log( x ); + + if( result.isNan() ) + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + + return result; +} + +HNumber function_lg( const Evaluator*, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber x = args[0]; + HNumber result = HMath::lg( x ); + + if( result.isNan() ) + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + + return result; +} + +HNumber function_sin( const Evaluator* eval, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + + return HMath::sin( angle ); +} + +HNumber function_cos( const Evaluator* eval, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + + return HMath::cos( angle ); +} + +HNumber function_tan( const Evaluator* eval, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + + HNumber result = HMath::tan( angle ); + if ( result.isNan() ) + { + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + return result; +} + +HNumber function_cot( const Evaluator* eval, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + + HNumber result = HMath::cot( angle ); + if ( result.isNan() ) + { + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + return result; +} + +HNumber function_sec( const Evaluator* eval, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + + HNumber result = HMath::sec( angle ); + if ( result.isNan() ) + { + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + return result; +} + +HNumber function_csc( const Evaluator* eval, Function* fn, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + + HNumber result = HMath::csc( angle ); + if ( result.isNan() ) + { + fn->setError( fn->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + return result; +} + +HNumber function_asin( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 1 ) + return HNumber::nan(); + + HNumber x = arguments[0]; + + HNumber result = HMath::asin( x ); + + if ( result.isNan() ) + { + function->setError( function->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + if ( evaluator->angleMode() == Evaluator::Degree ) + result = rad2deg( result ); + + return result; +} + +HNumber function_acos( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 1 ) + return HNumber::nan(); + + HNumber x = arguments[0]; + + HNumber result = HMath::acos( x ); + + if ( result.isNan() ) + { + function->setError( function->name(), QString( "function undefined for specified argument" ) ); + return HNumber::nan(); + } + + if ( evaluator->angleMode() == Evaluator::Degree ) + result = rad2deg( result ); + + return result; +} + +HNumber function_atan( const Evaluator* eval, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber num = args[0]; + HNumber angle = HMath::atan( num ); + if( eval->angleMode() == Evaluator::Degree ) + angle = rad2deg( angle ); + + return angle; +} + +HNumber function_sinh( const Evaluator* eval, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + return HMath::sinh( angle ); +} + +HNumber function_cosh( const Evaluator* eval, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + return HMath::cosh( angle ); +} + +HNumber function_tanh( const Evaluator* eval, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + if( eval->angleMode() == Evaluator::Degree ) + angle = deg2rad( angle ); + return HMath::tanh( angle ); +} + +HNumber function_sign( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() != 1 ) + return HNumber::nan(); + return HMath::sign( args[0] ); +} + +HNumber function_nCr( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + // check number of arguments + if ( arguments.count() != 2 ) + return HNumber::nan(); + + // compute result + // n = arguments[0] + // r = arguments[1] + HNumber result = HMath::nCr( arguments[0], arguments[1] ); + + // check invalid usage and set error accordingly + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_nPr( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + // check number of arguments + if ( arguments.count() != 2 ) + return HNumber::nan(); + + // compute result + // n = arguments[0] + // r = arguments[1] + HNumber result = HMath::nPr( arguments[0], arguments[1] ); + + // check invalid usage and set error accordingly + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_degrees( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if ( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + return angle * HNumber(180) / HMath::pi(); +} + +HNumber function_radians( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if ( args.count() != 1 ) + return HNumber::nan(); + + HNumber angle = args[0]; + return angle * HMath::pi() / HNumber(180); +} + +HNumber function_max( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() < 1 ) + { + function->setError( function->name(), QString( "function requires at least 1 argument" ) ); + return HNumber::nan(); + } + + int totalParams = arguments.count(); + HNumber result = arguments[0]; + if(totalParams > 1) + for ( int i = 1; i < totalParams; i++ ) + result = HMath::max( result, arguments[i] ); + + return result; +} + +HNumber function_min( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() < 1 ) + { + function->setError( function->name(), QString( "function requires at least 1 argument" ) ); + return HNumber::nan(); + } + + int totalParams = arguments.count(); + HNumber result = arguments[0]; + if(totalParams > 1) + for ( int i = 1; i < totalParams; i++ ) + result = HMath::min( result, arguments[i] ); + + return result; +} + +HNumber function_sum( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() <= 0 ) + return HNumber(0); + + HNumber result = args[0]; + for( int c = 1; c < args.count(); c++ ) + result = result + args[c]; + + return result; +} + +HNumber function_product( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() <= 0 ) + return HNumber(0); + + HNumber result = args[0]; + for( int c = 1; c < args.count(); c++ ) + result = result * args[c]; + + return result; +} + +HNumber function_average( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() <= 0 ) + return HNumber("NaN"); + + HNumber result = args[0]; + for( int c = 1; c < args.count(); c++ ) + result = result + args[c]; + + result = result / HNumber(args.count()); + + return result; +} + +HNumber function_geomean( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() <= 0 ) + return HNumber("NaN"); + + HNumber result = args[0]; + for( int c = 1; c < args.count(); c++ ) + result = result * args[c]; + + result = result / HNumber(args.count()); + + if( result <= HNumber(0)) + return HNumber("NaN"); + + return result; +} + +HNumber function_dec( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() < 1 ) + return HNumber("NaN"); + HNumber result = args[0]; + result.setFormat('g'); + return result; +} + +HNumber function_hex( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() < 1 ) + return HNumber("NaN"); + HNumber result = args[0]; + result.setFormat('h'); + return result; +} + +HNumber function_oct( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() < 1 ) + return HNumber("NaN"); + HNumber result = args[0]; + result.setFormat('o'); + return result; +} + +HNumber function_bin( const Evaluator*, Function*, const FunctionArguments& args ) +{ + if( args.count() < 1 ) + return HNumber("NaN"); + HNumber result = args[0]; + result.setFormat('b'); + return result; +} + +HNumber function_binompmf( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 3 ) + return HNumber::nan(); + + HNumber k = arguments[0]; + HNumber n = arguments[1]; + HNumber p = arguments[2]; + HNumber result = HMath::binomialPmf( k, n, p ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_binomcdf( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 3 ) + return HNumber::nan(); + + HNumber k = arguments[0]; + HNumber n = arguments[1]; + HNumber p = arguments[2]; + HNumber result = HMath::binomialCdf( k, n, p ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_binommean( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 2 ) + return HNumber::nan(); + + HNumber n = arguments[0]; + HNumber p = arguments[1]; + HNumber result = HMath::binomialMean( n, p ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_binomvar( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 2 ) + return HNumber::nan(); + + HNumber n = arguments[0]; + HNumber p = arguments[1]; + HNumber result = HMath::binomialVariance( n, p ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_hyperpmf( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 4 ) + return HNumber::nan(); + + HNumber k = arguments[0]; + HNumber N = arguments[1]; + HNumber M = arguments[2]; + HNumber n = arguments[3]; + HNumber result = HMath::hypergeometricPmf( k, N, M, n ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_hypercdf( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 4 ) + return HNumber::nan(); + + HNumber k = arguments[0]; + HNumber N = arguments[1]; + HNumber M = arguments[2]; + HNumber n = arguments[3]; + HNumber result = HMath::hypergeometricCdf( k, N, M, n ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_hypermean( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 3 ) + return HNumber::nan(); + + HNumber N = arguments[0]; + HNumber M = arguments[1]; + HNumber n = arguments[2]; + HNumber result = HMath::hypergeometricMean( N, M, n ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_hypervar( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 3 ) + return HNumber::nan(); + + HNumber N = arguments[0]; + HNumber M = arguments[1]; + HNumber n = arguments[2]; + HNumber result = HMath::hypergeometricVariance( N, M, n ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_poipmf( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 2 ) + return HNumber::nan(); + + HNumber k = arguments[0]; + HNumber l = arguments[1]; + HNumber result = HMath::poissonPmf( k, l ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_poicdf( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 2 ) + return HNumber::nan(); + + HNumber k = arguments[0]; + HNumber l = arguments[1]; + HNumber result = HMath::poissonCdf( k, l ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_poimean( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 1 ) + return HNumber::nan(); + + HNumber l = arguments[0]; + HNumber result = HMath::poissonMean( l ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + +HNumber function_poivar( const Evaluator * evaluator, + Function * function, + const FunctionArguments & arguments ) +{ + if ( arguments.count() != 1 ) + return HNumber::nan(); + + HNumber l = arguments[0]; + HNumber result = HMath::poissonVariance( l ); + + if ( result.isNan() ) + function->setError( function->name(), QString( "function undefined for specified arguments" ) ); + + return result; +} + + +class FunctionPrivate +{ +public: + QString name; + int argc; + QString desc; + QString error; + FunctionPtr ptr; + + FunctionPrivate(): name(), argc(0), desc(), error(), ptr(0) {} +}; + +class FunctionRepositoryPrivate +{ +public: + QHash functions; +}; + +Function::Function( const QString& name, int argc, FunctionPtr ptr, const QString& desc ): + d( new FunctionPrivate ) +{ + d->name = name; + d->argc = argc; + d->desc = QString( desc.toLatin1() ); + d->ptr = ptr; +} + +Function::Function( const QString& name, FunctionPtr ptr, const QString& desc ): + d( new FunctionPrivate ) +{ + d->name = name; + d->argc = -1; + d->desc = QString( desc.toLatin1() ); + d->ptr = ptr; +} + +Function::~Function() +{ + delete d; +} + +QString Function::name() const +{ + return d->name; +} + +QString Function::description() const +{ + return d->desc; +} + +QString Function::error() const +{ + return d->error; +} + +void Function::setError( const QString& context, const QString& error ) +{ + d->error = context + ": " + error; +} + +HNumber Function::exec( const Evaluator* eval, const FunctionArguments& args ) +{ + d->error = QString(); + if( !d->ptr ) + { + setError( QString("error"), QString( QString( "cannot execute function %1") ).arg( name() ) ); + return HNumber(0); + } + + if( d->argc >= 0 ) + if( args.count() != d->argc ) + { + if ( d->argc == 1 ) + setError( d->name, QString( QString( "function accepts 1 argument" ) ) ); + else + setError( d->name, QString( QString( "function accepts %1 arguments" ) ).arg( d->argc ) ); + return HNumber(0); + } + + return (*d->ptr)( eval, this, args ); +} + +FunctionRepository* FunctionRepository::s_self = 0; + +FunctionRepository* FunctionRepository::self() +{ + if( !s_self ) + s_self = new FunctionRepository(); + return s_self; +} + +FunctionRepository::FunctionRepository() +{ + d = new FunctionRepositoryPrivate; + + /* + ANALYSIS + */ + add( new Function( "abs", 1, function_abs, + QString("Absolute Value") ) ); + add( new Function( "average", function_average, + QString("Average (Arithmetic Mean)") ) ); + add( new Function( "log", 1, function_log, + QString("Base-10 Logarithm") ) ); + add( new Function( "lg", 1, function_lg, + QString("Base-2 Logarithm") ) ); + add( new Function( "bin", function_bin, + QString("Binary Representation") ) ); + add( new Function( "ceil", 1, function_ceil, + QString("Ceiling") ) ); + add( new Function( "cbrt", 1, function_cbrt, + QString("Cube Root") ) ); + add( new Function( "dec", function_dec, + QString("Decimal Representation") ) ); + add( new Function( "exp", 1, function_exp, + QString("Exponential") ) ); + add( new Function( "floor", 1, function_floor, + QString("Floor") ) ); + add( new Function( "frac", 1, function_frac, + QString("Fractional Part") ) ); + add( new Function( "geomean", function_geomean, + QString("Geometric Mean") ) ); + add( new Function( "hex", function_hex, + QString("Hexadecimal Representation") ) ); + add( new Function( "int", 1, function_int, + QString("Integer Part") ) ); + add( new Function( "max", function_max, + QString("Maximum") ) ); + add( new Function( "min", function_min, + QString("Minimum") ) ); + add( new Function( "ln", 1, function_ln, + QString("Natural Logarithm") ) ); + add( new Function( "oct", function_oct, + QString("Octal Representation") ) ); + add( new Function( "product", function_product, + QString("Product") ) ); + add( new Function( "round", function_round, + QString("Rounding") ) ); + add( new Function( "sign", 1, function_sign, + QString("Signum") ) ); + add( new Function( "sqrt", 1, function_sqrt, + QString("Square Root") ) ); + add( new Function( "sum", function_sum, + QString("Sum") ) ); + add( new Function( "trunc", function_trunc, + QString("Truncation") ) ); + /* + DISCRETE + */ + add( new Function( "gcd", function_gcd, + QString("Greatest Common Divisor") ) ); + add( new Function( "ncr", 2, function_nCr, + QString("Combination (Binomial Coefficient)") ) ); + add( new Function( "npr", 2, function_nPr, + QString("Permutation (Arrangement)") ) ); + /* + PROBABILITY + */ + add( new Function( "binompmf", 3, function_binompmf, + QString("Binomial Probability Mass Function") )); + add( new Function( "binomcdf", 3, function_binomcdf, + QString("Binomial Cumulative Distribution Function") )); + add( new Function( "binommean", 2, function_binommean, + QString("Binomial Distribution Mean") )); + add( new Function( "binomvar", 2, function_binomvar, + QString("Binomial Distribution Variance") )); + add( new Function( "hyperpmf", 4, function_hyperpmf, + QString("Hypergeometric Probability Mass Function") )); + add( new Function( "hypercdf", 4, function_hypercdf, + QString("Hypergeometric Cumulative Distribution Function"))); + add( new Function( "hypermean", 3, function_hypermean, + QString("Hypergeometric Distribution Mean") )); + add( new Function( "hypervar", 3, function_hypervar, + QString("Hypergeometric Distribution Variance") )); + add( new Function( "poipmf", 2, function_poipmf, + QString("Poissonian Probability Mass Function") )); + add( new Function( "poicdf", 2, function_poicdf, + QString("Poissonian Cumulative Distribution Function"))); + add( new Function( "poimean", 1, function_poimean, + QString("Poissonian Distribution Mean") )); + add( new Function( "poivar", 1, function_poivar, + QString("Poissonian Distribution Variance") )); + /* + TRIGONOMETRY + */ + add( new Function( "acos", 1, function_acos, + QString("Arc Cosine") ) ); + add( new Function( "asin", 1, function_asin, + QString("Arc Sine") ) ); + add( new Function( "atan", 1, function_atan, + QString("Arc Tangent") ) ); + add( new Function( "csc", 1, function_csc, + QString("Cosecant") ) ); + add( new Function( "cos", 1, function_cos, + QString("Cosine") ) ); + add( new Function( "cot", 1, function_cot, + QString("Cotangent") ) ); + add( new Function( "cosh", 1, function_cosh, + QString("Hyperbolic Cosine") ) ); + add( new Function( "degrees", 1, function_degrees, + QString("Degrees Of Arc") ) ); + add( new Function( "radians", 1, function_radians, + QString("Radians") ) ); + add( new Function( "sinh", 1, function_sinh, + QString("Hyperbolic Sine") ) ); + add( new Function( "tanh", 1, function_tanh, + QString("Hyperbolic Tangent") ) ); + add( new Function( "sec", 1, function_sec, + QString("Secant") ) ); + add( new Function( "sin", 1, function_sin, + QString("Sine") ) ); + add( new Function( "tan", 1, function_tan, + QString("Tangent") ) ); +} + +FunctionRepository::~FunctionRepository() +{ + while( d->functions.size() > 0 ) + { + delete d->functions[ 0 ]; + d->functions.remove( 0 ); + } + + delete d; +} + +void FunctionRepository::add( Function* function ) +{ + if( !function ) return; + d->functions.insert( function->name().toUpper(), function ); +} + +Function* FunctionRepository::function( const QString& name ) +{ + return d->functions.value( name.toUpper() ); +} + +QStringList FunctionRepository::functionNames() const +{ + QStringList result; + QHashIterator it( d->functions ); + while( it.hasNext() ) + { + it.next(); + result.append( it.key().toLower() ); + } + return result; +} diff --git a/engine/functions.h b/engine/functions.h new file mode 100644 index 0000000..3f6b717 --- /dev/null +++ b/engine/functions.h @@ -0,0 +1,71 @@ +/* Copyright (C) 2004 Ariya Hidayat + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + + +#ifndef FUNCTIONS +#define FUNCTIONS + +#include +#include +#include "hmath.h" + +class Function; +class Evaluator; + +class FunctionPrivate; +class FunctionRepositoryPrivate; + +typedef QVector FunctionArguments; +typedef HNumber (*FunctionPtr)( const Evaluator*, Function*, const FunctionArguments& ); + +class Function +{ +public: + Function( const QString& name, int argc, FunctionPtr ptr, const QString& desc ); + Function( const QString& name, FunctionPtr ptr, const QString& desc ); + ~Function(); + QString name() const; + QString description() const; + QString error() const; + void setError( const QString& context, const QString& error ); + HNumber exec( const Evaluator*, const FunctionArguments& args ); + +private: + FunctionPrivate* d; + Function( const Function& ); + Function& operator=( const Function& ); +}; + +class FunctionRepository +{ +public: + FunctionRepository(); + ~FunctionRepository(); + static FunctionRepository* self(); + void add( Function* function ); + Function* function( const QString& name ); + QStringList functionNames() const; +private: + FunctionRepositoryPrivate* d; + static FunctionRepository* s_self; + FunctionRepository( const FunctionRepository& ); + FunctionRepository& operator=( const FunctionRepository& ); +}; + + +#endif // FUNCTIONS diff --git a/engine/hmath.cpp b/engine/hmath.cpp new file mode 100644 index 0000000..0ccf59d --- /dev/null +++ b/engine/hmath.cpp @@ -0,0 +1,2127 @@ +/* HMath: C++ high precision math routines + Copyright (C) 2004 Ariya Hidayat + 2007 Helder Correia + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "hmath.h" +#include "number.h" + +#include +#include +#include +#include + +#include +#include + +// internal number of decimal digits +#define HMATH_MAX_PREC 150 + +// digits used for number comparison +// (not all are used, to work around propagated error problem) +#define HMATH_COMPARE_PREC 70 + +// maximum shown digits if prec is negative +#define HMATH_MAX_SHOWN 10 + +// from number.c, need to be freed somehow +extern bc_num _zero_; +extern bc_num _one_; +extern bc_num _two_; + +class HNumberPrivate +{ +public: + bc_num num; + bool nan; + char format; +}; + +void out_of_memory(void){ + return; +} + +void rt_warn(char * ,...){ + return; +} + +void rt_error(char * ,...){ + return; +} + +static bc_num h_create( int len = 1, int scale = 0 ) +{ + bc_num temp; + temp = (bc_num) malloc( sizeof(bc_struct) ); + temp->n_sign = PLUS; + temp->n_len = len; + temp->n_scale = scale; + temp->n_refs = 1; + temp->n_ptr = (char*) malloc( len+scale+1 ); + temp->n_value = temp->n_ptr; + temp->n_next = 0; + memset (temp->n_ptr, 0, len+scale+1); + return temp; +} + +static void h_destroy( bc_num n ) +{ + free( n->n_ptr ); + free( n ); +} + +// reclaim and free one bc_num from the freelist +// workaround for number.c, because it doesn't really free a number +// but instead put it in the pool of unused numbers +// this function will take it back from that pool and set it really free +static void h_grabfree() +{ + bc_num t = bc_new_num( 1, 0 ); + h_destroy( t ); +} + +// make an exact (explicit) copy +static bc_num h_copy( bc_num n ) +{ + int len = n->n_len; + int scale = n->n_scale; + bc_num result = h_create( len, scale ); + memcpy( result->n_ptr, n->n_value, len+scale+1 ); + result->n_sign = n->n_sign; + result->n_value = result->n_ptr; + h_grabfree(); + return result; +} + +// same as copy, but readjust decimal digits +static bc_num h_rescale( bc_num n, int sc ) +{ + int len = n->n_len; + int scale = MIN( sc, n->n_scale ); + bc_num result = h_create( len, scale ); + memcpy( result->n_ptr, n->n_value, len+scale+1 ); + result->n_sign = n->n_sign; + result->n_value = result->n_ptr; + h_grabfree(); + return result; +} + +// add two numbers, return newly allocated number +static bc_num h_add( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_add( n1, n2, &r, 1 ); + h_grabfree(); + return r; +} + +// multiply two numbers, return newly allocated number +static bc_num h_mul( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_multiply( n1, n2, &r, HMATH_MAX_PREC ); + h_grabfree(); + return r; +} + +enum Base {Decimal, Hexadec, Octal, Binary}; + +static bool isValidDigit(const char c, const Base b) +{ + switch (b) + { + case Hexadec: + return ((c >= '0' && c <= '9') || (c >= 'A' && c < 'G') || (c >= 'a' && c < 'g')); + case Octal: + return (c >= '0' && c < '8'); + case Binary: + return ( c == '0' || c == '1' ); + case Decimal: + default: + break; + } + return (c >= '0' && c <= '9'); +} + +// convert simple string to number +static bc_num h_str2num( const char* str, int scale = HMATH_MAX_PREC ) +{ + int digits, strscale; + const char *ptr; + char *nptr; + bool zero_int; + + /* Check for valid number and count digits. */ + ptr = str; + digits = 0; + strscale = 0; + zero_int = false; + Base base = Decimal; + if (*ptr == '+' || *ptr == '-') ptr++; /* Sign */ + if ( *ptr == '0' ) //leadeing 0, could be hexadec etc. + { + ptr++; + if (*ptr == 'x') { base = Hexadec; ptr++; } + else if (*ptr == 'o') { base = Octal; ptr++; } + else if (*ptr == 'b') { base = Binary; ptr++; } + else if (*ptr == 'd') ptr++; + } + while (*ptr == '0') ptr++; /* Skip leading zeros. */ + while (isValidDigit((int)*ptr, base)) ptr++, digits++; /* digits, maybe non decimal base */ + if (*ptr == '.') { if (base == Decimal) ptr++; else return h_create(); } /* decimal point */ + while (isdigit((int)*ptr)) ptr++, strscale++; /* digits, must be decimal, we had a period ; */ + if ((*ptr != '\0') || (digits+strscale == 0)) + return h_create(); + + bc_num num; + + switch (base) + { + case Hexadec: + { + int chr; + bc_num _16 = h_create(); bc_int2num( &_16, 16 ); + bc_num factor = h_create(); bc_int2num( &factor, 1 ); + bc_num tmp1, tmp2; + num = h_create(); bc_int2num( &num, 0 ); + for (;digits > 0; --digits) // n = n+f*x; + { + --ptr; + chr = CH_HEX(*ptr); + if (chr) // skip increment if digit is '0' + { + tmp1 = h_create(); + bc_int2num( &tmp1, chr ); // x + tmp2 = h_mul( factor, tmp1 ); // f*x + h_destroy( tmp1 ); + tmp1 = h_add( num, tmp2 ); // n+f*x; + h_destroy( num ); h_destroy( tmp2 ); + num = h_copy( tmp1 ); // n = n+f*x; + h_destroy( tmp1 ); + } + tmp1 = h_mul( factor, _16 ); // f*16 + h_destroy( factor ); + factor = h_copy( tmp1 ); // f = f*16; + h_destroy( tmp1 ); + } + if (*str == '-') num->n_sign = MINUS; else num->n_sign = PLUS; + break; + } + case Octal: + { + int chr; + bc_num _8 = h_create(); bc_int2num( &_8, 8 ); + bc_num factor = h_create(); bc_int2num( &factor, 1 ); + bc_num tmp1, tmp2; + num = h_create(); bc_int2num( &num, 0 ); + for (;digits > 0; --digits) // n = n+f*x; + { + --ptr; + chr = CH_VAL(*ptr); + if (chr) // skip increment if digit is '0' + { + tmp1 = h_create(); + bc_int2num( &tmp1, chr ); // x + tmp2 = h_mul( factor, tmp1 ); // f*x + h_destroy( tmp1 ); + tmp1 = h_add( num, tmp2 ); // n+f*x; + h_destroy( num ); h_destroy( tmp2 ); + num = h_copy( tmp1 ); // n = n+f*x; + h_destroy( tmp1 ); + } + tmp1 = h_mul( factor, _8 ); // f*8 + h_destroy( factor ); + factor = h_copy( tmp1 ); // f = f*8; + h_destroy( tmp1 ); + } + if (*str == '-') num->n_sign = MINUS; else num->n_sign = PLUS; + break; + } + case Binary: + { + bc_num _2 = h_create(); bc_int2num( &_2, 2 ); + bc_num factor = h_create(); bc_int2num( &factor, 1 ); + bc_num tmp; + num = h_create(); bc_int2num( &num, 0 ); + for (;digits > 0; --digits) // n = n+f; + { + --ptr; + if (CH_VAL(*ptr)) // only increment if bit is set + { + tmp = h_add( num, factor ); // n+f; + h_destroy( num ); num = h_copy( tmp ); // n = n+f; + h_destroy( tmp ); + } + tmp = h_mul( factor, _2 ); // f*2 + factor = h_copy( tmp ); // f = f*2; + h_destroy( tmp ); + } + if (*str == '-') num->n_sign = MINUS; else num->n_sign = PLUS; + break; + } + case Decimal: + default: + { + /* Adjust numbers and allocate storage and initialize fields. */ + strscale = MIN(strscale, scale); + if (digits == 0) + { + zero_int = true; + digits = 1; + } + + num = h_create( digits, strscale ); + + ptr = str; + if (*ptr == '-') + { + num->n_sign = MINUS; + ptr++; + } + else + { + num->n_sign = PLUS; + if (*ptr == '+') ptr++; + } + while (*ptr == '0') ptr++; + nptr = num->n_value; + if (zero_int) + { + *nptr++ = 0; + digits = 0; + } + for (;digits > 0; digits--) + *nptr++ = (char)CH_VAL(*ptr++); + + + if (strscale > 0) + { + ptr++; + for (;strscale > 0; strscale--) + *nptr++ = (char)CH_VAL(*ptr++); + } + } + } + return num; +} + + +// subtract two numbers, return newly allocated number +static bc_num h_sub( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_sub( n1, n2, &r, 1 ); + h_grabfree(); + return r; +} + +// divide two numbers, return newly allocated number +static bc_num h_div( bc_num n1, bc_num n2 ) +{ + bc_num r = h_create(); + bc_divide( n1, n2, &r, HMATH_MAX_PREC ); + h_grabfree(); + return r; +} + +// find 10 raise to num +// e.g.: when num is 5, it results 100000 +static bc_num h_raise10( int n ) +{ + // calculate proper factor + int len = abs(n)+2; + char* sf = new char[len+1]; + sf[len] = '\0'; + if( n >= 0 ) + { + sf[0] = '1'; + sf[len-1] = '\0'; + sf[len-2] = '\0'; + for( int i = 0; i < n; i++ ) + sf[i+1] = '0'; + } + else + { + sf[0] = '0'; sf[1] = '.'; + for( int i = 0; i < -n; i++ ) + sf[i+2] = '0'; + sf[len-1] = '1'; + } + + bc_num factor = h_str2num( sf, abs(n) ); + delete[] sf; + + return factor; +} + +// round up to certain decimal digits +static bc_num h_round( bc_num n, int prec ) +{ + // no need to round? + if( prec >= n->n_scale ) + return h_copy( n ); + + // example: rounding "3.14159" to 4 decimal digits means + // adding 0.5e-4 to 3.14159, it becomes 3.14164 + // taking only 4 decimal digits, so it's now 3.1416 + if( prec < 0 ) prec = 0; + bc_num x = h_raise10( -prec-1 ); + bc_num y = 0; + bc_int2num( &y, 5 ); + bc_num z = h_mul( x, y ); + z->n_sign = n->n_sign; + bc_num r = h_add( n, z ); + h_destroy( x ); + h_destroy( y ); + h_destroy( z ); + + // only digits we are interested in + bc_num v = h_rescale( r, prec ); + h_destroy( r ); + + return v; +} + +// trunc up to certain decimal digits +static bc_num h_trunc( bc_num n, int prec ) +{ + // no need to truncate? + if( prec >= n->n_scale ) + return h_copy( n ); + + if( prec < 0 ) + prec = 0; + + // only digits we are interested in + bc_num v = h_rescale( n, prec ); + + return v; +} + +// remove trailing zeros +static void h_trimzeros( bc_num num ) +{ + while( ( num->n_scale > 0 ) && ( num->n_len+num->n_scale > 0 ) ) + if( num->n_value[num->n_len+num->n_scale-1] == 0 ) + num->n_scale--; + else break; +} + +static void h_init() +{ + static bool h_initialized = false; + if( !h_initialized ) + { + h_initialized = true; + bc_init_numbers(); + } +} + + +HNumber::HNumber(): d(0) +{ + h_init(); + d = new HNumberPrivate; + d->nan = false; + d->format = 0; + d->num = h_create(); +} + +HNumber::HNumber( const HNumber& hn ): d(0) +{ + h_init(); + d = new HNumberPrivate; + d->nan = false; + d->num = h_create(); + operator=( hn ); +} + +HNumber::HNumber( int i ): d(0) +{ + h_init(); + d = new HNumberPrivate; + d->nan = false; + d->num = h_create(); + d->format = 0; + bc_int2num( &d->num, i ); +} + +HNumber::HNumber( const char* str ): d(0) +{ + h_init(); + d = new HNumberPrivate; + d->nan = false; + d->num = h_create(); + d->format = 0; + + if( str ) + if( strlen(str) == 3 ) + if( tolower(str[0])=='n' ) + if( tolower(str[1])=='a' ) + if( tolower(str[2])=='n' ) + d->nan = true; + + if( str && !d->nan ) + { + char* s = new char[ strlen(str)+1 ]; + strcpy( s, str ); + + bool isHex = false, isDecimal = true; + char* p = s; + for( ;; p++ ) + { + if (*p == 'x' && *(p-1) == '0') + { + isHex = true; + isDecimal = false; + continue; + } + if ((*p == 'b' || *p == 'o') && *(p-1) == '0') + { + isDecimal = false; + continue; + } + if( *p != '+' ) + if( *p != '-' ) + if( *p != '.' ) + if( !isdigit(*p) ) + if(!(isHex && *p >= 'a' && *p < 'g')) + break; + } + + int expd = 0; + if (isDecimal) + { + if( ( *p == 'e' ) || ( *p == 'E' ) ) + { + *p = '\0'; + expd = atoi( p+1 ); + } + } + + h_destroy( d->num ); + d->num = h_str2num( s ); + delete [] s; + if (isDecimal) + { + if( expd >= HMATH_MAX_PREC ) // too large + { + d->nan = true; + } + else + if( expd <= -HMATH_MAX_PREC ) // too small + { + d->nan = true; + } + else + if( expd != 0 ) + { + bc_num factor = h_raise10( expd ); + bc_num nn = h_copy( d->num ); + h_destroy( d->num ); + d->num = h_mul( nn, factor ); + h_destroy( nn ); + h_destroy( factor ); + } + } + h_trimzeros( d->num ); + } + +} + +HNumber::~HNumber() +{ + h_destroy( d->num ); + delete d; +} + +bool HNumber::isNan() const +{ + return d->nan; +} + +bool HNumber::isZero() const +{ + return !d->nan && ( bc_is_zero( d->num )!=0 ); +} + +bool HNumber::isPositive() const +{ + return !d->nan && !isNegative() && !isZero(); +} + +bool HNumber::isNegative() const +{ + return !d->nan && ( bc_is_neg( d->num )!=0 ); +} + +bool HNumber::isInteger() const +{ + return *this == HMath::integer( *this ); +} + +char HNumber::format() const +{ + return d->format; +} + +void HNumber::setFormat(char c) const +{ + d->format = d->nan?0:c; +} + +HNumber HNumber::nan() +{ + HNumber n; + n.d->nan = true; + return n; +} + +int HNumber::toInt() +{ + char* str = HMath::formatFixed( *this ); + std::string s( str ); + delete[] str; + std::istringstream iss( s ); + int i; + iss >> i; + + return i; +} + +HNumber& HNumber::operator=( const HNumber& hn ) +{ + d->nan = hn.d->nan; + h_destroy( d->num ); + d->num = h_copy( hn.d->num ); + d->format = hn.format(); + return *this; +} + +HNumber HNumber::operator+( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_add( d->num, num.d->num ); + + return result; +} + +HNumber& HNumber::operator+=( const HNumber& num ) +{ + HNumber n = HNumber(*this) + num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator-( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_sub( d->num, num.d->num ); + + return result; +} + +HNumber& HNumber::operator-=( const HNumber& num ) +{ + HNumber n = HNumber(*this) - num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator*( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_mul( d->num, num.d->num ); + return result; +} + +HNumber& HNumber::operator*=( const HNumber& num ) +{ + HNumber n = HNumber(*this) * num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator/( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + if( num == 0 ) return HNumber::nan(); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_div( d->num, num.d->num ); + return result; +} + +HNumber& HNumber::operator/=( const HNumber& num ) +{ + HNumber n = HNumber(*this) / num; + operator=( n ); + return *this; +} + +HNumber HNumber::operator%( const HNumber& num ) const +{ + if( isNan() ) return HNumber( *this ); + if( num.isNan() ) return HNumber( num ); + + HNumber result; + bc_modulo (d->num, num.d->num, &result.d->num, 0); + return result; +} + +bool HNumber::operator>( const HNumber& n ) const +{ + return HMath::compare( *this, n ) > 0; +} + +bool HNumber::operator<( const HNumber& n ) const +{ + return HMath::compare( *this, n ) < 0; +} + +bool HNumber::operator>=( const HNumber& n ) const +{ + return HMath::compare( *this, n ) >= 0; +} + +bool HNumber::operator<=( const HNumber& n ) const +{ + return HMath::compare( *this, n ) <= 0; +} + +bool HNumber::operator==( const HNumber& n ) const +{ + return HMath::compare( *this, n ) == 0; +} + +bool HNumber::operator!=( const HNumber& n ) const +{ + return HMath::compare( *this, n ) != 0; +} + +// format number in engineering notation +char* HMath::formatEngineering( const HNumber& hn, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + int nIntDigs = hn.d->num->n_len; + int nFracDigs = hn.d->num->n_scale; + char * digs = hn.d->num->n_value; + int tenExp; + + // find the exponent and the factor + int tzeros = 0; + for( int c=0; cnum->n_len+hn.d->num->n_scale; c++, tzeros++ ) + if( hn.d->num->n_value[c]!= 0 ) break; + int expd = hn.d->num->n_len - tzeros - 1; + + if ( hn >= 1 || hn <= -1 ) + { + // point must be shifted to the left, if needed + + if ( nIntDigs % 3 == 0 ) + { + // 3n digits to the left + if ( hn > 999 || hn < -999 ) + // n > 1 + tenExp = nIntDigs - 3; + else + // n = 1 + tenExp = 0; + } + else + // non-3n digits to the left + tenExp = nIntDigs - nIntDigs % 3; + } + else + { + // point must be shifted to the right + // find first non-zero digit to the right of the point + int startDigIdx = nIntDigs; + int endDigIdx = nIntDigs + nFracDigs - 1; + int idx; + for ( idx = startDigIdx; idx <= endDigIdx; idx++ ) + if ( digs[idx] != 0 ) + break; + // calculate exponent to shift right + while ( idx % 3 != 0 ) + idx++; + tenExp = -idx; + } + + // scale the number by a new factor + HNumber nn = hn * HMath::raise( 10, -tenExp ); + + // too close to zero? + if( hn.isZero() || ( expd <= -HMATH_COMPARE_PREC ) ) + { + nn = HNumber( 0 ); + tenExp = 0; + } + + // build result expression string with E notation + char* str = formatFixed( nn, prec ); + std::string resString = std::string( str ) + "e"; + free( str ); + std::stringstream ss; + ss << tenExp; + resString += ss.str(); + char * result = (char *) malloc( resString.size() + 1 ); + strcpy( result, resString.c_str() ); + + return result; +} + +// format number with fixed number of decimal digits +char* HMath::formatFixed( const HNumber& hn, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + bc_num n = h_copy( hn.d->num ); + h_trimzeros( n ); + + int oprec = prec; + if( prec < 0 ) + { + prec = HMATH_MAX_SHOWN; + if( n->n_scale < HMATH_MAX_SHOWN ) + prec = n->n_scale; + } + + // yes, this is necessary! + bc_num m = h_round( n, prec ); + h_trimzeros( m ); + h_destroy( n ); + n = m; + if( oprec < 0 ) + { + prec = HMATH_MAX_SHOWN; + if( n->n_scale < HMATH_MAX_SHOWN ) + prec = n->n_scale; + } + + // how many to allocate? + int len = n->n_len + prec; + if( n->n_sign != PLUS ) len++; + if( prec > 0 ) len++; + + char* str = (char*)malloc( len+1 ); + char* p = str; + + // the sign and the integer part + // but avoid printing "-0" + if( n->n_sign != PLUS ) + if( !bc_is_zero( n ) ) *p++ = '-'; + for( int c=0; cn_len; c++ ) + *p++ = (char)BCD_CHAR( n->n_value[c] ); + + // the fraction part + if( prec > 0 ) + { + *p++ = '.'; + int k = (prec < n->n_scale) ? prec : n->n_scale; + for( int d=0; dn_value[n->n_len+d] ); + for( int r=n->n_scale; rnum->n_len+hn.d->num->n_scale; c++, tzeros++ ) + if( hn.d->num->n_value[c]!= 0 ) break; + int expd = hn.d->num->n_len - tzeros - 1; + + // extra digits needed for the exponent part + int expn = 0; + for( int e = ::abs(expd); e > 0; e/=10 ) expn++; + if( expd <= 0 ) expn++; + + // scale the number by a new factor + HNumber nn = hn * HMath::raise( 10, -expd ); + + // too close to zero? + if( hn.isZero() || ( expd <= -HMATH_COMPARE_PREC ) ) + { + nn = HNumber(0); + expd = 0; + expn = 1; + } + + char* str = formatFixed( nn, prec ); + char* result = (char*) malloc( strlen(str)+expn+2 ); + strcpy( result, str ); + free( str ); + + // the exponential part + char* p = result + strlen(result); + *p++ = 'e'; p[expn] = '\0'; + if( expd < 0 ) *p = '-'; + for( int k=expn; k>0; k-- ) + { + int digit = expd % 10; + p[k-1] = (char)('0' + ::abs( digit )); + expd = expd / 10; + if( expd == 0 ) break; + } + + return result; +} + +char* HMath::formatGeneral( const HNumber& hn, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + // find the exponent and the factor + int tzeros = 0; + for( int c=0; cnum->n_len+hn.d->num->n_scale; c++, tzeros++ ) + if( hn.d->num->n_value[c]!= 0 ) break; + int expd = hn.d->num->n_len - tzeros - 1; + + char* str; + if( expd > 5 ) + str = formatScientific( hn, prec ); + else if( ( expd < -4 ) && (expd>-HMATH_COMPARE_PREC ) ) + str = formatScientific( hn, prec ); + else if ( (expd < 0) && (prec>0) && (expd < -prec) ) + str = formatScientific( hn, prec ); + else + str = formatFixed( hn, prec ); + + return str; +} + +char* HMath::formatHexadec( const HNumber& hn ) +{ + char* str; + if( hn.isNan() || !hn.isInteger()) + { + str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + int digits = 1; HNumber _16(16); + bool negative = (hn < 0); + HNumber tmp = negative ? HNumber(0)-hn : hn; + while (integer(tmp/=_16) > 0) ++digits; // how many digits + str = (char*)malloc( digits+3+negative ); + char* ptr = &str[digits+2+negative]; *ptr = '\0'; + HNumber val; + int i; + tmp = negative ? HNumber(0)-hn : hn; + HNumber f; + HNumber f_old(1); + while (digits--) + { + f = f_old*_16; + val = tmp % (f); tmp -= val; + val /= f_old; f_old = f; i = bc_num2long( val.d->num); + *--ptr = (i < 10) ? '0'+i : 'A'+i-10; + } + *--ptr = 'x'; *--ptr = '0'; if (negative) *--ptr = '-'; + + return str; +} + +char* HMath::formatOctal( const HNumber& hn ) +{ + char* str; + if( hn.isNan() || !hn.isInteger()) + { + str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + int digits = 1; HNumber _8(8); + bool negative = (hn < 0); + HNumber tmp = negative ? HNumber(0)-hn : hn; + while (integer(tmp/=_8) > 0) ++digits; // how many digits + str = (char*)malloc( digits+3+negative ); + char* ptr = &str[digits+2+negative]; *ptr = '\0'; + HNumber val; + tmp = negative ? HNumber(0)-hn : hn; + HNumber f; + HNumber f_old(1); + while (digits--) + { + f = f_old*_8; + val = tmp % (f); tmp -= val; + val /= f_old; f_old = f; + *--ptr = '0'+bc_num2long( val.d->num); + } + *--ptr = 'o'; *--ptr = '0'; if (negative) *--ptr = '-'; + + return str; +} + +char* HMath::formatBinary( const HNumber& hn ) +{ + char* str; + if( hn.isNan() || !hn.isInteger()) + { + str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + int digits = 1; HNumber _2(2); + bool negative = (hn < 0); + HNumber tmp = negative ? HNumber(0)-hn : hn; + while (integer(tmp/=_2) > 0) ++digits; // how many digits + str = (char*)malloc( digits+3+negative ); + char* ptr = &str[digits+2+negative]; *ptr = '\0'; + HNumber val; + HNumber _0(0); + tmp = negative ? _0-hn : hn; + HNumber f; + HNumber f_old(1); + while (digits--) + { + f = f_old*_2; + val = tmp % (f); tmp -= val; + val /= f_old; f_old = f; + *--ptr = (val == _0) ?'0':'1'; + } + *--ptr = 'b'; *--ptr = '0'; if (negative) *--ptr = '-'; + + return str; +} + +char* HMath::format( const HNumber& hn, char format, int prec ) +{ + if( hn.isNan() ) + { + char* str = (char*)malloc( 4 ); + str[0] = 'N'; + str[1] = 'a'; + str[2] = 'N'; + str[3] = '\0'; + return str; + } + + if ( format=='g' ) return formatGeneral ( hn, prec ); + else if( format=='f' ) return formatFixed ( hn, prec ); + else if( format=='n' ) return formatEngineering( hn, prec ); + else if( format=='e' ) return formatScientific ( hn, prec ); + else if( format=='h' ) return formatHexadec ( hn ); + else if( format=='o' ) return formatOctal ( hn ); + else if( format=='b' ) return formatBinary ( hn ); + + // fallback to 'g' + return formatGeneral( hn, prec ); +} + +HNumber HMath::phi() +{ + return HNumber("1.61803398874989484820458683436563811772030917980576" + "28621354486227052604628189024497072072041893911374" + "84754088075386891752126633862223536931793180060766" + "72635443338908659593958290563832266131992829026788" + "06752087668925017116962070322210432162695486262963" + "13614438149758701220340805887954454749246185695364" + "86444924104432077134494704956584678850987433944221" + "25448770664780915884607499887124007652170575179788" + "34166256249407589069704000281210427621771117778053" + "15317141011704666599146697987317613560067087480710" + "13179523689427521948435305678300228785699782977834" + "78458782289110976250030269615617002504643382437764" + "86102838312683303724292675263116533924731671112115" + "88186385133162038400522216579128667529465490681131" + "71599343235973494985090409476213222981017261070596" + "11645629909816290555208524790352406020172799747175" + "34277759277862561943208275051312181562855122248093" + "94712341451702237358057727861600868838295230459264" + "78780178899219902707769038953219681986151437803149" + "97411069260886742962267575605231727775203536139362"); +} + +HNumber HMath::pi() +{ + return HNumber("3.14159265358979323846264338327950288419716939937510" + "58209749445923078164062862089986280348253421170679" + "82148086513282306647093844609550582231725359408128" + "48111745028410270193852110555964462294895493038196" + "44288109756659334461284756482337867831652712019091" + "45648566923460348610454326648213393607260249141273" + "72458700660631558817488152092096282925409171536436" + "78925903600113305305488204665213841469519415116094" + "33057270365759591953092186117381932611793105118548" + "07446237996274956735188575272489122793818301194912" + "98336733624406566430860213949463952247371907021798" + "60943702770539217176293176752384674818467669405132" + "00056812714526356082778577134275778960917363717872" + "14684409012249534301465495853710507922796892589235" + "42019956112129021960864034418159813629774771309960" + "51870721134999999837297804995105973173281609631859" + "50244594553469083026425223082533446850352619311881" + "71010003137838752886587533208381420617177669147303" + "59825349042875546873115956286388235378759375195778" + "1857780532171226806613001927876611195909216420198" ); +} + +HNumber HMath::add( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 + n2; + return result; +} + +HNumber HMath::sub( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 - n2; + return result; +} + +HNumber HMath::mul( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 * n2; + return result; +} + +HNumber HMath::div( const HNumber& n1, const HNumber& n2 ) +{ + HNumber result = n1 / n2; + return result; +} + +int HMath::compare( const HNumber& n1, const HNumber& n2 ) +{ + if( n1.isNan() && n2.isNan() ) return 0; + + HNumber delta = sub( n1, n2 ); + delta = HMath::round( delta, HMATH_COMPARE_PREC ); + if( delta.isZero() ) return 0; + else if( delta.isNegative() ) return -1; + return 1; +} + +HNumber HMath::max( const HNumber& n1, const HNumber& n2 ) +{ + if ( n1.isNan() || n2.isNan() ) + return HNumber::nan(); + + if ( n1 >= n2 ) + return n1; + else + return n2; +} + +HNumber HMath::min( const HNumber& n1, const HNumber& n2 ) +{ + if ( n1.isNan() || n2.isNan() ) + return HNumber::nan(); + + if ( n1 <= n2 ) + return n1; + else + return n2; +} + +HNumber HMath::abs( const HNumber& n ) +{ + HNumber r( n ); + r.d->num->n_sign = PLUS; + return r; +} + +HNumber HMath::negate( const HNumber& n ) +{ + if( n.isNan() || n.isZero() ) + return HNumber( n ); + + HNumber result( n ); + result.d->num->n_sign = ( n.d->num->n_sign == PLUS ) ? MINUS : PLUS; + return result; +} + +HNumber HMath::round( const HNumber& n, int prec ) +{ + if( n.isNan() ) + return HNumber::nan(); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_round( n.d->num, prec ); + return result; +} + +HNumber HMath::trunc( const HNumber& n, int prec ) +{ + if( n.isNan() ) + return HNumber::nan(); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_trunc( n.d->num, prec ); + return result; +} + +HNumber HMath::integer( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isZero() ) + return HNumber( 0 ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_rescale( n.d->num, 0 ); + return result; +} + +HNumber HMath::frac( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + return n - integer(n); +} + +HNumber HMath::floor( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isInteger() ) + return n; + else if( n.isPositive() ) + return n - frac(n); + else + return n - HNumber(1) - frac(n); +} + +HNumber HMath::ceil( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isInteger() ) + return n; + else if( n.isPositive() ) + return n + HNumber(1) - frac(n); + else + return n - frac(n); +} + +HNumber HMath::gcd( const HNumber& n1, const HNumber& n2 ) +{ + if( n1.isNan() || n2.isNan() ) + return HNumber::nan(); + + HNumber a = abs( n1 ); + HNumber b = abs( n2 ); + + if ( a == 0 ) + return b; + if ( b == 0 ) + return a; + + // run Euclidean algorithm + while ( true ) + { + a = a % b; + + if ( a == 0 ) + return b; + + b = b % a; + + if ( b == 0 ) + return a; + } +} + +HNumber HMath::sqrt( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isZero() ) + return n; + + if( n.isNegative() ) + return HNumber::nan(); + + // useful constant + HNumber half("0.5"); + + // Use Netwon-Raphson algorithm + HNumber r( 1 ); + for( int i = 0; i < HMATH_MAX_PREC; i++ ) + { + HNumber q = n / r; + if( r == q ) break; + HNumber s = r + q; + r = s * half; + } + + return r; +} + +HNumber HMath::cbrt( const HNumber& n ) +{ + if( n.isNan() ) + return HNumber::nan(); + + if( n.isZero() ) + return n; + + // useful constants + HNumber three = HNumber("3"); + HNumber twoThirds = HNumber("2") / three; + + // iterations to approximate result + // X[i+1] = (2/3)X[i] + n / (3 * X[i]^2)) + // initial guess = sqrt( n ) + // r = X[i], q = X[i+1], a = n + HNumber a = n.isNegative() ? n * HNumber("-1") : n; + HNumber r = sqrt( a ); + for( int i = 0; i < HMATH_MAX_PREC; i++ ) + { + HNumber q = (twoThirds * r) + (a / (three * r * r)); + if( r == q ) + break; + r = q; + } + + if( n.isNegative()) + return r * HNumber("-1"); + else + return r; +} + +HNumber HMath::raise( const HNumber& n1, int n ) +{ + if( n1.isNan() ) return n1; + + // http://en.wikipedia.org/wiki/Exponentiation#Powers_of_zero + if( n1.isZero() ) + { + if( n < 0 ) + return HNumber::nan(); + if( n > 0 ) + return HNumber(0); + + // debatable, see http://en.wikipedia.org/wiki/Empty_product#0_raised_to_the_0th_power + // vs http://mathworld.wolfram.com/Power.html + if( n == 0 ) + return HNumber(1); + } + + if( n1 == HNumber(1) ) return n1; + if( n == 0 ) return HNumber(1); + if( n == 1 ) return n1; + + if(n > 0) + { + // squaring algorithm to find exponentiation + // see http://en.wikipedia.org/wiki/Exponentiation_by_squaring + HNumber result = HNumber(1); + HNumber x = n1; + while(n > 0) + { + if(n & 1) + { + result = result * x; + n--; + } + x = x * x; + n = n >> 1; + } + return result; + } + + HNumber result = n1; + for( ; n < 1; n++ ) + result /= n1; + + return result; +} + +HNumber HMath::raise( const HNumber& n1, const HNumber& n2 ) +{ + if( n1.isNan() ) return HNumber::nan(); + if( n2.isNan() ) return HNumber::nan(); + + // see previous function + if( n1.isZero() ) + { + if( n2.isNegative() ) + return HNumber::nan(); + if( n2.isPositive() ) + return HNumber(0); + if( n2.isZero() ) + return HNumber(1); + } + + if( n1 == HNumber(1) ) return n1; + if( n2.isZero() ) return HNumber(1); + if( n2 == HNumber(1) ) return n1; + + HNumber result; + + // n1 is negative, n2 must be integer + if( n1.isNegative() ) + { + if( HMath::integer(n2) != n2) + result = HNumber::nan(); + else + { + // use integer raise function + HNumber nn = n2; + result = raise( n1, atoi( HMath::formatFixed(nn, 0) ) ); + } + } + else + { + // n2 integer? use the integer raise version + if( HMath::integer(n2) == n2) + { + // use integer raise function + HNumber nn = n2; + result = raise( n1, atoi( HMath::formatFixed(nn, 0) ) ); + } + else + { + // x^y = exp( y*ln(x) ) + result = n2 * HMath::ln(n1); + result = HMath::exp( result ); + } + } + + return result; +} + +HNumber HMath::exp( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + bool negative = x.isNegative(); + HNumber xs = HMath::abs( x ); + + // adjust so that x is less than 1 + // use the fact that e^x = (e^(x/2))^2 + HNumber one(1); + HNumber half("0.5"); + unsigned factor = 0; + while( xs > one ) + { + factor ++; + xs = xs * half; + } + + // Taylor expansion: e^x = 1 + x + x^2/2! + x^3/3! + ... + HNumber num = xs; + HNumber den = 1; + HNumber sum = xs + 1; + + // now loop to sum the series + for( int i = 2; i < HMATH_MAX_PREC; i++ ) + { + num *= xs; + den *= HNumber(i); + if( num.isZero() ) break; + HNumber s = HMath::div( num, den ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = sum; + if( factor > 0 ) + while( factor > 0 ) + { + factor--; + result *= result; + } + + if( negative ) + result = HMath::div( HNumber(1), result ); + + return result; +}; + +HNumber HMath::ln( const HNumber& x ) +{ + if ( x.isNan() || x <= 0 ) + return HNumber::nan(); + + // short circuit + if( x == HNumber(10) ) + return HNumber("2.30258509299404568401799145468436420760110148862877" + "29760333279009675726102948650438303813865953227795" + "49054520440916779445247118780973037711833599749301" + "72118016928228381938415404059160910960135436620869" ); + + // useful constants + HNumber two(2); + HNumber one(1); + HNumber half("0.5"); + + // adjust so that x is between 0.5 and 2.0 + // use the fact that ln(x^2) = 2*ln(x) + HNumber xs( x ); + unsigned factor = 2; + while( xs >= two ) + { + factor *= 2; + xs = HMath::sqrt( xs ); + } + while( xs <= half ) + { + factor *= 2; + xs = HMath::sqrt( xs ); + } + + // Taylor expansion: ln(x) = 2(a+a^3/3+a^5/5+...) + // where a=(x-1)/(x+1) + HNumber p = xs - 1; + HNumber q = xs + 1; + HNumber a = p / q; + HNumber as = a*a; + HNumber t = a; + HNumber sum = a; + + // loop for the series (limited to avoid nasty cases) + for( int i = 3; i < HMATH_MAX_PREC; i+= 2 ) + { + t *= as; + if( t.isZero() ) break; + HNumber s = HMath::div( t, HNumber(i) ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = sum * HNumber( factor ); + return result; +} + +HNumber HMath::log( const HNumber& x ) +{ + if ( x.isNan() || x <= 0 ) + return HNumber::nan(); + + return HMath::ln( x ) / HMath::ln(10); +} + +HNumber HMath::lg( const HNumber& x ) +{ + if ( x.isNan() || x <= 0 ) + return HNumber::nan(); + + return HMath::ln( x ) / HMath::ln(2); +} + +// ensure angle is within 0 to 2*pi +// useful for sin, cos +static HNumber simplifyAngle( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + +#if 1 + // using simple method + HNumber pi2 = HMath::pi() * 2; + HNumber nn = x / pi2; + HNumber xs = x - HMath::integer(nn)*pi2; + if( xs.isNegative() ) xs += pi2; + + return xs; +#else + // using argument reduction method + // see http://www.derekroconnor.net/Software/Ng--ArgReduction.pdf + HNumber factor = HNumber(2) / HMath::pi(); + HNumber y = x * factor; + HNumber f = HMath::frac(y); + HNumber r = f * HMath::pi()/HNumber(2); + + // find int(y) mod 4 + HNumber n = HMath::integer( y ); + HNumber m = n - HNumber(4)*HMath::integer( n / HNumber(4) ); + HNumber halfpi = HMath::pi() / HNumber(2); + if(m == HNumber(1)) + r += halfpi; + if(m == HNumber(2)) + r += HMath::pi(); + if(m == HNumber(3)) + r += halfpi + HMath::pi(); + + return r; +#endif +} + +HNumber HMath::sin( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // adjust to small angle for speedup + HNumber xs = simplifyAngle( x ); + + // limits shortcut + if ( x == 0 || x == HMath::pi() || x == HMath::pi() * 2 ) + return HNumber( 0 ); + else if( x == HMath::pi() / 2 ) + return HNumber( 1 ); + else if( x == HMath::pi() * 3 / 2 ) + return HNumber( -1 ); + + // Taylor expansion: sin(x) = x - x^3/3! + x^5/5! - x^7/7! ... + HNumber xsq = xs*xs; + HNumber num = xs; + HNumber den = 1; + HNumber sum = xs; + + // loop for the series (limited to avoid nasty cases) + for( int i = 3; i < HMATH_MAX_PREC; i+=2 ) + { + num *= xsq; + if( num.isZero() ) break; + den *= HNumber(i-1); + den *= HNumber(i); + den = HMath::negate( den ); + HNumber s = HMath::div( num, den ); + if( s.isZero() ) break; + sum += s; + } + + return sum; +} + +HNumber HMath::cos( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // adjust to small angle for speedup + HNumber xs = simplifyAngle( x ); + + // limits shortcut + if ( x == 0 || x == HMath::pi() * 2 ) + return HNumber( 1 ); + else if( x == HMath::pi() / 2 || x == HMath::pi() * 3 / 2 ) + return HNumber( 0 ); + else if( x == HMath::pi() ) + return HNumber( -1 ); + + // Taylor expansion: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! ... + HNumber xsq = xs*xs; + HNumber num = 1; + HNumber den = 1; + HNumber sum = 1; + + // loop for the series (limited to avoid nasty cases) + for( int i = 2; i < HMATH_MAX_PREC; i+=2 ) + { + num *= xsq; + if( num.isZero() ) break; + den *= HNumber(i-1); + den *= HNumber(i); + den = HMath::negate( den ); + HNumber s = num / den; + if( s.isZero() ) break; + sum += s; + } + + return sum; +} + +HNumber HMath::tan( const HNumber& x ) +{ + return HMath::sin(x) / HMath::cos(x); +} + +HNumber HMath::cot( const HNumber& x ) +{ + return HMath::cos(x) / HMath::sin(x); +} + +HNumber HMath::sec( const HNumber& x ) +{ + return HNumber(1) / HMath::cos(x); +} + +HNumber HMath::csc( const HNumber& x ) +{ + return HNumber(1) / HMath::sin(x); +} + +HNumber HMath::atan( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // useful constants + HNumber one("1.0"); + HNumber c( "0.2" ); + + // short circuit + if( x == c ) + return HNumber("0.19739555984988075837004976519479029344758510378785" + "21015176889402410339699782437857326978280372880441" + "12628118073691360104456479886794239355747565495216" + "30327005221074700156450155600612861855266332573187" ); + + if( x == one ) + // essentially equals to HMath::pi()/4; + return HNumber("0.78539816339744830961566084581987572104929234984377" + "64552437361480769541015715522496570087063355292669" + "95537021628320576661773461152387645557931339852032" + "12027936257102567548463027638991115573723873259549" ); + + bool negative = x.isNegative(); + HNumber xs = HMath::abs( x ); + + // adjust so that x is less than c (we choose c = 0.2) + // use the fact that atan(x) = atan(c) + atan((x-c)/(1+xc)) + HNumber factor(0); + HNumber base(0); + while( xs > c ) + { + base = HMath::atan( c ); + factor += one; + HNumber p = xs - c; + HNumber q = xs * c; + xs = p / (q+one); + } + + // Taylor series: atan(x) = x - x^3/3 + x^5/5 - x^7/7 + ... + HNumber num = xs; + HNumber xsq = xs*xs; + HNumber den = 1; + HNumber sum = xs; + + // loop for the series (limited to avoid nasty cases) + for( int i = 3; i < HMATH_MAX_PREC; i+=2 ) + { + num *= xsq; + if( num.isZero() ) break; + den = HNumber(i); + int n = (i-1)/2; + if( n&1 ) den = HNumber(-i); + HNumber s = HMath::div( num, den ); + if( s.isZero() ) break; + sum += s; + } + + HNumber result = factor*base + sum; + if( negative ) result = HMath::negate( result ); + return result; +}; + +HNumber HMath::asin( const HNumber & x ) +{ + if ( x.isNan() || x < -1 || x > 1 ) + return HNumber::nan(); + + // shortcuts + if ( x == -1 ) + return HMath::pi() / 2 * (-1); + if ( x == 0 ) + return HNumber( 0 ); + if ( x == 1 ) + return HMath::pi() / 2; + + // asin( x ) = atan( x / sqrt( 1 - x * x ) ); + HNumber d = HMath::sqrt( HNumber( 1 ) - x * x ); + if ( d == 0 ) + { + HNumber result = HMath::pi() / 2; + if ( x < 0 ) + result *= -1; + return result; + } + + return HMath::atan( x / d ); +}; + +HNumber HMath::acos( const HNumber & x ) +{ + if ( x.isNan() || x < -1 || x > 1 ) + return HNumber::nan(); + + // shortcuts + if ( x == -1 ) + return HMath::pi(); + if ( x == 0 ) + return HMath::pi()/2; + if ( x == 1 ) + return 0; + + // acos( x ) = atan( sqrt( 1 - x * x ) / x ); + HNumber n = HMath::sqrt( HNumber( 1 ) - x * x ); + return HMath::atan( n / x ); +}; + +HNumber HMath::sinh( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // sinh(x) = 0.5*(e^x - e^(-x) ) + HNumber result = HMath::exp(x) - HMath::exp( HMath::negate(x) ); + result = result / 2; + + return result; +} + +HNumber HMath::cosh( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // cosh(x) = 0.5*(e^x - e^(-x) ) + HNumber result = HMath::exp(x) + HMath::exp( HMath::negate(x) ); + result = result / 2; + + return result; +} + +HNumber HMath::tanh( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // tanh(h) = sinh(x)/cosh(x) + HNumber c = HMath::cosh( x ); + if( c.isZero() ) + return HNumber::nan(); + + HNumber s = HMath::sinh( x ); + HNumber result = s / c; + + return result; +} + +HNumber HMath::sign( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + HNumber result( 0 ); + if (x == result) + return result; + if (x < result) + result = HNumber(-1); + else + result = HNumber(1); + + return result; +} + +HNumber HMath::nCr( const HNumber & n, + const HNumber & r ) +{ + if ( n.isNan() || n < 0 || + r.isNan() || r < 0 || + r > n ) + return HNumber::nan(); + + // shortcuts + HNumber one = HNumber( 1 ); + if ( r == HNumber( 0 ) || r == n ) + return one; + if ( r == one ) + return n; + + if ( r > n/2 ) + return factorial( n, r+1 ) / factorial( n-r, 1 ); + else + return factorial( n, n-r+1 ) / factorial( r, 1 ); +} + +HNumber HMath::nPr( const HNumber & n, + const HNumber & r ) +{ + if ( n.isNan() || n < 0 || + r.isNan() || r < 0 || + r > n ) + return HNumber::nan(); + + // shortcuts + HNumber one = HNumber( 1 ); + if ( r == HNumber( 0 ) ) + return one; + if ( r == one ) + return n; + if ( r == n ) + return factorial( n ); + + return factorial( n, n-r+1 ); +} + +HNumber HMath::factorial( const HNumber& x, const HNumber& base ) +{ + if( x.isNan() + || x < HNumber(0) || !x.isInteger() + || base < HNumber(1) || !base.isInteger() + || (x < base && x != HNumber(0)) ) + return HNumber::nan(); + + if( x == HNumber(0) || x == HNumber(1) ) + return HNumber(1); + + if( x == base ) + return x; + + HNumber one(1); + HNumber result = one; + HNumber n = base - one; + do + { + n = n + one; + result = result * n; + } + while (n < x); + + return result; +} + +HNumber HMath::binomialPmf( const HNumber & k, + const HNumber & n, + const HNumber & p ) +{ + if ( k.isNan() || ! k.isInteger() || k < 0 || k > n + || n.isNan() || ! n.isInteger() || n < 0 + || p.isNan() || p < 0 || p > 1 ) + return HNumber::nan(); + + return HMath::nCr( n, k ) * HMath::raise( p, k ) * + HMath::raise( HNumber(1)-p, n-k ); +} + +HNumber HMath::binomialCdf( const HNumber & k, + const HNumber & n, + const HNumber & p ) +{ + if ( k.isNan() || ! k.isInteger() || k < 0 || k > n + || n.isNan() || ! n.isInteger() || n < 0 + || p.isNan() || p < 0 || p > 1 ) + return HNumber::nan(); + + HNumber result( 0 ); + for ( HNumber i( 0 ); i <= k; i += 1 ) + result += HMath::nCr( n, i ) * HMath::raise( p, i ) + * HMath::raise( HNumber(1)-p, n-i ); + return result; +} + +HNumber HMath::binomialMean( const HNumber & n, + const HNumber & p ) +{ + if ( n.isNan() || ! n.isInteger() || n < 0 + || p.isNan() || p < 0 || p > 1 ) + return HNumber::nan(); + + return n * p; +} + +HNumber HMath::binomialVariance( const HNumber & n, + const HNumber & p ) +{ + if ( n.isNan() || ! n.isInteger() || n < 0 + || p.isNan() || p < 0 || p > 1 ) + return HNumber::nan(); + + return n * p * ( HNumber(1) - p ); +} + +HNumber HMath::hypergeometricPmf( const HNumber & k, + const HNumber & N, + const HNumber & M, + const HNumber & n ) +{ + if ( k.isNan() || ! k.isInteger() || k < max( 0, M+n-N ) || k > min( M, n ) + || N.isNan() || ! N.isInteger() || N < 0 + || M.isNan() || ! M.isInteger() || M < 0 || M > N + || n.isNan() || ! n.isInteger() || n < 0 || n > N ) + return HNumber::nan(); + + return HMath::nCr( M, k ) * HMath::nCr( N-M, n-k ) / HMath::nCr( N, n ); +} + +HNumber HMath::hypergeometricCdf( const HNumber & k, + const HNumber & N, + const HNumber & M, + const HNumber & n ) +{ + if ( k.isNan() || ! k.isInteger() || k < max( 0, M+n-N ) || k > min( M, n ) + || N.isNan() || ! N.isInteger() || N < 0 + || M.isNan() || ! M.isInteger() || M < 0 || M > N + || n.isNan() || ! n.isInteger() || n < 0 || n > N ) + return HNumber::nan(); + + HNumber result( 0 ); + for ( HNumber i( 0 ); i <= k; i += 1 ) + result += HMath::nCr( M, i ) * HMath::nCr( N-M, n-i ) / HMath::nCr( N, n ); + return result; +} + +HNumber HMath::hypergeometricMean( const HNumber & N, + const HNumber & M, + const HNumber & n ) +{ + if ( N.isNan() || ! N.isInteger() || N < 0 + || M.isNan() || ! M.isInteger() || M < 0 || M > N + || n.isNan() || ! n.isInteger() || n < 0 || n > N ) + return HNumber::nan(); + + return n * M / N; +} + +HNumber HMath::hypergeometricVariance( const HNumber & N, + const HNumber & M, + const HNumber & n ) +{ + if ( N.isNan() || ! N.isInteger() || N < 0 + || M.isNan() || ! M.isInteger() || M < 0 || M > N + || n.isNan() || ! n.isInteger() || n < 0 || n > N ) + return HNumber::nan(); + + return (n * (M/N) * (HNumber(1) - M/N) * (N-n)) / (N - HNumber(1)); +} + +HNumber HMath::poissonPmf( const HNumber & k, + const HNumber & l ) +{ + if ( k.isNan() || ! k.isInteger() || k < 0 + || l.isNan() || l < 0 ) + return HNumber::nan(); + + return exp( l*(-1) ) * raise( l, k ) / factorial( k ); +} + +HNumber HMath::poissonCdf( const HNumber & k, + const HNumber & l ) +{ + if ( k.isNan() || ! k.isInteger() || k < 0 + || l.isNan() || l < 0 ) + return HNumber::nan(); + + HNumber result( 0 ); + for ( HNumber i( 0 ); i <= k; i += 1 ) + result += exp( l*(-1) ) * raise( l, i ) / factorial( i ); + + return result; +} + +HNumber HMath::poissonMean( const HNumber & l ) +{ + if ( l.isNan() || l < 0 ) + return HNumber::nan(); + + return l; +} + +HNumber HMath::poissonVariance( const HNumber & l ) +{ + if ( l.isNan() || l < 0 ) + return HNumber::nan(); + + return l; +} + +void HMath::finalize() +{ + bc_free_num( &_zero_ ); + bc_free_num( &_one_ ); + bc_free_num( &_two_ ); + free( _one_ ); + free( _zero_ ); + free( _two_ ); + h_grabfree(); + h_grabfree(); + h_grabfree(); + h_grabfree(); +} + +std::ostream& operator<<( std::ostream& s, const HNumber& n ) +{ + char* str = HMath::formatFixed( n ); + s << str; + delete[] str; + return s; +} diff --git a/engine/hmath.h b/engine/hmath.h new file mode 100644 index 0000000..d2a82bc --- /dev/null +++ b/engine/hmath.h @@ -0,0 +1,650 @@ +/* HMath: C++ high precision math routines + Copyright (C) 2004 Ariya Hidayat + 2007 Helder Correia + + 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 2 + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef HMATH_H +#define HMATH_H + +#include + +class HMath; +class HNumberPrivate; + +class HNumber +{ + friend class HMath; + +public: + /*! + * Creates a new number. + */ + HNumber(); + + /*! + * Destroys this number. + */ + ~HNumber(); + + /*! + * Copies from another number. + */ + HNumber( const HNumber& ); + + /*! + * Assigns from another number. + */ + HNumber& operator=( const HNumber& ); + + /*! + * Creates a new number from an integer value. + */ + HNumber( int i ); + + /*! + * Creates a new number from a string. + */ + HNumber( const char* ); + + /*! + * Returns true if this number is Not a Number (NaN). + */ + bool isNan() const; + + /*! + * Returns true if this number is zero. + */ + bool isZero() const; + + /*! + * Returns true if this number is more than zero. + */ + bool isPositive() const; + + /*! + * Returns true if this number is less than zero. + */ + bool isNegative() const; + + /*! + * Returns true if this number is integer. + */ + bool isInteger() const; + + /*! + * Returns the preferred format (base/precision), default is 0 + */ + char format() const; + + /*! + * Sets the preferred format (base/precision), default is 0 + */ + void setFormat(char c = 0) const; + + /*! + * Adds another number. + */ + HNumber operator+( const HNumber& ) const; + + /*! + * Adds another number. + */ + HNumber& operator+=( const HNumber& ); + + /*! + * Subtract from another number. + */ + HNumber operator-( const HNumber& ) const; + + /*! + * Subtract from another number. + */ + HNumber& operator-=( const HNumber& ); + + /*! + * Multiplies with another number. + */ + HNumber operator*( const HNumber& ) const; + + /*! + * Multiplies with another number. + */ + HNumber& operator*=( const HNumber& ); + + /*! + * Divides with another number. + */ + HNumber operator/( const HNumber& ) const; + + /*! + * Divides with another number. + */ + HNumber& operator/=( const HNumber& ); + + /*! + * Modulo (rest of integer division) + */ + HNumber operator%( const HNumber& ) const; + + /*! + * Returns true if this number is greater than n. + */ + bool operator>( const HNumber& n ) const; + + /*! + * Returns true if this number is less than n. + */ + bool operator<( const HNumber& n ) const; + + /*! + * Returns true if this number is greater than or equal to n. + */ + bool operator>=( const HNumber& n ) const; + + /*! + * Returns true if this number is less than or equal to n. + */ + bool operator<=( const HNumber& n ) const; + + /*! + * Returns true if this number is equal to n. + */ + bool operator==( const HNumber& n ) const; + + /*! + * Returns true if this number is not equal to n. + */ + bool operator!=( const HNumber& n ) const; + + /*! + * Returns a NaN (Not a Number). + */ + static HNumber nan(); + + /*! + * Returns the number as an int. + * It is meant to convert small (integer) numbers only and no + * checking is done whatsoever. + */ + int toInt(); + +private: + HNumberPrivate* d; +}; + +class HMath +{ +public: + /*! + * Formats the given number as string, using specified decimal digits. + * Note that the returned string must be freed. + */ + static char* format( const HNumber&n, char format = 'g', int prec = -1 ); + + /*! + * Formats the given number as string, in engineering notation. + * Note that the returned string must be freed. + */ + static char* formatEngineering( const HNumber&n, int prec = -1 ); + + /*! + * Formats the given number as string, using specified decimal digits. + * Note that the returned string must be freed. + */ + static char* formatFixed( const HNumber&n, int prec = -1 ); + + /*! + * Formats the given number as string, in scientific format. + * Note that the returned string must be freed. + */ + static char* formatScientific( const HNumber&n, int prec = -1 ); + + /*! + * Formats the given number as string, using specified decimal digits. + * Note that the returned string must be freed. + */ + static char* formatGeneral( const HNumber&n, int prec = -1 ); + + /*! + * Formats the given number as string, using hexadecimal digits. (NaN if hn isn't integer) + * Note that the returned string must be freed. + */ + static char* formatHexadec( const HNumber&n ); + + /*! + * Formats the given number as string, using octal digits. (NaN if hn isn't integer) + * Note that the returned string must be freed. + */ + static char* formatOctal( const HNumber&n ); + + /*! + * Formats the given number as string, using binary digits. (NaN if hn isn't integer) + * Note that the returned string must be freed. + */ + static char* formatBinary( const HNumber&n ); + + /*! + * Returns the constant phi (golden number). + */ + static HNumber phi(); + + /*! + * Returns the constant pi. + */ + static HNumber pi(); + + /*! + * Adds two numbers. + */ + static HNumber add( const HNumber& n1, const HNumber& n2 ); + + /*! + * Subtracts two numbers. + */ + static HNumber sub( const HNumber& n1, const HNumber& n2 ); + + /*! + * Multiplies two numbers. + */ + static HNumber mul( const HNumber& n1, const HNumber& n2 ); + + /*! + * Divides two numbers. + */ + static HNumber div( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns -1, 0, 1 if n1 is less than, equal to, or more than n2. + */ + static int compare( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns the maximum of two numbers. + */ + static HNumber max( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns the minimum of two numbers. + */ + static HNumber min( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns the absolute value of n. + */ + static HNumber abs( const HNumber& n ); + + /*! + * Returns the negative of n. + */ + static HNumber negate( const HNumber& n ); + + /*! + * Returns the integer part of n. + */ + static HNumber integer( const HNumber& n ); + + /*! + * Returns the fraction part of n. + */ + static HNumber frac( const HNumber& n ); + + /*! + * Returns the floor of n. + */ + static HNumber floor( const HNumber& n ); + + /*! + * Returns the ceiling of n. + */ + static HNumber ceil( const HNumber& n ); + + /*! + * Returns the greatest common divisor of n1 and n2. + */ + static HNumber gcd( const HNumber& n1, const HNumber& n2 ); + + /*! + * Rounds n to the specified decimal digits. + */ + static HNumber round( const HNumber&n, int prec = 0 ); + + /*! + * Truncates n to the specified decimal digits. + */ + static HNumber trunc( const HNumber&n, int prec = 0 ); + + /*! + * Returns the square root of n. If n is negative, returns NaN. + */ + static HNumber sqrt( const HNumber& n ); + + /*! + * Returns the cube root of n. + */ + static HNumber cbrt( const HNumber& n ); + + /*! + * Raises n1 to an integer n. + */ + static HNumber raise( const HNumber& n1, int n ); + + /*! + * Raises n1 to n2. + */ + static HNumber raise( const HNumber& n1, const HNumber& n2 ); + + /*! + * Returns e raised to x. + */ + static HNumber exp( const HNumber& x ); + + /*! + * Returns the natural logarithm of x. + * If x is non positive, returns NaN. + */ + static HNumber ln( const HNumber& x ); + + /*! + * Returns the base-10 logarithm of x. + * If x is non positive, returns NaN. + */ + static HNumber log( const HNumber& x ); + + /*! + * Returns the base-2 logarithm of x. + * If x is non positive, returns NaN. + */ + static HNumber lg( const HNumber& x ); + + /*! + * Returns the sine of x. Note that x must be in radians. + */ + static HNumber sin( const HNumber& x ); + + /*! + * Returns the cosine of x. Note that x must be in radians. + */ + static HNumber cos( const HNumber& x ); + + /*! + * Returns the tangent of x. Note that x must be in radians. + */ + static HNumber tan( const HNumber& x ); + + /*! + * Returns the cotangent of x. Note that x must be in radians. + */ + static HNumber cot( const HNumber& x ); + + /*! + * Returns the secant of x. Note that x must be in radians. + */ + static HNumber sec( const HNumber& x ); + + /*! + * Returns the cosecant of x. Note that x must be in radians. + */ + static HNumber csc( const HNumber& x ); + + /*! + * Returns the arc sine of x. + */ + static HNumber asin( const HNumber& x ); + + /*! + * Returns the arc cosine of x. + */ + static HNumber acos( const HNumber& x ); + + /*! + * Returns the arc tangent of x. + */ + static HNumber atan( const HNumber& x ); + + /*! + * Returns the hyperbolic sine of x. Note that x must be in radians. + */ + static HNumber sinh( const HNumber& x ); + + /*! + * Returns the hyperbolic cosine of x. Note that x must be in radians. + */ + static HNumber cosh( const HNumber& x ); + + /*! + * Returns the hyperbolic tangent of x. Note that x must be in radians. + */ + static HNumber tanh( const HNumber& x ); + + /*! + * Returns the sign of x. + */ + static HNumber sign( const HNumber& x ); + + /*! + * Returns the combinations of n elements choosen k elements. + */ + static HNumber nCr( const HNumber& n, const HNumber& r ); + + /*! + * Returns the permutations of n elements chosen r elements. + */ + static HNumber nPr( const HNumber& n, const HNumber& r ); + + /*! + * Returns the factorial of x. + * If x is non integer, returns NaN. + */ + static HNumber factorial( const HNumber& x, const HNumber& base = HNumber(1) ); + + /* + PROBABILITY + */ + /** + * Calculates the binomial discrete distribution probability mass function: + * \f[X{\sim}B(n,p)\f] + * \f[\Pr(X=k|n,p)={n\choose k}p^{k}(1-p)^{n-k}\f] + * + * \param[in] k the number of probed exact successes + * \param[in] n the number of trials + * \param[in] p the probability of success in a single trial + * + * \return the probability of exactly \p k successes, otherwise \p NaN if the + * function is not defined for the specified parameters. + */ + static HNumber binomialPmf( const HNumber & k, + const HNumber & n, + const HNumber & p ); + + /** + * Calculates the binomial cumulative distribution function: + * \f[X{\sim}B(n,p)\f] + * \f[\Pr(X \leq k|n,p)=\sum_{i=0}^{k}{n\choose i}p^{i}(1-p)^{n-i}\f] + * + * \param[in] k the maximum number of probed successes + * \param[in] n the number of trials + * \param[in] p the probability of success in a single trial + * + * \return the probability of up to \p k successes, otherwise \p NaN if the + * function is not defined for the specified parameters. + */ + static HNumber binomialCdf( const HNumber & k, + const HNumber & n, + const HNumber & p ); + + /** + * Calculates the expected value of a binomially distributed random variable: + * \f[X{\sim}B(n,p)\f] + * \f[E(X)=np\f] + * + * \param[in] n the number of trials + * \param[in] p the probability of success in a single trial + * + * \return the expected value of the variable, otherwise \p NaN if the + * function is not defined for the specified parameters. + */ + static HNumber binomialMean( const HNumber & n, + const HNumber & p ); + + /** + * Calculates the variance of a binomially distributed random variable: + * \f[X{\sim}B(n,p)\f] + * \f[Var(X)=np(1-p)\f] + * + * \param[in] n the number of trials + * \param[in] p the probability of success in a single trial + * + * \return the variance of the variable, otherwise \p NaN if the + * function is not defined for the specified parameters. + */ + static HNumber binomialVariance( const HNumber & n, + const HNumber & p ); + + /** + * Calculates the hypergeometric discrete distribution probability mass + * function: + * \f[X{\sim}H(N,M,n)\f] + * \f[\Pr(X=k|N,M,n)=\frac{{M\choose k}{N-M\choose n-k}}{{N\choose n}}\f] + * + * \param[in] k the number of probed exact successes + * \param[in] N the number of total elements + * \param[in] M the number of success elements + * \param[in] n the number of selected elements + * + * \return the probability of exactly \p k successes, otherwise \p NaN if the + * function is not defined for the specified parameters. + */ + static HNumber hypergeometricPmf( const HNumber & k, + const HNumber & N, + const HNumber & M, + const HNumber & n ); + + /** + * Calculates the hypergeometric cumulative distribution function: + * \f[X{\sim}H(N,M,n)\f] + * \f[\Pr(X\leq k|N,M,n)= + * \sum_{i=0}^{k}\frac{{M\choose k}{N-M\choose n-k}}{{N\choose n}}\f] + * + * \param[in] k the maximum number of probed successes + * \param[in] N the number of total elements + * \param[in] M the number of success elements + * \param[in] n the number of selected elements + * + * \return the probability of up to \p k successes, otherwise \p NaN if the + * function is not defined for the specified parameters. + */ + static HNumber hypergeometricCdf( const HNumber & k, + const HNumber & N, + const HNumber & M, + const HNumber & n ); + + /** + * Calculates the expected value of a hypergeometrically distributed random + * variable: + * \f[X{\sim}H(N,M,n)\f] + * \f[E(X)=n\frac{M}{N}\f] + * + * \param[in] N the number of total elements + * \param[in] M the number of success elements + * \param[in] n the number of selected elements + * + * \return the expected value of the variable, otherwise \p NaN if the + * function is not defined for the specified parameter. + */ + static HNumber hypergeometricMean( const HNumber & N, + const HNumber & M, + const HNumber & n ); + + /** + * Calculates the variance of a hypergeometrically distributed random variable: + * \f[X{\sim}H(N,M,n)\f] + * \f[Var(X)=\frac{n\frac{M}{N}(1-\frac{M}{N})(N-n)}{N-1}\f] + * + * \param[in] N the number of total elements + * \param[in] M the number of success elements + * \param[in] n the number of selected elements + * + * \return the variance of the variable, otherwise \p NaN if the function is + * not defined for the specified parameter. + */ + static HNumber hypergeometricVariance( const HNumber & N, + const HNumber & M, + const HNumber & n ); + + /** + * Calculates the poissonian discrete distribution probability mass function: + * \f[X{\sim}P(\lambda)\f] + * \f[\Pr(X=k|\lambda)=\frac{e^{-\lambda}\lambda^k}{k!}\f] + * + * \param[in] k the number of event occurrences + * \param[in] l the expected number of occurrences that occur in an interval + * + * \return the probability of exactly \p k event occurrences, otherwise \p NaN + * if the function is not defined for the specified parameters. + */ + static HNumber poissonPmf( const HNumber & k, + const HNumber & l ); + + /** + * Calculates the poissonian cumulative distribution function: + * \f[X{\sim}P(\lambda)\f] + * \f[\Pr(X\leq k|\lambda)=\sum_{i=0}^{k}\frac{e^{-\lambda}\lambda^k}{k!}\f] + * + * \param[in] k the maximum number of event occurrences + * \param[in] l the expected number of occurrences that occur in an interval + * + * \return the probability of up to \p k event occurrences, otherwise \p NaN + * if the function is not defined for the specified parameters. + */ + static HNumber poissonCdf( const HNumber & k, + const HNumber & l ); + + /** + * Calculates the expected value of a Poisson distributed random variable: + * \f[X{\sim}P(\lambda)\f] + * \f[E(X)=\lambda\f] + * + * \param[in] l the expected number of occurrences that occur in an interval + * + * \return the expected value of the variable, otherwise \p NaN if the + * function is not defined for the specified parameter. + */ + static HNumber poissonMean( const HNumber & l ); + + /** + * Calculates the variance of a Poisson distributed random variable: + * \f[X{\sim}P(\lambda)\f] + * \f[Var(X)=\lambda\f] + * + * \param[in] l the expected number of occurrences that occur in an interval + * + * \return the variance of the variable, otherwise \p NaN if the function is + * not defined for the specified parameter. + */ + static HNumber poissonVariance( const HNumber & l ); + + /*! + * Releases all resources. After calling this function, you can not use + * any other functions as well as class HNumber. + */ + static void finalize(); +}; + +std::ostream& operator<<( std::ostream& s, const HNumber& n ); + +#endif // HMATH_H diff --git a/engine/number.c b/engine/number.c new file mode 100644 index 0000000..6b924d7 --- /dev/null +++ b/engine/number.c @@ -0,0 +1,1807 @@ +/* number.c: Implements arbitrary precision numbers. */ +/* + Copyright (C) 1991, 1992, 1993, 1994, 1997, 2000 Free Software Foundation, Inc. + + 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 2 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to: + + The Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02111-1307 USA. + + + You may contact the author by: + e-mail: philnelson@acm.org + us-mail: Philip A. Nelson + Computer Science Department, 9062 + Western Washington University + Bellingham, WA 98226-9062 + +*************************************************************************/ + +#include "number.h" + +#include +#include +#include +#include +#include /* Prototypes needed for external utility routines. */ + +#define bc_rt_warn rt_warn +#define bc_rt_error rt_error +#define bc_out_of_memory out_of_memory + +_PROTOTYPE(void rt_warn, (char *mesg ,...)); +_PROTOTYPE(void rt_error, (char *mesg ,...)); +_PROTOTYPE(void out_of_memory, (void)); + + +void out_of_memory(void){ + return; +} + +void rt_warn(char *mesg ,...){ + return; +} + +void rt_error(char *mesg ,...){ + return; +} + +/* Storage used for special numbers. */ +bc_num _zero_; +bc_num _one_; +bc_num _two_; + +static bc_num _bc_Free_list = NULL; + +/* new_num allocates a number and sets fields to known values. */ + +bc_num +bc_new_num (length, scale) + int length, scale; +{ + bc_num temp; + + if (_bc_Free_list != NULL) { + temp = _bc_Free_list; + _bc_Free_list = temp->n_next; + } else { + temp = (bc_num) malloc (sizeof(bc_struct)); + if (temp == NULL) bc_out_of_memory (); + } + temp->n_sign = PLUS; + temp->n_len = length; + temp->n_scale = scale; + temp->n_refs = 1; + temp->n_ptr = (char *) malloc (length+scale+1); + if (temp->n_ptr == NULL) bc_out_of_memory(); + temp->n_value = temp->n_ptr; + memset (temp->n_ptr, 0, length+scale); + return temp; +} + +/* "Frees" a bc_num NUM. Actually decreases reference count and only + frees the storage if reference count is zero. */ + +void +bc_free_num (num) +bc_num *num; +{ + if (*num == NULL) return; + (*num)->n_refs--; + if ((*num)->n_refs == 0) { + if ((*num)->n_ptr) + free ((*num)->n_ptr); + (*num)->n_next = _bc_Free_list; + _bc_Free_list = *num; + } + *num = NULL; +} + + +/* Intitialize the number package! */ + +void +bc_init_numbers () +{ + _zero_ = bc_new_num (1,0); + _one_ = bc_new_num (1,0); + _one_->n_value[0] = 1; + _two_ = bc_new_num (1,0); + _two_->n_value[0] = 2; +} + + +/* Make a copy of a number! Just increments the reference count! */ + +bc_num +bc_copy_num (num) +bc_num num; +{ + num->n_refs++; + return num; +} + + +/* Initialize a number NUM by making it a copy of zero. */ + +void +bc_init_num (num) +bc_num *num; +{ + *num = bc_copy_num (_zero_); +} + +/* For many things, we may have leading zeros in a number NUM. + _bc_rm_leading_zeros just moves the data "value" pointer to the + correct place and adjusts the length. */ + +static void +_bc_rm_leading_zeros (num) +bc_num num; +{ + /* We can move n_value to point to the first non zero digit! */ + while (*num->n_value == 0 && num->n_len > 1) { + num->n_value++; + num->n_len--; + } +} + + +/* Compare two bc numbers. Return value is 0 if equal, -1 if N1 is less + than N2 and +1 if N1 is greater than N2. If USE_SIGN is false, just + compare the magnitudes. */ + +static int +_bc_do_compare (n1, n2, use_sign, ignore_last) + bc_num n1, n2; +int use_sign; +int ignore_last; +{ + char *n1ptr, *n2ptr; + int count; + + /* First, compare signs. */ + if (use_sign && n1->n_sign != n2->n_sign) + { + if (n1->n_sign == PLUS) + return (1); /* Positive N1 > Negative N2 */ + else + return (-1); /* Negative N1 < Positive N1 */ + } + + /* Now compare the magnitude. */ + if (n1->n_len != n2->n_len) + { + if (n1->n_len > n2->n_len) + { + /* Magnitude of n1 > n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (1); + else + return (-1); + } + else + { + /* Magnitude of n1 < n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (-1); + else + return (1); + } + } + + /* If we get here, they have the same number of integer digits. + check the integer part and the equal length part of the fraction. */ + count = n1->n_len + MIN (n1->n_scale, n2->n_scale); + n1ptr = n1->n_value; + n2ptr = n2->n_value; + + while ((count > 0) && (*n1ptr == *n2ptr)) + { + n1ptr++; + n2ptr++; + count--; + } + if (ignore_last && count == 1 && n1->n_scale == n2->n_scale) + return (0); + if (count != 0) + { + if (*n1ptr > *n2ptr) + { + /* Magnitude of n1 > n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (1); + else + return (-1); + } + else + { + /* Magnitude of n1 < n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (-1); + else + return (1); + } + } + + /* They are equal up to the last part of the equal part of the fraction. */ + if (n1->n_scale != n2->n_scale) + { + if (n1->n_scale > n2->n_scale) + { + for (count = n1->n_scale-n2->n_scale; count>0; count--) + if (*n1ptr++ != 0) + { + /* Magnitude of n1 > n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (1); + else + return (-1); + } + } + else + { + for (count = n2->n_scale-n1->n_scale; count>0; count--) + if (*n2ptr++ != 0) + { + /* Magnitude of n1 < n2. */ + if (!use_sign || n1->n_sign == PLUS) + return (-1); + else + return (1); + } + } + } + + /* They must be equal! */ + return (0); +} + + +/* This is the "user callable" routine to compare numbers N1 and N2. */ + +int +bc_compare (n1, n2) + bc_num n1, n2; +{ + return _bc_do_compare (n1, n2, TRUE, FALSE); +} + +/* In some places we need to check if the number is negative. */ + +char +bc_is_neg (num) +bc_num num; +{ + return num->n_sign == MINUS; +} + +/* In some places we need to check if the number NUM is zero. */ + +char +bc_is_zero (num) +bc_num num; +{ + int count; + char *nptr; + + /* Quick check. */ + if (num == _zero_) return TRUE; + + /* Initialize */ + count = num->n_len + num->n_scale; + nptr = num->n_value; + + /* The check */ + while ((count > 0) && (*nptr++ == 0)) count--; + + if (count != 0) + return FALSE; + else + return TRUE; +} + +/* In some places we need to check if the number NUM is almost zero. + Specifically, all but the last digit is 0 and the last digit is 1. + Last digit is defined by scale. */ + +char +bc_is_near_zero (num, scale) +bc_num num; +int scale; +{ + int count; + char *nptr; + + /* Error checking */ + if (scale > num->n_scale) + scale = num->n_scale; + + /* Initialize */ + count = num->n_len + scale; + nptr = num->n_value; + + /* The check */ + while ((count > 0) && (*nptr++ == 0)) count--; + + if (count != 0 && (count != 1 || *--nptr != 1)) + return FALSE; + else + return TRUE; +} + + +/* Perform addition: N1 is added to N2 and the value is + returned. The signs of N1 and N2 are ignored. + SCALE_MIN is to set the minimum scale of the result. */ + +static bc_num +_bc_do_add (n1, n2, scale_min) + bc_num n1, n2; +int scale_min; +{ + bc_num sum; + int sum_scale, sum_digits; + char *n1ptr, *n2ptr, *sumptr; + int carry, n1bytes, n2bytes; + int count; + + /* Prepare sum. */ + sum_scale = MAX (n1->n_scale, n2->n_scale); + sum_digits = MAX (n1->n_len, n2->n_len) + 1; + sum = bc_new_num (sum_digits, MAX(sum_scale, scale_min)); + + /* Zero extra digits made by scale_min. */ + if (scale_min > sum_scale) + { + sumptr = (char *) (sum->n_value + sum_scale + sum_digits); + for (count = scale_min - sum_scale; count > 0; count--) + *sumptr++ = 0; + } + + /* Start with the fraction part. Initialize the pointers. */ + n1bytes = n1->n_scale; + n2bytes = n2->n_scale; + n1ptr = (char *) (n1->n_value + n1->n_len + n1bytes - 1); + n2ptr = (char *) (n2->n_value + n2->n_len + n2bytes - 1); + sumptr = (char *) (sum->n_value + sum_scale + sum_digits - 1); + + /* Add the fraction part. First copy the longer fraction.*/ + if (n1bytes != n2bytes) + { + if (n1bytes > n2bytes) + while (n1bytes>n2bytes) + { *sumptr-- = *n1ptr--; n1bytes--;} + else + while (n2bytes>n1bytes) + { *sumptr-- = *n2ptr--; n2bytes--;} + } + + /* Now add the remaining fraction part and equal size integer parts. */ + n1bytes += n1->n_len; + n2bytes += n2->n_len; + carry = 0; + while ((n1bytes > 0) && (n2bytes > 0)) + { + *sumptr = *n1ptr-- + *n2ptr-- + carry; + if (*sumptr > (BASE-1)) + { + carry = 1; + *sumptr -= BASE; + } + else + carry = 0; + sumptr--; + n1bytes--; + n2bytes--; + } + + /* Now add carry the longer integer part. */ + if (n1bytes == 0) + { n1bytes = n2bytes; n1ptr = n2ptr; } + while (n1bytes-- > 0) + { + *sumptr = *n1ptr-- + carry; + if (*sumptr > (BASE-1)) + { + carry = 1; + *sumptr -= BASE; + } + else + carry = 0; + sumptr--; + } + + /* Set final carry. */ + if (carry == 1) + *sumptr += 1; + + /* Adjust sum and return. */ + _bc_rm_leading_zeros (sum); + return sum; +} + + +/* Perform subtraction: N2 is subtracted from N1 and the value is + returned. The signs of N1 and N2 are ignored. Also, N1 is + assumed to be larger than N2. SCALE_MIN is the minimum scale + of the result. */ + +static bc_num +_bc_do_sub (n1, n2, scale_min) + bc_num n1, n2; +int scale_min; +{ + bc_num diff; + int diff_scale, diff_len; + int min_scale, min_len; + char *n1ptr, *n2ptr, *diffptr; + int borrow, count, val; + + /* Allocate temporary storage. */ + diff_len = MAX (n1->n_len, n2->n_len); + diff_scale = MAX (n1->n_scale, n2->n_scale); + min_len = MIN (n1->n_len, n2->n_len); + min_scale = MIN (n1->n_scale, n2->n_scale); + diff = bc_new_num (diff_len, MAX(diff_scale, scale_min)); + + /* Zero extra digits made by scale_min. */ + if (scale_min > diff_scale) + { + diffptr = (char *) (diff->n_value + diff_len + diff_scale); + for (count = scale_min - diff_scale; count > 0; count--) + *diffptr++ = 0; + } + + /* Initialize the subtract. */ + n1ptr = (char *) (n1->n_value + n1->n_len + n1->n_scale -1); + n2ptr = (char *) (n2->n_value + n2->n_len + n2->n_scale -1); + diffptr = (char *) (diff->n_value + diff_len + diff_scale -1); + + /* Subtract the numbers. */ + borrow = 0; + + /* Take care of the longer scaled number. */ + if (n1->n_scale != min_scale) + { + /* n1 has the longer scale */ + for (count = n1->n_scale - min_scale; count > 0; count--) + *diffptr-- = *n1ptr--; + } + else + { + /* n2 has the longer scale */ + for (count = n2->n_scale - min_scale; count > 0; count--) + { + val = - *n2ptr-- - borrow; + if (val < 0) + { + val += BASE; + borrow = 1; + } + else + borrow = 0; + *diffptr-- = val; + } + } + + /* Now do the equal length scale and integer parts. */ + + for (count = 0; count < min_len + min_scale; count++) + { + val = *n1ptr-- - *n2ptr-- - borrow; + if (val < 0) + { + val += BASE; + borrow = 1; + } + else + borrow = 0; + *diffptr-- = val; + } + + /* If n1 has more digits then n2, we now do that subtract. */ + if (diff_len != min_len) + { + for (count = diff_len - min_len; count > 0; count--) + { + val = *n1ptr-- - borrow; + if (val < 0) + { + val += BASE; + borrow = 1; + } + else + borrow = 0; + *diffptr-- = val; + } + } + + /* Clean up and return. */ + _bc_rm_leading_zeros (diff); + return diff; +} + + +/* Here is the full subtract routine that takes care of negative numbers. + N2 is subtracted from N1 and the result placed in RESULT. SCALE_MIN + is the minimum scale for the result. */ + +void +bc_sub (n1, n2, result, scale_min) + bc_num n1, n2, *result; +int scale_min; +{ + bc_num diff = NULL; + int cmp_res; + int res_scale; + + if (n1->n_sign != n2->n_sign) + { + diff = _bc_do_add (n1, n2, scale_min); + diff->n_sign = n1->n_sign; + } + else + { + /* subtraction must be done. */ + /* Compare magnitudes. */ + cmp_res = _bc_do_compare (n1, n2, FALSE, FALSE); + switch (cmp_res) + { + case -1: + /* n1 is less than n2, subtract n1 from n2. */ + diff = _bc_do_sub (n2, n1, scale_min); + diff->n_sign = (n2->n_sign == PLUS ? MINUS : PLUS); + break; + case 0: + /* They are equal! return zero! */ + res_scale = MAX (scale_min, MAX(n1->n_scale, n2->n_scale)); + diff = bc_new_num (1, res_scale); + memset (diff->n_value, 0, res_scale+1); + break; + case 1: + /* n2 is less than n1, subtract n2 from n1. */ + diff = _bc_do_sub (n1, n2, scale_min); + diff->n_sign = n1->n_sign; + break; + } + } + + /* Clean up and return. */ + bc_free_num (result); + *result = diff; +} + + +/* Here is the full add routine that takes care of negative numbers. + N1 is added to N2 and the result placed into RESULT. SCALE_MIN + is the minimum scale for the result. */ + +void +bc_add (n1, n2, result, scale_min) + bc_num n1, n2, *result; +int scale_min; +{ + bc_num sum = NULL; + int cmp_res; + int res_scale; + + if (n1->n_sign == n2->n_sign) + { + sum = _bc_do_add (n1, n2, scale_min); + sum->n_sign = n1->n_sign; + } + else + { + /* subtraction must be done. */ + cmp_res = _bc_do_compare (n1, n2, FALSE, FALSE); /* Compare magnitudes. */ + switch (cmp_res) + { + case -1: + /* n1 is less than n2, subtract n1 from n2. */ + sum = _bc_do_sub (n2, n1, scale_min); + sum->n_sign = n2->n_sign; + break; + case 0: + /* They are equal! return zero with the correct scale! */ + res_scale = MAX (scale_min, MAX(n1->n_scale, n2->n_scale)); + sum = bc_new_num (1, res_scale); + memset (sum->n_value, 0, res_scale+1); + break; + case 1: + /* n2 is less than n1, subtract n2 from n1. */ + sum = _bc_do_sub (n1, n2, scale_min); + sum->n_sign = n1->n_sign; + } + } + + /* Clean up and return. */ + bc_free_num (result); + *result = sum; +} + +/* Recursive vs non-recursive multiply crossover ranges. */ +#if defined(MULDIGITS) +#include "muldigits.h" +#else +#define MUL_BASE_DIGITS 80 +#endif + +int mul_base_digits = MUL_BASE_DIGITS; +#define MUL_SMALL_DIGITS mul_base_digits/4 + +/* Multiply utility routines */ + +static bc_num +new_sub_num (length, scale, value) + int length, scale; +char *value; +{ + bc_num temp; + + if (_bc_Free_list != NULL) { + temp = _bc_Free_list; + _bc_Free_list = temp->n_next; + } else { + temp = (bc_num) malloc (sizeof(bc_struct)); + if (temp == NULL) bc_out_of_memory (); + } + temp->n_sign = PLUS; + temp->n_len = length; + temp->n_scale = scale; + temp->n_refs = 1; + temp->n_ptr = NULL; + temp->n_value = value; + return temp; +} + +static void +_bc_simp_mul (bc_num n1, int n1len, bc_num n2, int n2len, bc_num *prod, + int full_scale) +{ + char *n1ptr, *n2ptr, *pvptr; + char *n1end, *n2end; /* To the end of n1 and n2. */ + int indx, sum, prodlen; + + prodlen = n1len+n2len+1; + + *prod = bc_new_num (prodlen, 0); + + n1end = (char *) (n1->n_value + n1len - 1); + n2end = (char *) (n2->n_value + n2len - 1); + pvptr = (char *) ((*prod)->n_value + prodlen - 1); + sum = 0; + + /* Here is the loop... */ + for (indx = 0; indx < prodlen-1; indx++) + { + n1ptr = (char *) (n1end - MAX(0, indx-n2len+1)); + n2ptr = (char *) (n2end - MIN(indx, n2len-1)); + while ((n1ptr >= n1->n_value) && (n2ptr <= n2end)) + sum += *n1ptr-- * *n2ptr++; + *pvptr-- = sum % BASE; + sum = sum / BASE; + } + *pvptr = sum; +} + + +/* A special adder/subtractor for the recursive divide and conquer + multiply algorithm. Note: if sub is called, accum must + be larger that what is being subtracted. Also, accum and val + must have n_scale = 0. (e.g. they must look like integers. *) */ +static void +_bc_shift_addsub (bc_num accum, bc_num val, int shift, int sub) +{ + signed char *accp, *valp; + int count, carry; + + count = val->n_len; + if (val->n_value[0] == 0) + count--; + assert (accum->n_len+accum->n_scale >= shift+count); + + /* Set up pointers and others */ + accp = (signed char *)(accum->n_value + + accum->n_len + accum->n_scale - shift - 1); + valp = (signed char *)(val->n_value + val->n_len - 1); + carry = 0; + + if (sub) { + /* Subtraction, carry is really borrow. */ + while (count--) { + *accp -= *valp-- + carry; + if (*accp < 0) { + carry = 1; + *accp-- += BASE; + } else { + carry = 0; + accp--; + } + } + while (carry) { + *accp -= carry; + if (*accp < 0) + *accp-- += BASE; + else + carry = 0; + } + } else { + /* Addition */ + while (count--) { + *accp += *valp-- + carry; + if (*accp > (BASE-1)) { + carry = 1; + *accp-- -= BASE; + } else { + carry = 0; + accp--; + } + } + while (carry) { + *accp += carry; + if (*accp > (BASE-1)) + *accp-- -= BASE; + else + carry = 0; + } + } +} + +/* Recursive divide and conquer multiply algorithm. + Based on + Let u = u0 + u1*(b^n) + Let v = v0 + v1*(b^n) + Then uv = (B^2n+B^n)*u1*v1 + B^n*(u1-u0)*(v0-v1) + (B^n+1)*u0*v0 + + B is the base of storage, number of digits in u1,u0 close to equal. +*/ +static void +_bc_rec_mul (bc_num u, int ulen, bc_num v, int vlen, bc_num *prod, + int full_scale) +{ + bc_num u0, u1, v0, v1; + int u0len, v0len; + bc_num m1, m2, m3, d1, d2; + int n, prodlen, m1zero; + int d1len, d2len; + + /* Base case? */ + if ((ulen+vlen) < mul_base_digits + || ulen < MUL_SMALL_DIGITS + || vlen < MUL_SMALL_DIGITS ) { + _bc_simp_mul (u, ulen, v, vlen, prod, full_scale); + return; + } + + /* Calculate n -- the u and v split point in digits. */ + n = (MAX(ulen, vlen)+1) / 2; + + /* Split u and v. */ + if (ulen < n) { + u1 = bc_copy_num (_zero_); + u0 = new_sub_num (ulen,0, u->n_value); + } else { + u1 = new_sub_num (ulen-n, 0, u->n_value); + u0 = new_sub_num (n, 0, u->n_value+ulen-n); + } + if (vlen < n) { + v1 = bc_copy_num (_zero_); + v0 = new_sub_num (vlen,0, v->n_value); + } else { + v1 = new_sub_num (vlen-n, 0, v->n_value); + v0 = new_sub_num (n, 0, v->n_value+vlen-n); + } + _bc_rm_leading_zeros (u1); + _bc_rm_leading_zeros (u0); + u0len = u0->n_len; + _bc_rm_leading_zeros (v1); + _bc_rm_leading_zeros (v0); + v0len = v0->n_len; + + m1zero = bc_is_zero(u1) || bc_is_zero(v1); + + /* Calculate sub results ... */ + + bc_init_num(&d1); + bc_init_num(&d2); + bc_sub (u1, u0, &d1, 0); + d1len = d1->n_len; + bc_sub (v0, v1, &d2, 0); + d2len = d2->n_len; + + + /* Do recursive multiplies and shifted adds. */ + if (m1zero) + m1 = bc_copy_num (_zero_); + else + _bc_rec_mul (u1, u1->n_len, v1, v1->n_len, &m1, 0); + + if (bc_is_zero(d1) || bc_is_zero(d2)) + m2 = bc_copy_num (_zero_); + else + _bc_rec_mul (d1, d1len, d2, d2len, &m2, 0); + + if (bc_is_zero(u0) || bc_is_zero(v0)) + m3 = bc_copy_num (_zero_); + else + _bc_rec_mul (u0, u0->n_len, v0, v0->n_len, &m3, 0); + + /* Initialize product */ + prodlen = ulen+vlen+1; + *prod = bc_new_num(prodlen, 0); + + if (!m1zero) { + _bc_shift_addsub (*prod, m1, 2*n, 0); + _bc_shift_addsub (*prod, m1, n, 0); + } + _bc_shift_addsub (*prod, m3, n, 0); + _bc_shift_addsub (*prod, m3, 0, 0); + _bc_shift_addsub (*prod, m2, n, d1->n_sign != d2->n_sign); + + /* Now clean up! */ + bc_free_num (&u1); + bc_free_num (&u0); + bc_free_num (&v1); + bc_free_num (&m1); + bc_free_num (&v0); + bc_free_num (&m2); + bc_free_num (&m3); + bc_free_num (&d1); + bc_free_num (&d2); +} + +/* The multiply routine. N2 times N1 is put int PROD with the scale of + the result being MIN(N2 scale+N1 scale, MAX (SCALE, N2 scale, N1 scale)). +*/ + +void +bc_multiply (n1, n2, prod, scale) + bc_num n1, n2, *prod; +int scale; +{ + bc_num pval; + int len1, len2; + int full_scale, prod_scale; + + /* Initialize things. */ + len1 = n1->n_len + n1->n_scale; + len2 = n2->n_len + n2->n_scale; + full_scale = n1->n_scale + n2->n_scale; + prod_scale = MIN(full_scale,MAX(scale,MAX(n1->n_scale,n2->n_scale))); + + /* Do the multiply */ + _bc_rec_mul (n1, len1, n2, len2, &pval, full_scale); + + /* Assign to prod and clean up the number. */ + pval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS ); + pval->n_value = pval->n_ptr; + pval->n_len = len2 + len1 + 1 - full_scale; + pval->n_scale = prod_scale; + _bc_rm_leading_zeros (pval); + if (bc_is_zero (pval)) + pval->n_sign = PLUS; + bc_free_num (prod); + *prod = pval; +} + +/* Some utility routines for the divide: First a one digit multiply. + NUM (with SIZE digits) is multiplied by DIGIT and the result is + placed into RESULT. It is written so that NUM and RESULT can be + the same pointers. */ + +static void +_one_mult (num, size, digit, result) +unsigned char *num; +int size, digit; +unsigned char *result; +{ + int carry, value; + unsigned char *nptr, *rptr; + + if (digit == 0) + memset (result, 0, size); + else + { + if (digit == 1) + memcpy (result, num, size); + else + { + /* Initialize */ + nptr = (unsigned char *) (num+size-1); + rptr = (unsigned char *) (result+size-1); + carry = 0; + + while (size-- > 0) + { + value = *nptr-- * digit + carry; + *rptr-- = value % BASE; + carry = value / BASE; + } + + if (carry != 0) *rptr = carry; + } + } +} + + +/* The full division routine. This computes N1 / N2. It returns + 0 if the division is ok and the result is in QUOT. The number of + digits after the decimal point is SCALE. It returns -1 if division + by zero is tried. The algorithm is found in Knuth Vol 2. p237. */ + +int +bc_divide (n1, n2, quot, scale) + bc_num n1, n2, *quot; +int scale; +{ + bc_num qval; + unsigned char *num1, *num2; + unsigned char *ptr1, *ptr2, *n2ptr, *qptr; + int scale1, val; + unsigned int len1, len2, scale2, qdigits, extra, count; + unsigned int qdig, qguess, borrow, carry; + unsigned char *mval; + char zero; + unsigned int norm; + + /* Test for divide by zero. */ + if (bc_is_zero (n2)) return -1; + + /* Test for divide by 1. If it is we must truncate. */ + if (n2->n_scale == 0) + { + if (n2->n_len == 1 && *n2->n_value == 1) + { + qval = bc_new_num (n1->n_len, scale); + qval->n_sign = (n1->n_sign == n2->n_sign ? PLUS : MINUS); + memset (&qval->n_value[n1->n_len],0,scale); + memcpy (qval->n_value, n1->n_value, + n1->n_len + MIN(n1->n_scale,scale)); + bc_free_num (quot); + *quot = qval; + } + } + + /* Set up the divide. Move the decimal point on n1 by n2's scale. + Remember, zeros on the end of num2 are wasted effort for dividing. */ + scale2 = n2->n_scale; + n2ptr = (unsigned char *) n2->n_value+n2->n_len+scale2-1; + while ((scale2 > 0) && (*n2ptr-- == 0)) scale2--; + + len1 = n1->n_len + scale2; + scale1 = n1->n_scale - scale2; + if (scale1 < scale) + extra = scale - scale1; + else + extra = 0; + num1 = (unsigned char *) malloc (n1->n_len+n1->n_scale+extra+2); + if (num1 == NULL) bc_out_of_memory(); + memset (num1, 0, n1->n_len+n1->n_scale+extra+2); + memcpy (num1+1, n1->n_value, n1->n_len+n1->n_scale); + + len2 = n2->n_len + scale2; + num2 = (unsigned char *) malloc (len2+1); + if (num2 == NULL) bc_out_of_memory(); + memcpy (num2, n2->n_value, len2); + *(num2+len2) = 0; + n2ptr = num2; + while (*n2ptr == 0) + { + n2ptr++; + len2--; + } + + /* Calculate the number of quotient digits. */ + if (len2 > len1+scale) + { + qdigits = scale+1; + zero = TRUE; + } + else + { + zero = FALSE; + if (len2>len1) + qdigits = scale+1; /* One for the zero integer part. */ + else + qdigits = len1-len2+scale+1; + } + + /* Allocate and zero the storage for the quotient. */ + qval = bc_new_num (qdigits-scale,scale); + memset (qval->n_value, 0, qdigits); + + /* Allocate storage for the temporary storage mval. */ + mval = (unsigned char *) malloc (len2+1); + if (mval == NULL) bc_out_of_memory (); + + /* Now for the full divide algorithm. */ + if (!zero) + { + /* Normalize */ + norm = 10 / ((int)*n2ptr + 1); + if (norm != 1) + { + _one_mult (num1, len1+scale1+extra+1, norm, num1); + _one_mult (n2ptr, len2, norm, n2ptr); + } + + /* Initialize divide loop. */ + qdig = 0; + if (len2 > len1) + qptr = (unsigned char *) qval->n_value+len2-len1; + else + qptr = (unsigned char *) qval->n_value; + + /* Loop */ + while (qdig <= len1+scale-len2) + { + /* Calculate the quotient digit guess. */ + if (*n2ptr == num1[qdig]) + qguess = 9; + else + qguess = (num1[qdig]*10 + num1[qdig+1]) / *n2ptr; + + /* Test qguess. */ + if (n2ptr[1]*qguess > + (num1[qdig]*10 + num1[qdig+1] - *n2ptr*qguess)*10 + + num1[qdig+2]) + { + qguess--; + /* And again. */ + if (n2ptr[1]*qguess > + (num1[qdig]*10 + num1[qdig+1] - *n2ptr*qguess)*10 + + num1[qdig+2]) + qguess--; + } + + /* Multiply and subtract. */ + borrow = 0; + if (qguess != 0) + { + *mval = 0; + _one_mult (n2ptr, len2, qguess, mval+1); + ptr1 = (unsigned char *) num1+qdig+len2; + ptr2 = (unsigned char *) mval+len2; + for (count = 0; count < len2+1; count++) + { + val = (int) *ptr1 - (int) *ptr2-- - borrow; + if (val < 0) + { + val += 10; + borrow = 1; + } + else + borrow = 0; + *ptr1-- = val; + } + } + + /* Test for negative result. */ + if (borrow == 1) + { + qguess--; + ptr1 = (unsigned char *) num1+qdig+len2; + ptr2 = (unsigned char *) n2ptr+len2-1; + carry = 0; + for (count = 0; count < len2; count++) + { + val = (int) *ptr1 + (int) *ptr2-- + carry; + if (val > 9) + { + val -= 10; + carry = 1; + } + else + carry = 0; + *ptr1-- = val; + } + if (carry == 1) *ptr1 = (*ptr1 + 1) % 10; + } + + /* We now know the quotient digit. */ + *qptr++ = qguess; + qdig++; + } + } + + /* Clean up and return the number. */ + qval->n_sign = ( n1->n_sign == n2->n_sign ? PLUS : MINUS ); + if (bc_is_zero (qval)) qval->n_sign = PLUS; + _bc_rm_leading_zeros (qval); + bc_free_num (quot); + *quot = qval; + + /* Clean up temporary storage. */ + free (mval); + free (num1); + free (num2); + + return 0; /* Everything is OK. */ +} + + +/* Division *and* modulo for numbers. This computes both NUM1 / NUM2 and + NUM1 % NUM2 and puts the results in QUOT and REM, except that if QUOT + is NULL then that store will be omitted. +*/ + +int +bc_divmod (num1, num2, quot, rem, scale) + bc_num num1, num2, *quot, *rem; +int scale; +{ + bc_num quotient = NULL; + bc_num temp; + int rscale; + + /* Check for correct numbers. */ + if (bc_is_zero (num2)) return -1; + + /* Calculate final scale. */ + rscale = MAX (num1->n_scale, num2->n_scale+scale); + bc_init_num(&temp); + + /* Calculate it. */ + bc_divide (num1, num2, &temp, scale); + if (quot) + quotient = bc_copy_num (temp); + bc_multiply (temp, num2, &temp, rscale); + bc_sub (num1, temp, rem, rscale); + bc_free_num (&temp); + + if (quot) + { + bc_free_num (quot); + *quot = quotient; + } + + return 0; /* Everything is OK. */ +} + + +/* Modulo for numbers. This computes NUM1 % NUM2 and puts the + result in RESULT. */ + +int +bc_modulo (num1, num2, result, scale) + bc_num num1, num2, *result; +int scale; +{ + return bc_divmod (num1, num2, NULL, result, scale); +} + +/* Raise BASE to the EXPO power, reduced modulo MOD. The result is + placed in RESULT. If a EXPO is not an integer, + only the integer part is used. */ + +int +bc_raisemod (base, expo, mod, result, scale) + bc_num base, expo, mod, *result; +int scale; +{ + bc_num power, exponent, parity, temp; + int rscale; + + /* Check for correct numbers. */ + if (bc_is_zero(mod)) return -1; + if (bc_is_neg(expo)) return -1; + + /* Set initial values. */ + power = bc_copy_num (base); + exponent = bc_copy_num (expo); + temp = bc_copy_num (_one_); + bc_init_num(&parity); + + /* Check the base for scale digits. */ + if (base->n_scale != 0) + bc_rt_warn ("non-zero scale in base"); + + /* Check the exponent for scale digits. */ + if (exponent->n_scale != 0) + { + bc_rt_warn ("non-zero scale in exponent"); + bc_divide (exponent, _one_, &exponent, 0); /*truncate */ + } + + /* Check the modulus for scale digits. */ + if (mod->n_scale != 0) + bc_rt_warn ("non-zero scale in modulus"); + + /* Do the calculation. */ + rscale = MAX(scale, base->n_scale); + while ( !bc_is_zero(exponent) ) + { + (void) bc_divmod (exponent, _two_, &exponent, &parity, 0); + if ( !bc_is_zero(parity) ) + { + bc_multiply (temp, power, &temp, rscale); + (void) bc_modulo (temp, mod, &temp, scale); + } + + bc_multiply (power, power, &power, rscale); + (void) bc_modulo (power, mod, &power, scale); + } + + /* Assign the value. */ + bc_free_num (&power); + bc_free_num (&exponent); + bc_free_num (result); + *result = temp; + return 0; /* Everything is OK. */ +} + +/* Raise NUM1 to the NUM2 power. The result is placed in RESULT. + Maximum exponent is LONG_MAX. If a NUM2 is not an integer, + only the integer part is used. */ + +void +bc_raise (num1, num2, result, scale) + bc_num num1, num2, *result; +int scale; +{ + bc_num temp, power; + long exponent; + int rscale; + int pwrscale; + int calcscale; + char neg; + + /* Check the exponent for scale digits and convert to a long. */ + if (num2->n_scale != 0) + bc_rt_warn ("non-zero scale in exponent"); + exponent = bc_num2long (num2); + if (exponent == 0 && (num2->n_len > 1 || num2->n_value[0] != 0)) + bc_rt_error ("exponent too large in raise"); + + /* Special case if exponent is a zero. */ + if (exponent == 0) + { + bc_free_num (result); + *result = bc_copy_num (_one_); + return; + } + + /* Other initializations. */ + if (exponent < 0) + { + neg = TRUE; + exponent = -exponent; + rscale = scale; + } + else + { + neg = FALSE; + rscale = MIN (num1->n_scale*exponent, MAX(scale, num1->n_scale)); + } + + /* Set initial value of temp. */ + power = bc_copy_num (num1); + pwrscale = num1->n_scale; + while ((exponent & 1) == 0) + { + pwrscale = 2*pwrscale; + bc_multiply (power, power, &power, pwrscale); + exponent = exponent >> 1; + } + temp = bc_copy_num (power); + calcscale = pwrscale; + exponent = exponent >> 1; + + /* Do the calculation. */ + while (exponent > 0) + { + pwrscale = 2*pwrscale; + bc_multiply (power, power, &power, pwrscale); + if ((exponent & 1) == 1) { + calcscale = pwrscale + calcscale; + bc_multiply (temp, power, &temp, calcscale); + } + exponent = exponent >> 1; + } + + /* Assign the value. */ + if (neg) + { + bc_divide (_one_, temp, result, rscale); + bc_free_num (&temp); + } + else + { + bc_free_num (result); + *result = temp; + if ((*result)->n_scale > rscale) + (*result)->n_scale = rscale; + } + bc_free_num (&power); +} + +/* Take the square root NUM and return it in NUM with SCALE digits + after the decimal place. */ + +int +bc_sqrt (num, scale) +bc_num *num; +int scale; +{ + int rscale, cmp_res, done; + int cscale; + bc_num guess, guess1, point5, diff; + + /* Initial checks. */ + cmp_res = bc_compare (*num, _zero_); + if (cmp_res < 0) + return 0; /* error */ + else + { + if (cmp_res == 0) + { + bc_free_num (num); + *num = bc_copy_num (_zero_); + return 1; + } + } + cmp_res = bc_compare (*num, _one_); + if (cmp_res == 0) + { + bc_free_num (num); + *num = bc_copy_num (_one_); + return 1; + } + + /* Initialize the variables. */ + rscale = MAX (scale, (*num)->n_scale); + bc_init_num(&guess); + bc_init_num(&guess1); + bc_init_num(&diff); + point5 = bc_new_num (1,1); + point5->n_value[1] = 5; + + + /* Calculate the initial guess. */ + if (cmp_res < 0) + { + /* The number is between 0 and 1. Guess should start at 1. */ + guess = bc_copy_num (_one_); + cscale = (*num)->n_scale; + } + else + { + /* The number is greater than 1. Guess should start at 10^(exp/2). */ + bc_int2num (&guess,10); + + bc_int2num (&guess1,(*num)->n_len); + bc_multiply (guess1, point5, &guess1, 0); + guess1->n_scale = 0; + bc_raise (guess, guess1, &guess, 0); + bc_free_num (&guess1); + cscale = 3; + } + + /* Find the square root using Newton's algorithm. */ + done = FALSE; + while (!done) + { + bc_free_num (&guess1); + guess1 = bc_copy_num (guess); + bc_divide (*num, guess, &guess, cscale); + bc_add (guess, guess1, &guess, 0); + bc_multiply (guess, point5, &guess, cscale); + bc_sub (guess, guess1, &diff, cscale+1); + if (bc_is_near_zero (diff, cscale)) + { + if (cscale < rscale+1) + cscale = MIN (cscale*3, rscale+1); + else + done = TRUE; + } + } + + /* Assign the number and clean up. */ + bc_free_num (num); + bc_divide (guess,_one_,num,rscale); + bc_free_num (&guess); + bc_free_num (&guess1); + bc_free_num (&point5); + bc_free_num (&diff); + return 1; +} + + +/* The following routines provide output for bcd numbers package + using the rules of POSIX bc for output. */ + +/* This structure is used for saving digits in the conversion process. */ +typedef struct stk_rec { + long digit; + struct stk_rec *next; +} stk_rec; + +/* The reference string for digits. */ +static char ref_str[] = "0123456789ABCDEF"; + + +/* A special output routine for "multi-character digits." Exactly + SIZE characters must be output for the value VAL. If SPACE is + non-zero, we must output one space before the number. OUT_CHAR + is the actual routine for writing the characters. */ + +void +bc_out_long (val, size, space, out_char) +long val; +int size, space; +#ifdef NUMBER__STDC__ +void (*out_char)(int); +#else +void (*out_char)(); +#endif +{ + char digits[40]; + int len, ix; + + if (space) (*out_char) (' '); + sprintf (digits, "%ld", val); + len = strlen (digits); + while (size > len) + { + (*out_char) ('0'); + size--; + } + for (ix=0; ix < len; ix++) + (*out_char) (digits[ix]); +} + +/* Output of a bcd number. NUM is written in base O_BASE using OUT_CHAR + as the routine to do the actual output of the characters. */ + +void +bc_out_num (num, o_base, out_char, leading_zero) +bc_num num; +int o_base; +#ifdef NUMBER__STDC__ +void (*out_char)(int); +#else +void (*out_char)(); +#endif +int leading_zero; +{ + char *nptr; + int index, fdigit, pre_space; + stk_rec *digits, *temp; + bc_num int_part, frac_part, base, cur_dig, t_num, max_o_digit; + + /* The negative sign if needed. */ + if (num->n_sign == MINUS) (*out_char) ('-'); + + /* Output the number. */ + if (bc_is_zero (num)) + (*out_char) ('0'); + else + if (o_base == 10) + { + /* The number is in base 10, do it the fast way. */ + nptr = num->n_value; + if (num->n_len > 1 || *nptr != 0) + for (index=num->n_len; index>0; index--) + (*out_char) (BCD_CHAR(*nptr++)); + else + nptr++; + + if (leading_zero && bc_is_zero (num)) + (*out_char) ('0'); + + /* Now the fraction. */ + if (num->n_scale > 0) + { + (*out_char) ('.'); + for (index=0; indexn_scale; index++) + (*out_char) (BCD_CHAR(*nptr++)); + } + } + else + { + /* special case ... */ + if (leading_zero && bc_is_zero (num)) + (*out_char) ('0'); + + /* The number is some other base. */ + digits = NULL; + bc_init_num (&int_part); + bc_divide (num, _one_, &int_part, 0); + bc_init_num (&frac_part); + bc_init_num (&cur_dig); + bc_init_num (&base); + bc_sub (num, int_part, &frac_part, 0); + /* Make the INT_PART and FRAC_PART positive. */ + int_part->n_sign = PLUS; + frac_part->n_sign = PLUS; + bc_int2num (&base, o_base); + bc_init_num (&max_o_digit); + bc_int2num (&max_o_digit, o_base-1); + + + /* Get the digits of the integer part and push them on a stack. */ + while (!bc_is_zero (int_part)) + { + bc_modulo (int_part, base, &cur_dig, 0); + temp = (stk_rec *) malloc (sizeof(stk_rec)); + if (temp == NULL) bc_out_of_memory(); + temp->digit = bc_num2long (cur_dig); + temp->next = digits; + digits = temp; + bc_divide (int_part, base, &int_part, 0); + } + + /* Print the digits on the stack. */ + if (digits != NULL) + { + /* Output the digits. */ + while (digits != NULL) + { + temp = digits; + digits = digits->next; + if (o_base <= 16) + (*out_char) (ref_str[ (int) temp->digit]); + else + bc_out_long (temp->digit, max_o_digit->n_len, 1, out_char); + free (temp); + } + } + + /* Get and print the digits of the fraction part. */ + if (num->n_scale > 0) + { + (*out_char) ('.'); + pre_space = 0; + t_num = bc_copy_num (_one_); + while (t_num->n_len <= num->n_scale) { + bc_multiply (frac_part, base, &frac_part, num->n_scale); + fdigit = bc_num2long (frac_part); + bc_int2num (&int_part, fdigit); + bc_sub (frac_part, int_part, &frac_part, 0); + if (o_base <= 16) + (*out_char) (ref_str[fdigit]); + else { + bc_out_long (fdigit, max_o_digit->n_len, pre_space, out_char); + pre_space = 1; + } + bc_multiply (t_num, base, &t_num, 0); + } + bc_free_num (&t_num); + } + + /* Clean up. */ + bc_free_num (&int_part); + bc_free_num (&frac_part); + bc_free_num (&base); + bc_free_num (&cur_dig); + bc_free_num (&max_o_digit); + } +} +/* Convert a number NUM to a long. The function returns only the integer + part of the number. For numbers that are too large to represent as + a long, this function returns a zero. This can be detected by checking + the NUM for zero after having a zero returned. */ + +long +bc_num2long (num) +bc_num num; +{ + long val; + char *nptr; + int index; + + /* Extract the int value, ignore the fraction. */ + val = 0; + nptr = num->n_value; + for (index=num->n_len; (index>0) && (val<=(LONG_MAX/BASE)); index--) + val = val*BASE + *nptr++; + + /* Check for overflow. If overflow, return zero. */ + if (index>0) val = 0; + if (val < 0) val = 0; + + /* Return the value. */ + if (num->n_sign == PLUS) + return (val); + else + return (-val); +} + + +/* Convert an integer VAL to a bc number NUM. */ + +void +bc_int2num (num, val) +bc_num *num; +int val; +{ + char buffer[30]; + char *bptr, *vptr; + int ix = 1; + char neg = 0; + + /* Sign. */ + if (val < 0) + { + neg = 1; + val = -val; + } + + /* Get things going. */ + bptr = buffer; + *bptr++ = val % BASE; + val = val / BASE; + + /* Extract remaining digits. */ + while (val != 0) + { + *bptr++ = val % BASE; + val = val / BASE; + ix++; /* Count the digits. */ + } + + /* Make the number. */ + bc_free_num (num); + *num = bc_new_num (ix, 0); + if (neg) (*num)->n_sign = MINUS; + + /* Assign the digits. */ + vptr = (*num)->n_value; + while (ix-- > 0) + *vptr++ = *--bptr; +} + +/* Convert a numbers to a string. Base 10 only.*/ + +char +*bc_num2str (num) +bc_num num; +{ + char *str, *sptr; + char *nptr; + int index, signch; + + /* Allocate the string memory. */ + signch = ( num->n_sign == PLUS ? 0 : 1 ); /* Number of sign chars. */ + if (num->n_scale > 0) + str = (char *) malloc (num->n_len + num->n_scale + 2 + signch); + else + str = (char *) malloc (num->n_len + 1 + signch); + if (str == NULL) bc_out_of_memory(); + + /* The negative sign if needed. */ + sptr = str; + if (signch) *sptr++ = '-'; + + /* Load the whole number. */ + nptr = num->n_value; + for (index=num->n_len; index>0; index--) + *sptr++ = BCD_CHAR(*nptr++); + + /* Now the fraction. */ + if (num->n_scale > 0) + { + *sptr++ = '.'; + for (index=0; indexn_scale; index++) + *sptr++ = BCD_CHAR(*nptr++); + } + + /* Terminate the string and return it! */ + *sptr = '\0'; + return (str); +} +/* Convert strings to bc numbers. Base 10 only.*/ + +void +bc_str2num (num, str, scale) +bc_num *num; +char *str; +int scale; +{ + int digits, strscale; + char *ptr, *nptr; + char zero_int; + + /* Prepare num. */ + bc_free_num (num); + + /* Check for valid number and count digits. */ + ptr = str; + digits = 0; + strscale = 0; + zero_int = FALSE; + if ( (*ptr == '+') || (*ptr == '-')) ptr++; /* Sign */ + while (*ptr == '0') ptr++; /* Skip leading zeros. */ + while (isdigit((int)*ptr)) ptr++, digits++; /* digits */ + if (*ptr == '.') ptr++; /* decimal point */ + while (isdigit((int)*ptr)) ptr++, strscale++; /* digits */ + if ((*ptr != '\0') || (digits+strscale == 0)) + { + *num = bc_copy_num (_zero_); + return; + } + + /* Adjust numbers and allocate storage and initialize fields. */ + strscale = MIN(strscale, scale); + if (digits == 0) + { + zero_int = TRUE; + digits = 1; + } + *num = bc_new_num (digits, strscale); + + /* Build the whole number. */ + ptr = str; + if (*ptr == '-') + { + (*num)->n_sign = MINUS; + ptr++; + } + else + { + (*num)->n_sign = PLUS; + if (*ptr == '+') ptr++; + } + while (*ptr == '0') ptr++; /* Skip leading zeros. */ + nptr = (*num)->n_value; + if (zero_int) + { + *nptr++ = 0; + digits = 0; + } + for (;digits > 0; digits--) + *nptr++ = CH_VAL(*ptr++); + + + /* Build the fractional part. */ + if (strscale > 0) + { + ptr++; /* skip the decimal point! */ + for (;strscale > 0; strscale--) + *nptr++ = CH_VAL(*ptr++); + } +} + +/* pn prints the number NUM in base 10. */ + +static void +out_char (int c) +{ + putchar(c); +} + + +void +pn (num) +bc_num num; +{ + bc_out_num (num, 10, out_char, 0); + out_char ('\n'); +} + + +/* pv prints a character array as if it was a string of bcd digits. */ +void +pv (name, num, len) +char *name; +unsigned char *num; +int len; +{ + int i; + printf ("%s=", name); + for (i=0; i(b)?(a):(b)) +#define MIN(a,b) ((a)>(b)?(b):(a)) +#define ODD(a) ((a)&1) + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 0x7ffffff +#endif + + +/* Global numbers. */ + extern bc_num _zero_; + extern bc_num _one_; + extern bc_num _two_; + + +/* Function Prototypes */ + +/* Define the _PROTOTYPE macro if it is needed. */ + +#ifndef _PROTOTYPE +#ifdef NUMBER__STDC__ +#define _PROTOTYPE(func, args) func args +#else +#define _PROTOTYPE(func, args) func() +#endif +#endif + + _PROTOTYPE(void bc_init_numbers, (void)); + + _PROTOTYPE(bc_num bc_new_num, (int length, int scale)); + + _PROTOTYPE(void bc_free_num, (bc_num *num)); + + _PROTOTYPE(bc_num bc_copy_num, (bc_num num)); + + _PROTOTYPE(void bc_init_num, (bc_num *num)); + + _PROTOTYPE(void bc_str2num, (bc_num *num, char *str, int scale)); + + _PROTOTYPE(char *bc_num2str, (bc_num num)); + + _PROTOTYPE(void bc_int2num, (bc_num *num, int val)); + + _PROTOTYPE(long bc_num2long, (bc_num num)); + + _PROTOTYPE(int bc_compare, (bc_num n1, bc_num n2)); + + _PROTOTYPE(char bc_is_zero, (bc_num num)); + + _PROTOTYPE(char bc_is_near_zero, (bc_num num, int scale)); + + _PROTOTYPE(char bc_is_neg, (bc_num num)); + + _PROTOTYPE(void bc_add, (bc_num n1, bc_num n2, bc_num *result, int scale_min)); + + _PROTOTYPE(void bc_sub, (bc_num n1, bc_num n2, bc_num *result, int scale_min)); + + _PROTOTYPE(void bc_multiply, (bc_num n1, bc_num n2, bc_num *prod, int scale)); + + _PROTOTYPE(int bc_divide, (bc_num n1, bc_num n2, bc_num *quot, int scale)); + + _PROTOTYPE(int bc_modulo, (bc_num num1, bc_num num2, bc_num *result, + int scale)); + + _PROTOTYPE(int bc_divmod, (bc_num num1, bc_num num2, bc_num *quot, + bc_num *rem, int scale)); + + _PROTOTYPE(int bc_raisemod, (bc_num base, bc_num expo, bc_num mod, + bc_num *result, int scale)); + + _PROTOTYPE(void bc_raise, (bc_num num1, bc_num num2, bc_num *result, + int scale)); + + _PROTOTYPE(int bc_sqrt, (bc_num *num, int scale)); + + _PROTOTYPE(void bc_out_num, (bc_num num, int o_base, void (* out_char)(int), + int leading_zero)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/engine/opcode.h b/engine/opcode.h new file mode 100644 index 0000000..d9db4ba --- /dev/null +++ b/engine/opcode.h @@ -0,0 +1,20 @@ +#ifndef CORE_OPCODE_H +#define CORE_OPCODE_H + +#include + +class Opcode +{ +public: + + enum { Nop = 0, Load, Ref, Function, Add, Sub, Neg, Mul, Div, Pow, Fact, Modulo, IntDiv }; + + unsigned type; + unsigned index; + + Opcode(): type(Nop), index(0) {}; + Opcode( unsigned t ): type(t), index(0) {}; + Opcode( unsigned t, unsigned i ): type(t), index(i) {}; +}; + +#endif // CORE_OPCODE_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..024fc6c --- /dev/null +++ b/main.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 CutefishOS. + * + * Author: revenmartin + * + * 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 + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include "calcengine.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + qmlRegisterType("Cutefish.Calculator", 1, 0, "CalcEngine"); + + QQmlApplicationEngine engine; + +#ifdef QT_DEBUG + engine.rootContext()->setContextProperty(QStringLiteral("debug"), true); +#else + engine.rootContext()->setContextProperty(QStringLiteral("debug"), false); +#endif + + // Translations + QLocale locale; + QString qmFilePath = QString("%1/%2.qm").arg("/usr/share/cutefish-calculator/translations/").arg(locale.name()); + if (QFile::exists(qmFilePath)) { + QTranslator *translator = new QTranslator(QGuiApplication::instance()); + if (translator->load(qmFilePath)) { + QGuiApplication::installTranslator(translator); + } else { + translator->deleteLater(); + } + } + + engine.addImportPath(QStringLiteral("qrc:/")); + engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + + return app.exec(); +} diff --git a/qml.qrc b/qml.qrc new file mode 100644 index 0000000..b82fe0e --- /dev/null +++ b/qml.qrc @@ -0,0 +1,8 @@ + + + qml/main.qml + qml/ButtonsView.qml + qml/Zone.qml + qml/CTextField.qml + + diff --git a/qml/ButtonsView.qml b/qml/ButtonsView.qml new file mode 100644 index 0000000..66fd315 --- /dev/null +++ b/qml/ButtonsView.qml @@ -0,0 +1,69 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 +import MeuiKit 1.0 as Meui + +Item { + id: buttonsView + + property var labels + property var targets + property int rowsCount: 5 + + signal buttonClicked(string strToAppend) + signal buttonLongPressed(string strToAppend) + + Grid { + id: grid + anchors.centerIn: parent + anchors.margins: Meui.Units.smallSpacing + columns: getColumnsCount() + rows: buttonsView.rowsCount + + Repeater { + model: buttonsView.labels + + MouseArea { + id: buttonRect + width: buttonsView.width / grid.columns - Meui.Units.smallSpacing / 2 + height: buttonsView.height / grid.rows - Meui.Units.smallSpacing / 2 + onClicked: buttonsView.buttonClicked(targets[index]) + onPressAndHold: buttonsView.buttonLongPressed(targets[index]) + + Rectangle { + anchors.centerIn: parent + radius: Meui.Theme.smallRadius + width: parent.width - radius + height: parent.height - radius + color: buttonRect.pressed ? Meui.Theme.highlightColor : Qt.rgba(Meui.Theme.backgroundColor.r, + Meui.Theme.backgroundColor.g, + Meui.Theme.backgroundColor.b, 0.5) + + border.width: 1 + border.color: Meui.Theme.darkMode ? Qt.lighter(Meui.Theme.backgroundColor, 1.1) : Qt.darker(Meui.Theme.backgroundColor, 1.1) + + Behavior on color { + ColorAnimation { + duration: 50 + } + } + } + + Text { + anchors.fill: parent + text: modelData + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + fontSizeMode: Text.Fit + minimumPointSize: Math.round(buttonRect.height / 5) + font.pointSize: Math.round(buttonRect.height / 5) + color: buttonRect.pressed ? Meui.Theme.highlightedTextColor : Meui.Theme.textColor + } + } + } + } + + function getColumnsCount() { + return Math.ceil(buttonsView.labels.length / buttonsView.rowsCount); + } +} diff --git a/qml/CTextField.qml b/qml/CTextField.qml new file mode 100644 index 0000000..5935eef --- /dev/null +++ b/qml/CTextField.qml @@ -0,0 +1,58 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.5 + +TextField { + id: textField + selectByMouse: true + horizontalAlignment: TextInput.AlignRight + focus: Qt.StrongFocus + font.pixelSize: 24 + + property int selectStart + property int selectEnd + property int curPos + + background: Rectangle { + border.width: 0 + color: "transparent" + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.IBeamCursor + acceptedButtons: Qt.RightButton + + onClicked: { + selectStart = textField.selectionStart; + selectEnd = textField.selectionEnd; + curPos = textField.cursorPosition; + contextMenu.x = mouse.x; + contextMenu.y = mouse.y; + contextMenu.open(); + textField.cursorPosition = curPos; + textField.select(selectStart, selectEnd); + } + } + + Menu { + id: contextMenu + MenuItem { + text: qsTr("Cut") + onTriggered: { + textField.cut() + } + } + MenuItem { + text: qsTr("Copy") + onTriggered: { + textField.copy() + } + } + MenuItem { + text: qsTr("Paste") + onTriggered: { + textField.paste() + } + } + } +} diff --git a/qml/Zone.qml b/qml/Zone.qml new file mode 100644 index 0000000..a34fdd6 --- /dev/null +++ b/qml/Zone.qml @@ -0,0 +1,92 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.5 +import QtQuick.Controls.Styles 1.4 + +import MeuiKit 1.0 as Meui +import Cutefish.Calculator 1.0 + +Item { + id: zone + + ColumnLayout { + id: layout + anchors.fill: parent + anchors.margins: 0 + + ListView { + id: listView + model: ListModel { id: historyModel } + clip: true + + Layout.fillHeight: true + Layout.fillWidth: true + + flickableDirection: Flickable.VerticalFlick + ScrollBar.vertical: ScrollBar {} + + onCountChanged: { + listView.currentIndex = count - 1 + } + + delegate: Item { + height: label.implicitHeight + Meui.Units.largeSpacing * 2 + width: parent ? parent.width : undefined + + Label { + id: label + anchors.fill: parent + horizontalAlignment: Qt.AlignRight + text: historyModel.get(index).text + elide: Text.ElideMiddle + color: Meui.Theme.disabledTextColor + + leftPadding: Meui.Units.largeSpacing + rightPadding: Meui.Units.largeSpacing + + MouseArea { + hoverEnabled: true + } + } + } + } + + CTextField { + id: textField + height: 50 + Layout.fillWidth: true + Keys.onReturnPressed: appendToTextField('=') + Keys.onEnterPressed: appendToTextField('=') + + leftPadding: Meui.Units.largeSpacing + rightPadding: Meui.Units.largeSpacing + } + } + + function appendToTextField(text) { + if (text === '=') { + var res = calculate(textField.text) + if (res !== '') { + var expressionText = textField.text + textField.text = res; + + // If the expression and the result area equal, + // it will not be added to the ListView + if (expressionText !== res) { + expressionText = expressionText + " = " + res + historyModel.append({"text": expressionText}) + } + } + } else if (text === 'AC/C') { + if (textField.text != "") + textField.clear() + else + historyModel.clear() + } else if (text === 'BACK') { + // backspace + textField.remove(textField.cursorPosition, textField.cursorPosition - 1) + } else { + textField.insert(textField.cursorPosition, text) + } + } +} diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..fb06dfc --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,58 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.5 +import QtQuick.Layouts 1.3 +import Cutefish.Calculator 1.0 +import MeuiKit 1.0 as Meui + +Meui.Window { + visible: true + width: 350 + height: 550 + minimumWidth: 350 + minimumHeight: 550 + title: qsTr("Calculator") + id: rootWindow + + backgroundColor: Meui.Theme.secondBackgroundColor + backgroundOpacity: Meui.Theme.darkMode ? 0.9 : 0.7 + + Meui.WindowBlur { + view: rootWindow + enabled: true + geometry: Qt.rect(rootWindow.x, rootWindow.y, rootWindow.width, rootWindow.height) + windowRadius: rootWindow.windowRadius + } + + CalcEngine { + id: calcEngine + + Component.onCompleted: { + console.log("load calc engine finished") + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + Zone { + id: zone + Layout.fillWidth: true + Layout.preferredHeight: parent.height * 0.35 + } + + ButtonsView { + id: buttons + Layout.fillWidth: true + Layout.fillHeight: true + labels: ['AC/C', '%', '←', '÷', '7', '8', '9', '×', '4', '5', '6', '−', '1', '2', '3', '+', '0', '.', '()', '='] + targets: ['AC/C', '%', 'BACK', '/', '7', '8', '9', '*', '4', '5', '6', '-', '1', '2', '3', '+', '0', '.', '()', '='] + onButtonClicked: zone.appendToTextField(strToAppend) + } + } + + function calculate(evalText) { + var res = calcEngine.eval(evalText) + return res + } +} diff --git a/screenshots/screenshot.png b/screenshots/screenshot.png new file mode 100644 index 0000000..3753097 Binary files /dev/null and b/screenshots/screenshot.png differ diff --git a/translations/cs_CZ.ts b/translations/cs_CZ.ts new file mode 100644 index 0000000..560c86e --- /dev/null +++ b/translations/cs_CZ.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Vyjmout + + + + Copy + Kopírovat + + + + Paste + Vložit + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Kalkulačka + + + diff --git a/translations/de_DE.ts b/translations/de_DE.ts new file mode 100644 index 0000000..c7d3081 --- /dev/null +++ b/translations/de_DE.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Ausschneiden + + + + Copy + Kopie + + + + Paste + Einfügen + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Rechner + + + diff --git a/translations/en_US.ts b/translations/en_US.ts new file mode 100644 index 0000000..f560ca1 --- /dev/null +++ b/translations/en_US.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Cut + + + + Copy + Copy + + + + Paste + Paste + + + + QObject + + + Error: %1 + Error: %1 + + + + + + + + + + + + + + + Invalid expression. + Invalid expression. + + + + + + Variable cannot be overwritten. + Variable cannot be overwritten. + + + + Identifier matches an existing function name. + Identifier matches an existing function name. + + + + + + Division by zero. + Division by zero. + + + + + Unknown function or variable. + Unknown function or variable. + + + + main + + + Calculator + Calculator + + + diff --git a/translations/es_ES.ts b/translations/es_ES.ts new file mode 100644 index 0000000..91c7d13 --- /dev/null +++ b/translations/es_ES.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Cortar + + + + Copy + Copiar + + + + Paste + Pegar + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Calculadora + + + diff --git a/translations/es_MX.ts b/translations/es_MX.ts new file mode 100644 index 0000000..bece95b --- /dev/null +++ b/translations/es_MX.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + 剪低 + + + + Copy + 複製 + + + + Paste + 貼上 + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + + + + diff --git a/translations/it_IT.ts b/translations/it_IT.ts new file mode 100644 index 0000000..7a913e0 --- /dev/null +++ b/translations/it_IT.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + + + + + Copy + Copia + + + + Paste + + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + + + + diff --git a/translations/nb_NO.ts b/translations/nb_NO.ts new file mode 100644 index 0000000..cb5f967 --- /dev/null +++ b/translations/nb_NO.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Klipp ut + + + + Copy + Kopier + + + + Paste + Lim inn + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Kalkulator + + + diff --git a/translations/pl_PL.ts b/translations/pl_PL.ts new file mode 100644 index 0000000..62473ad --- /dev/null +++ b/translations/pl_PL.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Wytnij + + + + Copy + Kopiuj + + + + Paste + Wklej + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Kalkulator + + + diff --git a/translations/pt_BR.ts b/translations/pt_BR.ts new file mode 100644 index 0000000..ac566bf --- /dev/null +++ b/translations/pt_BR.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Recortar + + + + Copy + Copiar + + + + Paste + Colar + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Calculadora + + + diff --git a/translations/ru_RU.ts b/translations/ru_RU.ts new file mode 100644 index 0000000..9bf87de --- /dev/null +++ b/translations/ru_RU.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + Вырезать + + + + Copy + Скопировать + + + + Paste + Вставить + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + Калькулятор + + + diff --git a/translations/si_LK.ts b/translations/si_LK.ts new file mode 100644 index 0000000..7b7e511 --- /dev/null +++ b/translations/si_LK.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + + + + + Copy + + + + + Paste + + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + + + + diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts new file mode 100644 index 0000000..0f2bfe7 --- /dev/null +++ b/translations/zh_CN.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + 剪切 + + + + Copy + 复制 + + + + Paste + 粘贴 + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + 计算器 + + + diff --git a/translations/zh_HK.ts b/translations/zh_HK.ts new file mode 100644 index 0000000..5e26489 --- /dev/null +++ b/translations/zh_HK.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + 剪低 + + + + Copy + 複製 + + + + Paste + 貼上 + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + + + + diff --git a/translations/zh_TW.ts b/translations/zh_TW.ts new file mode 100644 index 0000000..7c9d0f2 --- /dev/null +++ b/translations/zh_TW.ts @@ -0,0 +1,79 @@ + + + + + CTextField + + + Cut + 剪下 + + + + Copy + 複製 + + + + Paste + 貼上 + + + + QObject + + + Error: %1 + + + + + + + + + + + + + + + + Invalid expression. + + + + + + + Variable cannot be overwritten. + + + + + Identifier matches an existing function name. + + + + + + + Division by zero. + + + + + + Unknown function or variable. + + + + + main + + + Calculator + + + +