diff options
Diffstat (limited to 'src')
40 files changed, 11005 insertions, 0 deletions
diff --git a/src/SConscript b/src/SConscript new file mode 100644 index 0000000..66e701d --- /dev/null +++ b/src/SConscript @@ -0,0 +1,81 @@ +#! /usr/bin/env python +## This script demonstrates how to build and install +## a simple kde program having KconfigXT settings +## with scons +## +## Thomas Nagy, 2004, 2005 + +## This file can be reused freely for any project (see COPYING) + +############################ +## load the config + +## Use the environment and the tools set in the top-level +## SConstruct file (set with 'Export') - this is very important + +Import( 'env' ) +myenv=env.Copy() + +############################# +## the programs to build + +# The sources for our program - only .ui, .skel and .cpp are accepted +abakus_sources = """ +abakus.cpp +abakuslistview.cpp +dragsupport.cpp +editor.cpp +evaluator.cpp +function.cpp +lexer_lex.cpp +mainwindow.cpp +node.cpp +numerictypes.cpp +parser_yacc.cpp +result.cpp +resultlistview.cpp +resultlistviewtext.cpp +rpnmuncher.cpp +valuemanager.cpp +dcopIface.skel +""" + +if myenv.get('mpfr', 'no') == 'yes': + myenv.Append(LIBS = ['mpfr', 'gmp']) +else: + abakus_sources = abakus_sources + " hmath.cpp number.c" + +myenv.KDEprogram( "abakus", abakus_sources ) +myenv.KDEicon( 'abakus' ) + +# Mark these as being created by flex and bison if it's installed. +if myenv.Dictionary().has_key('PARSER_INCLUDED'): + myenv.CXXFile( "lexer_lex.cpp", "lexer.ll" ) + myenv.CXXFile( "parser_yacc.cpp", "parser.yy", YACCFLAGS="-d" ) + +if myenv['HAVE_ASNEEDED']: + myenv.Append(LINKFLAGS = '-Wl,--as-needed') + +myenv.Append(CXXFLAGS = '-Wno-non-virtual-dtor') + +############################ +## Customization + +## Additional include paths for compiling the source files +## Always add '../' (top-level directory) because moc makes code that needs it +myenv.KDEaddpaths_includes('#/src/ #/') + +## Necessary libraries to link against +myenv.KDEaddlibs( 'qt-mt kio kdecore kdeprint kdeui' ) + +############################# +## Data to install + +## The ui.rc file and the tips go into datadir/appname/ +myenv.KDEinstall( 'KDEDATA', 'abakus', 'abakusui.rc' ) + +## Warning : there is a difference between the normal destop file used for the menu +## and the servicetype desktop file, so they go in different directories +## you will find more information in 'test3' +myenv.KDEinstall( 'KDEMENU', 'Utilities', 'abakus.desktop') + diff --git a/src/abakus.cpp b/src/abakus.cpp new file mode 100644 index 0000000..6200641 --- /dev/null +++ b/src/abakus.cpp @@ -0,0 +1,74 @@ +/* + * abakus.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kdebug.h> + +#include <config.h> + +#if HAVE_MPFR +#include <mpfr.h> +#endif + +#include "mainwindow.h" + +const char *const version = "0.91"; + +int main(int argc, char **argv) +{ + KAboutData *about = new KAboutData("abakus", I18N_NOOP("abakus"), version, + I18N_NOOP("A simple keyboard-driven calculator"), KAboutData::License_GPL, + "(c) 2004, 2005 Michael Pyne", 0 /* text */, "http://grammarian.homelinux.net/abakus/", + "michael.pyne@kdemail.net"); + + about->addAuthor("Michael Pyne", + I18N_NOOP("Developer"), + "michael.pyne@kdemail.net", + "http://grammarian.homelinux.net/"); + about->addCredit("Ariya Hidayat", + I18N_NOOP("High precision math routines, and inspiration for the new design came from his C++ implementation (SpeedCrunch)"), + "ariya@kde.org", + "http://speedcrunch.berlios.de/"); + about->addCredit("Roberto Alsina", + I18N_NOOP("Came up with the initial idea, along with a Python implementation."), + "ralsina@netline.com.ar", + "http://dot.kde.org/1096309744/"); + about->addCredit("Zack Rusin", + I18N_NOOP("Inspiration/code for the initial design came from his Ruby implementation."), + "zack@kde.org"); + +#if HAVE_MPFR + mpfr_set_default_prec(6 * 78); // 78 digits, figure about 6 bits needed. + kdDebug() << "Using the MPFR high-precision library.\n"; +#else + kdDebug() << "Using the internal high-precision library.\n"; +#endif + + KCmdLineArgs::init(argc, argv, about); + KApplication app; + MainWindow *win = new MainWindow; + + app.setMainWidget(win); + app.connect(&app, SIGNAL(lastWindowClosed()), SLOT(quit())); + win->show(); + win->resize(500, 300); + + return app.exec(); +} diff --git a/src/abakus.desktop b/src/abakus.desktop new file mode 100644 index 0000000..282bc4e --- /dev/null +++ b/src/abakus.desktop @@ -0,0 +1,38 @@ +# KDE Config File +[Desktop Entry] +Encoding=UTF-8 +Type=Application +X-KDE-StartupNotify=true +Exec=abakus -caption "%c" %i %m +Icon=abakus +Comment= +Comment[xx]=xxxx +Terminal=0 +Name=Abakus +Name[tr]=Abaküs +Name[xx]=xxAbakusxx +GenericName=Calculator +GenericName[bg]=Калкулатор +GenericName[br]=Jederez +GenericName[cs]=Kalkulátor +GenericName[cy]=Cyfrifiannell +GenericName[da]=Lommeregner +GenericName[el]=Υπολογιστής Τσέπης +GenericName[es]=Calculadora +GenericName[et]=Kalkulaator +GenericName[fr]=Calculateur +GenericName[ga]=Áireamhán +GenericName[gl]=Calculadora +GenericName[it]=Calcolatrice +GenericName[ka]=კალკულატორი +GenericName[lt]=Skaičiuotuvas +GenericName[nl]=Rekenmachine +GenericName[pt]=Calculadora +GenericName[rw]=Mubazi +GenericName[sk]=Kalkulačka +GenericName[sr]=Рачунаљка +GenericName[sr@Latn]=Računaljka +GenericName[sv]=Miniräknare +GenericName[tr]=Hesap Makinesi +GenericName[xx]=xxCalculatorxx +Categories=Qt;KDE;Utility diff --git a/src/abakuscommon.h b/src/abakuscommon.h new file mode 100644 index 0000000..83a71f2 --- /dev/null +++ b/src/abakuscommon.h @@ -0,0 +1,22 @@ +// header file for pch support. +#if defined __cplusplus + +#include <kapplication.h> +#include <kdebug.h> +#include <kpushbutton.h> +#include <kconfig.h> +#include <kglobal.h> +#include <klocale.h> +#include <kcombobox.h> +#include <kpopupmenu.h> +#include <kaction.h> + +#include <qlabel.h> +#include <qregexp.h> +#include <qtimer.h> + +#include "function.h" +#include "node.h" +#include "valuemanager.h" + +#endif diff --git a/src/abakuslistview.cpp b/src/abakuslistview.cpp new file mode 100644 index 0000000..964c8a7 --- /dev/null +++ b/src/abakuslistview.cpp @@ -0,0 +1,232 @@ +/* + * abakuslistview.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <klocale.h> +#include <kpopupmenu.h> +#include <kdebug.h> + +#include <qdragobject.h> +#include <qcursor.h> +#include <qheader.h> + +#include "dragsupport.h" +#include "abakuslistview.h" +#include "valuemanager.h" +#include "function.h" + +ListView::ListView(QWidget *parent, const char *name) : + KListView(parent, name), m_menu(0), m_usePopup(false), m_removeSingleId(0), + m_removeAllId(0) +{ + setResizeMode(LastColumn); + setDragEnabled(true); + + connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), + SLOT(rightClicked(QListViewItem *, const QPoint &))); +} + +QDragObject *ListView::dragObject() +{ + QPoint viewportPos = viewport()->mapFromGlobal(QCursor::pos()); + QListViewItem *item = itemAt(viewportPos); + + if(!item) + return 0; + + int column = header()->sectionAt(viewportPos.x()); + QString dragText = item->text(column); + + QDragObject *drag = new QTextDrag(dragText, this, "list item drag"); + drag->setPixmap(DragSupport::makePixmap(dragText, font())); + + return drag; +} + +void ListView::enablePopupHandler(bool enable) +{ + if(enable == m_usePopup) + return; + + m_usePopup = enable; + + if(m_usePopup) { + if(m_menu) + kdError() << "ListView menu shouldn't exist here!\n"; + + m_menu = new KPopupMenu(this); + + m_removeSingleId = m_menu->insertItem(removeItemString(), this, SLOT(removeSelected())); + m_removeAllId = m_menu->insertItem("Placeholder", this, SLOT(removeAllItems())); + } + else { + delete m_menu; + m_menu = 0; + } +} + +QString ListView::removeItemString() const +{ + return QString(); +} + +QString ListView::removeAllItemsString(unsigned count) const +{ + Q_UNUSED(count); + + return QString(); +} + +void ListView::removeSelectedItem(QListViewItem *item) +{ + Q_UNUSED(item); +} + +void ListView::removeAllItems() +{ +} + +bool ListView::isItemRemovable(QListViewItem *item) const +{ + Q_UNUSED(item); + + return false; +} + +void ListView::rightClicked(QListViewItem *item, const QPoint &pt) +{ + if(!m_usePopup) + return; + + m_menu->setItemEnabled(m_removeSingleId, item && isItemRemovable(item)); + m_menu->changeItem(m_removeAllId, removeAllItemsString(childCount())); + m_menu->popup(pt); +} + +void ListView::removeSelected() +{ + removeSelectedItem(selectedItem()); +} + +ValueListViewItem::ValueListViewItem(QListView *listView, const QString &name, + const Abakus::number_t &value) : + KListViewItem(listView, name), m_value(value) +{ + valueChanged(); +} + +void ValueListViewItem::valueChanged() +{ + setText(1, m_value.toString()); + repaint(); +} + +void ValueListViewItem::valueChanged(const Abakus::number_t &newValue) +{ + m_value = newValue; + + valueChanged(); +} + +Abakus::number_t ValueListViewItem::itemValue() const +{ + return m_value; +} + +VariableListView::VariableListView(QWidget *parent, const char *name) : + ListView(parent, name) +{ + enablePopupHandler(true); +} + +QString VariableListView::removeItemString() const +{ + return i18n("Remove selected variable"); +} + +QString VariableListView::removeAllItemsString(unsigned count) const +{ + // count is unreliable because not all of the elements in the list view + // can be removed. + count = 0; + QStringList values = ValueManager::instance()->valueNames(); + + for(QStringList::ConstIterator value = values.constBegin(); value != values.constEnd(); ++value) + if(!ValueManager::instance()->isValueReadOnly(*value)) + ++count; + + return i18n("Remove all variables (1 variable)", + "Remove all variables (%n variables)", + count); +} + +bool VariableListView::isItemRemovable(QListViewItem *item) const +{ + return !ValueManager::instance()->isValueReadOnly(item->text(0)); +} + +void VariableListView::removeSelectedItem(QListViewItem *item) +{ + ValueManager::instance()->removeValue(item->text(0)); +} + +void VariableListView::removeAllItems() +{ + ValueManager::instance()->slotRemoveUserVariables(); +} + +FunctionListView::FunctionListView(QWidget *parent, const char *name) : + ListView(parent, name) +{ + enablePopupHandler(true); +} + +QString FunctionListView::removeItemString() const +{ + return i18n("Remove selected function"); +} + +QString FunctionListView::removeAllItemsString(unsigned count) const +{ + return i18n("Remove all functions (1 function)", + "Remove all functions (%n functions)", + count); +} + +bool FunctionListView::isItemRemovable(QListViewItem *item) const +{ + return true; +} + +void FunctionListView::removeSelectedItem(QListViewItem *item) +{ + // Use section to get the beginning of the string up to (and not + // including) the first ( + QString name = item->text(0).section('(', 0, 0); + + FunctionManager::instance()->removeFunction(name); +} + +void FunctionListView::removeAllItems() +{ + QStringList fns = FunctionManager::instance()->functionList(FunctionManager::UserDefined); + + for(QStringList::ConstIterator fn = fns.constBegin(); fn != fns.constEnd(); ++fn) + FunctionManager::instance()->removeFunction(*fn); +} + +#include "abakuslistview.moc" diff --git a/src/abakuslistview.h b/src/abakuslistview.h new file mode 100644 index 0000000..1712b6e --- /dev/null +++ b/src/abakuslistview.h @@ -0,0 +1,147 @@ +#ifndef ABAKUS_LISTVIEW_H +#define ABAKUS_LISTVIEW_H +/* + * abakuslistview.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <klistview.h> + +#include "numerictypes.h" + +class KPopupMenu; + +class ListView : public KListView +{ + Q_OBJECT + + public: + ListView(QWidget *parent, const char *name = 0); + + protected: + virtual QDragObject *dragObject(); + + /** + * Used to enable fancy popup handling support in subclasses. Subclasses + * also need to reimplement a few functions if they want to use this. + * + * This should be called in the subclass's constructor. + */ + void enablePopupHandler(bool enable); + + /** + * If using the popup menu handling, the subclass needs to return a + * translated string of the form "Remove selected <itemtype>". + */ + virtual QString removeItemString() const; + + /** + * If using the popup menu handling, the subclass needs to return a + * translated string of the form "Remove all <itemtype>s." I recommend + * also appending a " (%n <itemtype>s), which you can use the @p count + * parameter for. + */ + virtual QString removeAllItemsString(unsigned count) const; + + protected slots: + /** + * If using the popup menu handing, the subclass needs to reimplement this + * function to remove the selected item, which is passed in as a + * parameter. + */ + virtual void removeSelectedItem(QListViewItem *item); + + /** + * If using the popup menu handling, the subclass needs to reimplement this + * function to remove all items. + */ + virtual void removeAllItems(); + + /** + * If using the popup menu handling, this function may be called to + * determine whether the selected item given by @p item is removable. + */ + virtual bool isItemRemovable(QListViewItem *item) const; + + private slots: + void rightClicked(QListViewItem *item, const QPoint &pt); + void removeSelected(); + + private: + KPopupMenu *m_menu; + bool m_usePopup; + + int m_removeSingleId; + int m_removeAllId; +}; + +class ValueListViewItem : public KListViewItem +{ + public: + ValueListViewItem (QListView *listView, const QString &name, const Abakus::number_t &value); + + // Will cause the list item to rethink the text. + void valueChanged(); + void valueChanged(const Abakus::number_t &newValue); + + Abakus::number_t itemValue() const; + + private: + Abakus::number_t m_value; +}; + +/** + * Subclass used for the list of variables. + */ +class VariableListView : public ListView +{ + Q_OBJECT + public: + + VariableListView(QWidget *parent, const char *name = 0); + + protected: + virtual QString removeItemString() const; + virtual QString removeAllItemsString(unsigned count) const; + virtual bool isItemRemovable(QListViewItem *item) const; + + protected slots: + virtual void removeSelectedItem(QListViewItem *item); + virtual void removeAllItems(); +}; + +/** + * Subclass used for the list of functions. + */ +class FunctionListView : public ListView +{ + Q_OBJECT + public: + + FunctionListView(QWidget *parent, const char *name = 0); + + protected: + virtual QString removeItemString() const; + virtual QString removeAllItemsString(unsigned count) const; + virtual bool isItemRemovable(QListViewItem *item) const; + + protected slots: + virtual void removeSelectedItem(QListViewItem *item); + virtual void removeAllItems(); +}; + +#endif diff --git a/src/abakusui.rc b/src/abakusui.rc new file mode 100644 index 0000000..395323e --- /dev/null +++ b/src/abakusui.rc @@ -0,0 +1,35 @@ +<!DOCTYPE kpartgui> +<kpartgui name="abakus" version="2"> +<MenuBar> + <Menu name="view"><text>&View</text> + <Action name="toggleHistoryList"/> + <Action name="toggleVariableList"/> + <Action name="toggleFunctionList"/> + + <Separator/> + + <Action name="precisionAuto"/> + <Action name="precision3"/> + <Action name="precision8"/> + <Action name="precision15"/> + <Action name="precision50"/> + <Action name="precisionCustom"/> + + <Separator/> + + <Action name="toggleCompactMode"/> + + <Separator/> + + <Action name="clearHistory"/> + </Menu> + <Menu name="mode"><text>&Mode</text> + <Action name="setDegreesMode"/> + <Action name="setRadiansMode"/> + + <Separator/> + + <Action name="toggleExpressionMode"/> + </Menu> +</MenuBar> +</kpartgui> diff --git a/src/dcopIface.h b/src/dcopIface.h new file mode 100644 index 0000000..505f411 --- /dev/null +++ b/src/dcopIface.h @@ -0,0 +1,47 @@ +#ifndef ABAKUS_DCOP_IFACE +#define ABAKUS_DCOP_IFACE +/* + * dcopIface.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <kdebug.h> +#include <dcopobject.h> + +#include <qstring.h> + +#include "mainwindow.h" +#include "numerictypes.h" +#include "function.h" + +class AbakusIface : virtual public DCOPObject +{ + K_DCOP + public: + AbakusIface() : DCOPObject("Calculator") + { + } + + k_dcop: + virtual double evaluate(const QString &expr) + { + Abakus::number_t result = parseString(expr.latin1()); + return result.asDouble(); + } +}; + +#endif diff --git a/src/dragsupport.cpp b/src/dragsupport.cpp new file mode 100644 index 0000000..0a8b875 --- /dev/null +++ b/src/dragsupport.cpp @@ -0,0 +1,87 @@ +/* + * dragsupport.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <qstring.h> +#include <qpixmap.h> +#include <qimage.h> +#include <qpainter.h> +#include <qcolor.h> +#include <qfont.h> +#include <qbrush.h> +#include <qfontmetrics.h> + +#include "dragsupport.h" + +namespace DragSupport +{ + +QPixmap makePixmap(const QString &text, const QFont &font) +{ + QColor background(234, 178, 230); + QFontMetrics fm(font); + + int height = 2 * fm.height(); + QSize bonusSize (height, 0); + QSize size(fm.width(text), height); + QImage image(size + bonusSize, 32); + + image.setAlphaBuffer(false); + image.fill(0); // All transparent pixels + image.setAlphaBuffer(true); + + QPixmap pix(size + bonusSize); + pix.fill(Qt::magenta); // Watch for incoming hacks + + QPainter painter(&pix); + painter.setFont(font); + + // Outline black, background white + painter.setPen(Qt::black); + painter.setBrush(background); + + // roundRect is annoying in that the four "pies" in each corner aren't + // circular, they're elliptical. Try to make the radii force it circular + // again. + painter.drawRoundRect(pix.rect(), 75 * pix.height() / pix.width(), 75); + + // Alias better names for some constants. + int textLeft = height / 2; + + // Draw text + painter.setPen(Qt::black); + painter.drawText(textLeft, height / 4, size.width(), size.height(), 0, text); + + QImage overlay(pix.convertToImage()); + + // The images should have the same size, copy pixels from overlay to the + // bottom unless the pixel is called magenta. The pixels we don't copy + // are transparent in the QImage, and will remain transparent when + // converted to a QPixmap. + + for(int i = 0; i < image.width(); ++i) + for(int j = 0; j < image.height(); ++j) { + if(QColor(overlay.pixel(i, j)) != Qt::magenta) + image.setPixel(i, j, overlay.pixel(i, j)); + } + + pix.convertFromImage(image); + return pix; +} + +} // DragSupport diff --git a/src/dragsupport.h b/src/dragsupport.h new file mode 100644 index 0000000..76ebe81 --- /dev/null +++ b/src/dragsupport.h @@ -0,0 +1,33 @@ +#ifndef ABAKUS_DRAGSUPPORT_H +#define ABAKUS_DRAGSUPPORT_H +/* + * dragsupport.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +class QPixmap; +class QString; +class QFont; + +namespace DragSupport { + + QPixmap makePixmap(const QString &text, const QFont &font); + +} +#endif + +// vim: set et ts=8 sw=4: diff --git a/src/editor.cpp b/src/editor.cpp new file mode 100644 index 0000000..1ae425f --- /dev/null +++ b/src/editor.cpp @@ -0,0 +1,892 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004,2005 Ariya Hidayat <ariya@kde.org> + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne <michael.pyne@kdemail.net> + + 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; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + +#include "function.h" +#include "valuemanager.h" +#include "editor.h" +#include "evaluator.h" +#include "result.h" + +#include <qapplication.h> +#include <qlabel.h> +#include <qlineedit.h> +#include <qlistbox.h> +#include <qpainter.h> +#include <qregexp.h> +#include <qstringlist.h> +#include <qstyle.h> +#include <qsyntaxhighlighter.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <qmessagebox.h> +#include <qvbox.h> + +#include <netwm.h> +#include <fixx11h.h> // netwm.h includes X11 headers which conflict with qevent +#include <qevent.h> + +#include <kdebug.h> + +#include <algorithm> + +// XXX: QT 4: Replace this with qBinaryFind(). +using std::binary_search; + +class CalcResultLabel : public QLabel +{ +public: + CalcResultLabel(QWidget *parent, const char *name, int WFlags) : + QLabel(parent, name, WFlags) + { + } + +protected: + virtual void mousePressEvent(QMouseEvent *) + { + hide(); + } +}; + +class EditorHighlighter : public QSyntaxHighlighter +{ +public: + EditorHighlighter( Editor* ); + int highlightParagraph ( const QString & text, int ); + +private: + Editor* editor; +}; + +class Editor::Private +{ +public: + Evaluator* eval; + QStringList history; + int index; + bool autoCompleteEnabled; + EditorCompletion* completion; + QTimer* completionTimer; + bool autoCalcEnabled; + char format; + int decimalDigits; + QTimer* autoCalcTimer; + QLabel* autoCalcLabel; + bool syntaxHighlightEnabled; + EditorHighlighter* highlighter; + QMap<ColorType,QColor> highlightColors; + QTimer* matchingTimer; +}; + +class EditorCompletion::Private +{ +public: + Editor* editor; + QVBox *completionPopup; + QListBox *completionListBox; +}; + +class ChoiceItem: public QListBoxText +{ + public: + ChoiceItem( QListBox*, const QString& ); + void setMinNameWidth (int w) { minNameWidth = w; } + int nameWidth() const; + + protected: + void paint( QPainter* p ); + + private: + QString item; + QString desc; + int minNameWidth; +}; + +ChoiceItem::ChoiceItem( QListBox* listBox, const QString& text ): + QListBoxText( listBox, text ), minNameWidth(0) +{ + QStringList list = QStringList::split( ':', text ); + if( list.count() ) item = list[0]; + if( list.count()>1 ) desc = list[1]; +} + +// Returns width of this particular list item's name. +int ChoiceItem::nameWidth() const +{ + if(item.isEmpty()) + return 0; + + QFontMetrics fm = listBox()->fontMetrics(); + return fm.width( item ); +} + +void ChoiceItem::paint( QPainter* painter ) +{ + int itemHeight = height( listBox() ); + QFontMetrics fm = painter->fontMetrics(); + int yPos = ( ( itemHeight - fm.height() ) / 2 ) + fm.ascent(); + painter->drawText( 3, yPos, item ); + + //int xPos = fm.width( item ); + int xPos = QMAX(fm.width(item), minNameWidth); + if( !isSelected() ) + painter->setPen( listBox()->palette().disabled().text().dark() ); + painter->drawText( 10 + xPos, yPos, desc ); +} + +EditorHighlighter::EditorHighlighter( Editor* e ): + QSyntaxHighlighter( e ) +{ + editor = e; +} + +int EditorHighlighter::highlightParagraph ( const QString & text, int ) +{ + if( !editor->isSyntaxHighlightEnabled() ) + { + setFormat( 0, text.length(), editor->colorGroup().text() ); + return 0; + } + + QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); + fnames.sort(); // Sort list so we can bin search it. + + Tokens tokens = Evaluator::scan( text ); + for( unsigned i = 0; i < tokens.count(); i++ ) + { + Token& token = tokens[i]; + QString text = token.text().lower(); + QFont font = editor->font(); + QColor color = Qt::black; + switch( token.type() ) + { + case Token::Number: + color = editor->highlightColor( Editor::Number ); + break; + + case Token::Identifier: + { + color = editor->highlightColor( Editor::Variable ); + if( binary_search( fnames.constBegin(), fnames.constEnd(), text) ) { + color = editor->highlightColor( Editor::FunctionName ); + } + } + break; + + case Token::Operator: + break; + + default: break; + }; + if( token.pos() >= 0 ) { + setFormat( token.pos(), token.text().length(), font, color ); + } + } + return 0; +} + + + +Editor::Editor( QWidget* parent, const char* name ): + QTextEdit( parent, name ) +{ + d = new Private; + d->eval = 0; + d->index = 0; + d->autoCompleteEnabled = true; + d->completion = new EditorCompletion( this ); + d->completionTimer = new QTimer( this ); + d->autoCalcEnabled = true; + d->syntaxHighlightEnabled = true; + d->highlighter = new EditorHighlighter( this ); + d->autoCalcTimer = new QTimer( this ); + d->matchingTimer = new QTimer( this ); + + setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); + setWordWrap( NoWrap ); + setHScrollBarMode( AlwaysOff ); + setVScrollBarMode( AlwaysOff ); + setTextFormat( PlainText ); + setAutoFormatting( AutoNone ); + setTabChangesFocus( true ); + setLinkUnderline( false ); + + connect( d->completion, SIGNAL( selectedCompletion( const QString& ) ), + SLOT( autoComplete( const QString& ) ) ); + connect( this, SIGNAL( textChanged() ), SLOT( checkAutoComplete() ) ); + connect( d->completionTimer, SIGNAL( timeout() ), SLOT( triggerAutoComplete() ) ); + + connect( this, SIGNAL( textChanged() ), SLOT( checkMatching() ) ); + connect( d->matchingTimer, SIGNAL( timeout() ), SLOT( doMatchingLeft() ) ); + connect( d->matchingTimer, SIGNAL( timeout() ), SLOT( doMatchingRight() ) ); + connect( this, SIGNAL( textChanged() ), SLOT( checkAutoCalc() ) ); + connect( d->autoCalcTimer, SIGNAL( timeout() ), SLOT( autoCalc() ) ); + d->autoCalcLabel = new CalcResultLabel( 0, "autocalc", WStyle_StaysOnTop | + WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WX11BypassWM ); + d->autoCalcLabel->setFrameStyle( QFrame::Plain | QFrame::Box ); + d->autoCalcLabel->setPalette( QToolTip::palette() ); + d->autoCalcLabel->hide(); + + setHighlightColor( Number, QColor(0,0,127) ); + setHighlightColor( FunctionName, QColor(85,0,0) ); + setHighlightColor( Variable, QColor(0,85,0) ); + setHighlightColor( MatchedPar, QColor(255,255,183) ); +} + +Editor::~Editor() +{ + d->autoCalcLabel->hide(); + delete d; +} + +QSize Editor::sizeHint() const +{ + constPolish(); + QFontMetrics fm = fontMetrics(); + int h = QMAX(fm.lineSpacing(), 14); + int w = fm.width( 'x' ) * 20; + int m = frameWidth() * 2; + return( style().sizeFromContents(QStyle::CT_LineEdit, this, + QSize( w + m, h + m + 4 ). + expandedTo(QApplication::globalStrut()))); +} + +QStringList Editor::history() const +{ + return d->history; +} + +void Editor::setHistory( const QStringList& h ) +{ + d->history = h; + d->index = d->history.count(); +} + +bool Editor::autoCompleteEnabled() const +{ + return d->autoCompleteEnabled; +} + +void Editor::setAutoCompleteEnabled( bool enable ) +{ + d->autoCompleteEnabled = enable; +} + +bool Editor::autoCalcEnabled() const +{ + return d->autoCalcEnabled; +} + +void Editor::setAutoCalcEnabled( bool enable ) +{ + d->autoCalcEnabled = enable; +} + +void Editor::setFormat( char format ) +{ + d->format = format; +} + +void Editor::setDecimalDigits( int digits ) +{ + d->decimalDigits = digits; +} + +void Editor::appendHistory( const QString& text ) +{ + if( text.isEmpty() ) return; + + QString lastText; + if( d->history.count() ) + lastText = d->history[ d->history.count()-1 ]; + if( text == lastText ) return; + + d->history.append( text ); + d->index = d->history.count()-1; +} + +void Editor::clearHistory() +{ + d->history.clear(); + d->index = 0; +} + +void Editor::squelchNextAutoCalc() +{ + d->autoCalcTimer->stop(); +} + +void Editor::setText(const QString &txt) +{ + QTextEdit::setText(txt); + squelchNextAutoCalc(); +} + +void Editor::checkAutoComplete() +{ + if( !d->autoCompleteEnabled ) return; + + d->completionTimer->stop(); + d->completionTimer->start( 500, true ); +} + +void Editor::checkMatching() +{ + if( !d->syntaxHighlightEnabled ) return; + + d->matchingTimer->stop(); + d->matchingTimer->start( 200, true ); +} + +void Editor::checkAutoCalc() +{ + // Calc-As-You-Type + if( !d->autoCalcEnabled ) return; + + d->autoCalcTimer->stop(); + d->autoCalcTimer->start( 1000, true ); + d->autoCalcLabel->hide(); +} + +void Editor::doMatchingLeft() +{ + if( !d->syntaxHighlightEnabled ) return; + + // tokenize the expression + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + + // check for right par + QString subtext = text().left( curPos ); + Tokens tokens = Evaluator::scan( subtext ); + if( !tokens.valid() ) return; + if( tokens.count()<1 ) return; + Token lastToken = tokens[ tokens.count()-1 ]; + + // right par ? + if( lastToken.isOperator() ) + if( lastToken.asOperator() == Token::RightPar ) + if( lastToken.pos() == curPos-1 ) + { + // find the matching left par + unsigned par = 1; + int k = 0; + Token matchToken; + int matchPos = -1; + + for( k = tokens.count()-2; k >= 0; k-- ) + { + if( par < 1 ) break; + Token matchToken = tokens[k]; + if( matchToken.isOperator() ) + { + if( matchToken.asOperator() == Token::RightPar ) + par++; + if( matchToken.asOperator() == Token::LeftPar ) + par--; + if( par == 0 ) matchPos = matchToken.pos(); + } + } + + if( matchPos >= 0 ) + { + setSelection( 0, matchPos, 0, matchPos+1, 2 ); + setSelection( 0, lastToken.pos(), 0, lastToken.pos()+1, 1 ); + setCursorPosition( para, curPos ); + } + } +} + +void Editor::doMatchingRight() +{ + if( !d->syntaxHighlightEnabled ) return; + + // tokenize the expression + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + + // check for left par + QString subtext = text().right( text().length() - curPos ); + Tokens tokens = Evaluator::scan( subtext ); + if( !tokens.valid() ) return; + if( tokens.count()<1 ) return; + Token firstToken = tokens[ 0 ]; + + // left par ? + if( firstToken.isOperator() ) + if( firstToken.asOperator() == Token::LeftPar ) + if( firstToken.pos() == 0 ) + { + // find the matching right par + unsigned par = 1; + unsigned int k = 0; + Token matchToken; + int matchPos = -1; + + for( k = 1; k < tokens.count(); k++ ) + { + if( par < 1 ) break; + Token matchToken = tokens[k]; + if( matchToken.isOperator() ) + { + if( matchToken.asOperator() == Token::LeftPar ) + par++; + if( matchToken.asOperator() == Token::RightPar ) + par--; + if( par == 0 ) matchPos = matchToken.pos(); + } + } + + if( matchPos >= 0 ) + { + setSelection( 0, curPos+matchPos, 0, curPos+matchPos+1, 2 ); + setSelection( 0, curPos+firstToken.pos(), 0, curPos+firstToken.pos()+1, 1 ); + setCursorPosition( para, curPos ); + } + } + +} + +void Editor::triggerAutoComplete() +{ + if( !d->autoCompleteEnabled ) return; + + // tokenize the expression (don't worry, this is very fast) + // faster now that it uses flex. ;) + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + QString subtext = text().left( curPos ); + Tokens tokens = Evaluator::scan( subtext ); + if(!tokens.valid()) + { + kdWarning() << "invalid tokens.\n"; + return; + } + + if(tokens.isEmpty() || subtext.endsWith(" ")) + return; + + Token lastToken = tokens[ tokens.count()-1 ]; + + // last token must be an identifier + if( !lastToken.isIdentifier() ) + return; + + QString id = lastToken.text(); + if( id.isEmpty() ) + return; + + // find matches in function names + QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); + QStringList choices; + + for( unsigned i=0; i<fnames.count(); i++ ) + if( fnames[i].startsWith( id, false ) ) + { + QString str = fnames[i]; + + ::Function* f = FunctionManager::instance()->function( str ); + if( f && !f->description.isEmpty() ) + str.append( ':' ).append( f->description ); + + choices.append( str ); + } + + choices.sort(); + + // find matches in variables names + QStringList vchoices; + QStringList values = ValueManager::instance()->valueNames(); + + for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) + if( (*it).startsWith( id, false ) ) + { + QString choice = ValueManager::description(*it); + if(choice.isEmpty()) + choice = ValueManager::instance()->value(*it).toString(); + + vchoices.append( QString("%1:%2").arg( *it, choice ) ); + } + + vchoices.sort(); + choices += vchoices; + + // no match, don't bother with completion + if( !choices.count() ) return; + + // one match, complete it for the user + if( choices.count()==1 ) + { + QString str = QStringList::split( ':', choices[0] )[0]; + + // single perfect match, no need to give choices. + if(str == id.lower()) + return; + + str = str.remove( 0, id.length() ); + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + blockSignals( true ); + insert( str ); + setSelection( 0, curPos, 0, curPos+str.length() ); + blockSignals( false ); + return; + } + + // present the user with completion choices + d->completion->showCompletion( choices ); +} + +void Editor::autoComplete( const QString& item ) +{ + if( !d->autoCompleteEnabled || item.isEmpty() ) + return; + + int para = 0, curPos = 0; + getCursorPosition( ¶, &curPos ); + + QString subtext = text().left( curPos ); + Tokens tokens = Evaluator::scan( subtext ); + + if( !tokens.valid() || tokens.count() < 1 ) + return; + + Token lastToken = tokens[ tokens.count()-1 ]; + if( !lastToken.isIdentifier() ) + return; + + QStringList str = QStringList::split( ':', item ); + + blockSignals( true ); + setSelection( 0, lastToken.pos(), 0, lastToken.pos()+lastToken.text().length() ); + insert( str[0] ); + blockSignals( false ); +} + +void Editor::autoCalc() +{ + if( !d->autoCalcEnabled ) + return; + + QString str = Evaluator::autoFix( text() ); + if( str.isEmpty() ) + return; + + // too short? do not bother... + Tokens tokens = Evaluator::scan( str ); + if( tokens.count() < 2 ) + return; + + // If we're using set for a function don't try. + QRegExp setFn("\\s*set.*\\(.*="); + if( str.find(setFn) != -1 ) + return; + + // strip off assignment operator, e.g. "x=1+2" becomes "1+2" only + // the reason is that we want only to evaluate (on the fly) the expression, + // not to update (put the result in) the variable + if( tokens.count() > 2 && tokens[0].isIdentifier() && + tokens[1].asOperator() == Token::Equal ) + { + Tokens::const_iterator it = tokens.begin(); + ++it; + ++it; // Skip first two tokens. + + // Reconstruct string to evaluate using the tokens. + str = ""; + while(it != tokens.end()) + { + str += (*it).text(); + str += ' '; + ++it; + } + } + + Abakus::number_t result = parseString(str.latin1()); + if( Result::lastResult()->type() == Result::Value ) + { + QString ss = QString("Result: <b>%2</b>").arg(result.toString()); + d->autoCalcLabel->setText( ss ); + d->autoCalcLabel->adjustSize(); + + // reposition nicely + QPoint pos = mapToGlobal( QPoint( 0, 0 ) ); + pos.setY( pos.y() - d->autoCalcLabel->height() - 1 ); + d->autoCalcLabel->move( pos ); + d->autoCalcLabel->show(); + d->autoCalcLabel->raise(); + + // do not show it forever + QTimer::singleShot( 5000, d->autoCalcLabel, SLOT( hide()) ); + } + else + { + // invalid expression + d->autoCalcLabel->hide(); + } +} + +QString Editor::formatNumber( const Abakus::number_t &value ) const +{ + return value.toString(); +} + +void Editor::historyBack() +{ + if( d->history.isEmpty() ) + return; + + d->index--; + + if( d->index < 0 ) + d->index = 0; + + setText( d->history[ d->index ] ); + setCursorPosition( 0, text().length() ); + ensureCursorVisible(); +} + +void Editor::historyForward() +{ + if( d->history.isEmpty() ) + return; + + d->index++; + + if( d->index >= (int) d->history.count() ) + d->index = d->history.count() - 1; + + setText( d->history[ d->index ] ); + setCursorPosition( 0, text().length() ); + ensureCursorVisible(); +} + +void Editor::keyPressEvent( QKeyEvent* e ) +{ + if( e->key() == Key_Up ) + { + historyBack(); + e->accept(); + return; + } + + if( e->key() == Key_Down ) + { + historyForward(); + e->accept(); + return; + } + + if( e->key() == Key_Enter || e->key() == Key_Return ) + { + emit returnPressed(); + return; + } + + if( e->key() == Key_Left || + e->key() == Key_Right || + e->key() == Key_Home || + e->key() == Key_End ) + { + checkMatching(); + } + + QTextEdit::keyPressEvent( e ); +} + +void Editor::wheelEvent( QWheelEvent *e ) +{ + if( e->delta() > 0 ) + historyBack(); + else if( e->delta() < 0 ) + historyForward(); + + e->accept(); +} + +void Editor::setSyntaxHighlight( bool enable ) +{ + d->syntaxHighlightEnabled = enable; + d->highlighter->rehighlight(); +} + +bool Editor::isSyntaxHighlightEnabled() const +{ + return d->syntaxHighlightEnabled; +} + +void Editor::setHighlightColor( ColorType type, QColor color ) +{ + d->highlightColors[ type ] = color; + + setSelectionAttributes( 1, highlightColor( Editor::MatchedPar ), false ); + setSelectionAttributes( 2, highlightColor( Editor::MatchedPar ), false ); + + d->highlighter->rehighlight(); +} + +QColor Editor::highlightColor( ColorType type ) +{ + return d->highlightColors[ type ]; +} + + +EditorCompletion::EditorCompletion( Editor* editor ): QObject( editor ) +{ + d = new Private; + d->editor = editor; + + d->completionPopup = new QVBox( editor->topLevelWidget(), 0, WType_Popup ); + d->completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain ); + d->completionPopup->setLineWidth( 1 ); + d->completionPopup->installEventFilter( this ); + d->completionPopup->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum); + + d->completionListBox = new QListBox( d->completionPopup ); + d->completionPopup->setFocusProxy( d->completionListBox ); + d->completionListBox->setFrameStyle( QFrame::NoFrame ); + d->completionListBox->setVariableWidth( true ); + d->completionListBox->installEventFilter( this ); +} + +EditorCompletion::~EditorCompletion() +{ + delete d; +} + +bool EditorCompletion::eventFilter( QObject *obj, QEvent *ev ) +{ + if ( obj == d->completionPopup || obj == d->completionListBox ) + { + + if ( ev->type() == QEvent::KeyPress ) + { + QKeyEvent *ke = (QKeyEvent*)ev; + if ( ke->key() == Key_Enter || ke->key() == Key_Return ) + { + doneCompletion(); + return true; + } + else if ( ke->key() == Key_Left || ke->key() == Key_Right || + ke->key() == Key_Up || ke->key() == Key_Down || + ke->key() == Key_Home || ke->key() == Key_End || + ke->key() == Key_Prior || ke->key() == Key_Next ) + return false; + + d->completionPopup->close(); + d->editor->setFocus(); + QApplication::sendEvent( d->editor, ev ); + return true; + } + + if ( ev->type() == QEvent::MouseButtonDblClick ) + { + doneCompletion(); + return true; + } + + } + + return false; +} + +void EditorCompletion::doneCompletion() +{ + d->completionPopup->close(); + d->editor->setFocus(); + emit selectedCompletion( d->completionListBox->currentText() ); +} + +void EditorCompletion::showCompletion( const QStringList &choices ) +{ + static bool shown = false; + if( !choices.count() ) return; + + d->completionListBox->clear(); + int maxWidth = 0; + for( unsigned i = 0; i < choices.count(); i++ ) { + ChoiceItem *item = new ChoiceItem( d->completionListBox, choices[i] ); + int itemMaxWidth = item->nameWidth(); + + if(itemMaxWidth > maxWidth) + maxWidth = itemMaxWidth; + } + + for(unsigned i = 0; i < d->completionListBox->count(); ++i) { + ChoiceItem *item = static_cast<ChoiceItem *>(d->completionListBox->item(i)); + item->setMinNameWidth(maxWidth); + } + + d->completionListBox->setCurrentItem( 0 ); + + // size of the pop-up + d->completionPopup->setMaximumHeight( 120 ); + d->completionPopup->resize( d->completionListBox->sizeHint() + + QSize( d->completionListBox->verticalScrollBar()->width() + 4, + d->completionListBox->horizontalScrollBar()->height() + 4 ) ); + + if(!shown) + { + d->completionPopup->show(); + QTimer::singleShot ( 0, this, SLOT(moveCompletionPopup()) ); + } + else + { + moveCompletionPopup(); + d->completionPopup->show(); + } +} + +void EditorCompletion::moveCompletionPopup() +{ + int h = d->completionListBox->height(); + int w = d->completionListBox->width(); + + // position, reference is editor's cursor position in global coord + QFontMetrics fm( d->editor->font() ); + int para = 0, curPos = 0; + + d->editor->getCursorPosition( ¶, &curPos ); + + int pixelsOffset = fm.width( d->editor->text(), curPos ); + pixelsOffset -= d->editor->contentsX(); + QPoint pos = d->editor->mapToGlobal( QPoint( pixelsOffset, d->editor->height() ) ); + + // if popup is partially invisible, move to other position + NETRootInfo info(d->completionPopup->x11Display(), + NET::CurrentDesktop | NET::WorkArea | NET::NumberOfDesktops, + -1, false); + info.activate(); // wtf is this needed for? + NETRect NETarea = info.workArea(info.currentDesktop()); + + QRect area(NETarea.pos.x, NETarea.pos.y, NETarea.size.width, NETarea.size.height); + + if( pos.y() + h > area.y() + area.height() ) + pos.setY( pos.y() - h - d->editor->height() ); + if( pos.x() + w > area.x() + area.width() ) + pos.setX( area.x() + area.width() - w ); + + d->completionPopup->move( pos ); + d->completionListBox->setFocus(); +} + +#include "editor.moc" + +// vim: set et sw=2 ts=8: diff --git a/src/editor.h b/src/editor.h new file mode 100644 index 0000000..a71d661 --- /dev/null +++ b/src/editor.h @@ -0,0 +1,131 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004,2005 Ariya Hidayat <ariya@kde.org> + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne <michael.pyne@kdemail.net> + + 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; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + + +#ifndef ABAKUS_EDITOR_H +#define ABAKUS_EDITOR_H + +#include <qobject.h> +#include <qstringlist.h> +#include <qtextedit.h> + +#include "hmath.h" + +class QEvent; +class QKeyEvent; +class QWidget; +class Evaluator; + +class Editor : public QTextEdit +{ + Q_OBJECT + + public: + + typedef enum + { + Number, FunctionName, Variable, MatchedPar + } ColorType; + + Editor( QWidget* parent = 0, const char* name = 0 ); + ~Editor(); + + QSize sizeHint() const; + QSize xminimumSizeHint() const; + + QStringList history() const; + void setHistory( const QStringList& history ); + + bool autoCompleteEnabled() const; + void setAutoCompleteEnabled( bool enable ); + + bool autoCalcEnabled() const; + void setAutoCalcEnabled( bool enable ); + void setFormat( char format ); + void setDecimalDigits( int digits ); + + void setSyntaxHighlight( bool enable ); + bool isSyntaxHighlightEnabled() const; + void setHighlightColor( ColorType type, QColor color ); + QColor highlightColor( ColorType type ); + + public slots: + void appendHistory( const QString& text ); + void clearHistory(); + + // Stop the timer from going off. + void squelchNextAutoCalc(); + + void setText(const QString &txt); + + protected slots: + void checkAutoComplete(); + void triggerAutoComplete(); + void autoComplete( const QString& item ); + void checkAutoCalc(); + void autoCalc(); + void checkMatching(); + void doMatchingLeft(); + void doMatchingRight(); + void historyBack(); + void historyForward(); + + protected: + void keyPressEvent( QKeyEvent* ); + void wheelEvent( QWheelEvent* ); + QString formatNumber( const Abakus::number_t &value ) const; + + private: + class Private; + Private* d; + Editor( const Editor& ); + Editor& operator=( const Editor& ); +}; + + +class EditorCompletion : public QObject +{ + Q_OBJECT + + public: + EditorCompletion( Editor* editor ); + ~EditorCompletion(); + + bool eventFilter( QObject *o, QEvent *e ); + void doneCompletion(); + void showCompletion( const QStringList &choices ); + + protected slots: + void moveCompletionPopup(); + + signals: + void selectedCompletion( const QString& item ); + + private: + class Private; + Private* d; + EditorCompletion( const EditorCompletion& ); + EditorCompletion& operator=( const EditorCompletion& ); +}; + +#endif // ABAKUS_EDITOR_H + +// vim: set et ts=8 sw=4: diff --git a/src/evaluator.cpp b/src/evaluator.cpp new file mode 100644 index 0000000..4b47d5e --- /dev/null +++ b/src/evaluator.cpp @@ -0,0 +1,261 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004 Ariya Hidayat <ariya@kde.org> + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne <michael.pyne@kdemail.net> + + 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; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + +#include "evaluator.h" +#include "function.h" +#include "node.h" // For parser_yacc.hpp below +#include "parser_yacc.hpp" + +#include <qapplication.h> +#include <qmap.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qvaluevector.h> + +#include <kdebug.h> + +// +// Reimplementation of goodies from Evaluator follows. +// + +Evaluator::Evaluator() +{ +} + +Evaluator::~Evaluator() +{ +} + +void Evaluator::setExpression(const QString &expr) +{ + kdError() << k_funcinfo << " not implemented.\n"; +} + +QString Evaluator::expression() const +{ + kdError() << k_funcinfo << " not implemented.\n"; + return QString(); +} + +void Evaluator::clear() +{ + kdError() << k_funcinfo << " not implemented.\n"; + // Yeah, whatever. +} + +bool Evaluator::isValid() const +{ + return true; +} + +Tokens Evaluator::tokens() const +{ + kdError() << k_funcinfo << " not implemented.\n"; + return Tokens(); +} + +Tokens Evaluator::scan(const QString &expr) +{ + Lexer l(expr); + Tokens tokens; + + while(l.hasNext()) + { + int t = l.nextType(); + Token::Type type = Token::Unknown; + + switch(t) + { + case POWER: + case '*': + case '(': + case ')': + case '-': + case '+': + case ',': + case '=': + type = Token::Operator; + break; + + case NUM: + type = Token::Number; + break; + + case SET: + case REMOVE: + case DERIV: + case FN: + case ID: + type = Token::Identifier; + break; + + default: + type = Token::Unknown; + break; + } + + tokens.append(Token(type, l.tokenValue(), l.tokenPos())); + } + + return tokens; +} + +QString Evaluator::error() const +{ + kdError() << k_funcinfo << " not implemented.\n"; + return "No Error Yet"; +} + +/// +/// ARIYA'S CLASS CODE FOLLOWS +/// + +// 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::Comma; break; + case '(': result = Token::LeftPar; break; + case ')': result = Token::RightPar; break; + case '%': result = Token::Percent; break; + case '=': result = Token::Equal; break; + default : result = Token::InvalidOp; break; + } + } + + if( text.length() == 2 ) + { + if( text == "**" ) result = Token::Caret; + } + + return result; +} + +// 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; +} + +Abakus::number_t Token::asNumber() const +{ + if( isNumber() ) + return Abakus::number_t( m_text.latin1() ); + else + return Abakus::number_t(); +} + +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; +} + + +QString Evaluator::autoFix( const QString& expr ) +{ + int par = 0; + QString result; + + // strip off all funny characters + for( unsigned c = 0; c < expr.length(); c++ ) + if( expr[c] >= QChar(32) ) + result.append( expr[c] ); + + // automagically close all parenthesis + Tokens tokens = Evaluator::scan( result ); + for( unsigned i=0; i<tokens.count(); i++ ) + if( tokens[i].asOperator() == Token::LeftPar ) par++; + else if( tokens[i].asOperator() == Token::RightPar ) par--; + for(; par > 0; par-- ) + result.append( ')' ); + + // special treatment for simple function + // e.g. "cos" is regarded as "cos(ans)" + if( !result.isEmpty() ) + { + Tokens tokens = Evaluator::scan( result ); + if( (tokens.count() == 1) && + FunctionManager::instance()->isFunction(tokens[0].text()) + ) + { + result.append( "(ans)" ); + } + } + + return result; +} + +// vim: set et ts=8 sw=4: diff --git a/src/evaluator.h b/src/evaluator.h new file mode 100644 index 0000000..2d156eb --- /dev/null +++ b/src/evaluator.h @@ -0,0 +1,131 @@ +/* This file was part of the SpeedCrunch project + Copyright (C) 2004 Ariya Hidayat <ariya@kde.org> + + And is now part of abakus. + Copyright (c) 2005 Michael Pyne <michael.pyne@kdemail.net> + + 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; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02110-1301, USA. + */ + +#ifndef ABAKUS_EVALUATOR_H +#define ABAKUS_EVALUATOR_H + +#include <qstring.h> +#include <qvaluevector.h> + +#include "numerictypes.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) or **. + LeftPar, // ( + RightPar, // ) + Comma, // argument separator + Percent, + Equal // variable assignment + } Op; + + Token( Type type = Unknown, const QString& text = QString::null, 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; } + + Abakus::number_t 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 QValueVector<Token> +{ +public: + Tokens(): QValueVector<Token>(), 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; + Abakus::number_t value; +}; + +class Evaluator +{ +public: + Evaluator(); + ~Evaluator(); + + void setExpression( const QString& expr ); + QString expression() const; + + void clear(); + bool isValid() const; + + Tokens tokens() const; + static Tokens scan( const QString& expr ); + + QString error() const; + + // Abakus::number_t eval(); + + static QString autoFix( const QString& expr ); + +private: + Evaluator( const Evaluator& ); + Evaluator& operator=( const Evaluator& ); +}; + + +#endif // EVALUATOR + +// vim: set et ts=8 sw=4: diff --git a/src/function.cpp b/src/function.cpp new file mode 100644 index 0000000..70c4f1c --- /dev/null +++ b/src/function.cpp @@ -0,0 +1,298 @@ +/* + * function.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include "numerictypes.h" + +#include <kdebug.h> + +#include <qvaluevector.h> +#include <qstring.h> +#include <qregexp.h> + +#include <math.h> + +#include "function.h" +#include "node.h" +#include "valuemanager.h" +#include "hmath.h" + +// Used to try and avoid recursive function definitions +class DupFinder : public NodeFunctor +{ + public: + DupFinder(const QString &nameToFind) : + m_name(nameToFind), m_valid(true) + { + } + + virtual ~DupFinder() { } + + bool isValid() const { return m_valid; } + + virtual void operator()(const Node *node) + { + if(!m_valid) + return; + + const BaseFunction *fn = dynamic_cast<const BaseFunction *>(node); + if(fn && fn->name() == m_name) + m_valid = false; // Duplicate detected + } + + private: + QString m_name; + bool m_valid; +}; + +// Define static member for FunctionManager +FunctionManager *FunctionManager::m_manager = 0; + +FunctionManager *FunctionManager::instance() +{ + if(!m_manager) + m_manager = new FunctionManager; + + return m_manager; +} + +FunctionManager::FunctionManager(QObject *parent, const char *name) : + QObject(parent, name) +{ + m_dict.setAutoDelete(true); +} + +// Dummy return value to enable static initialization in the DECL_*() +// macros. +bool FunctionManager::addFunction(const QString &name, function_t fn, const QString &desc) +{ + Function *newFn = new Function; + QRegExp returnTrigRE("^a(cos|sin|tan)"); + QRegExp needsTrigRE("^(cos|sin|tan)"); + QString fnName(name); + + newFn->name = name; + newFn->description = desc; + newFn->fn = fn; + newFn->userDefined = false; + newFn->returnsTrig = fnName.contains(returnTrigRE); + newFn->needsTrig = fnName.contains(needsTrigRE); + + m_dict.insert(name, newFn); + + return false; +} + +#define DECLARE_FUNC(name, fn, desc) bool dummy##name = FunctionManager::instance()->addFunction(#name, fn, desc) + +// Declares a function name that is implemented by the function of a different +// name. e.g. atan -> Abakus::number_t::arctan() +#define DECLARE_FUNC2(name, fnName, desc) DECLARE_FUNC(name, &Abakus::number_t::fnName, desc) + +// Declares a function name that is implemented by the function of the +// same base name. +#define DECLARE_FUNC1(name, desc) DECLARE_FUNC2(name, name, desc) + +DECLARE_FUNC1(sin, "Trigonometric sine"); +DECLARE_FUNC1(cos, "Trigonometric cosine"); +DECLARE_FUNC1(tan, "Trigonometric tangent"); + +DECLARE_FUNC1(sinh, "Hyperbolic sine"); +DECLARE_FUNC1(cosh, "Hyperbolic cosine"); +DECLARE_FUNC1(tanh, "Hyperbolic tangent"); + +DECLARE_FUNC1(atan, "Inverse tangent"); +DECLARE_FUNC1(acos, "Inverse cosine"); +DECLARE_FUNC1(asin, "Inverse sine"); + +DECLARE_FUNC1(asinh, "Inverse hyperbolic sine"); +DECLARE_FUNC1(acosh, "Inverse hyperbolic cosine"); +DECLARE_FUNC1(atanh, "Inverse hyperbolic tangent"); + +DECLARE_FUNC1(abs, "Absolute value of number"); +DECLARE_FUNC1(sqrt, "Square root"); +DECLARE_FUNC1(ln, "Natural logarithm (base e)"); +DECLARE_FUNC1(log, "Logarithm (base 10)"); +DECLARE_FUNC1(exp, "Natural exponential function"); + +DECLARE_FUNC1(round, "Round to nearest number"); +DECLARE_FUNC1(ceil, "Nearest greatest integer"); +DECLARE_FUNC1(floor, "Nearest lesser integer"); +DECLARE_FUNC2(int, integer, "Integral part of number"); +DECLARE_FUNC1(frac, "Fractional part of number"); + +Function *FunctionManager::function(const QString &name) +{ + return m_dict[name]; +} + +// Returns true if the named identifier is a function, false otherwise. +bool FunctionManager::isFunction(const QString &name) +{ + return function(name) != 0; +} + +bool FunctionManager::isFunctionUserDefined(const QString &name) +{ + const Function *fn = function(name); + return (fn != 0) && (fn->userDefined); +} + +bool FunctionManager::addFunction(BaseFunction *fn, const QString &dependantVar) +{ + // First see if this function is recursive + DupFinder dupFinder(fn->name()); + UnaryFunction *unFunction = dynamic_cast<UnaryFunction *>(fn); + if(unFunction && unFunction->operand()) { + unFunction->operand()->applyMap(dupFinder); + if(!dupFinder.isValid()) + return false; + } + + // Structure holds extra data needed to call the user defined + // function. + UserFunction *newFn = new UserFunction; + newFn->sequenceNumber = m_dict.count(); + newFn->fn = fn; + newFn->varName = QString(dependantVar); + + // Now setup the Function data structure that holds the information + // we need to access and call the function later. + Function *fnTabEntry = new Function; + fnTabEntry->name = fn->name(); + fnTabEntry->userFn = newFn; + fnTabEntry->returnsTrig = false; + fnTabEntry->needsTrig = false; + fnTabEntry->userDefined = true; + + if(m_dict.find(fn->name())) + emit signalFunctionRemoved(fn->name()); + + m_dict.replace(fn->name(), fnTabEntry); + emit signalFunctionAdded(fn->name()); + + return true; +} + +void FunctionManager::removeFunction(const QString &name) +{ + Function *fn = function(name); + + // If we remove a function, we need to decrement the sequenceNumber of + // functions after this one. + if(fn && fn->userDefined) { + int savedSeqNum = fn->userFn->sequenceNumber; + + // Emit before we actually remove it so that the info on the function + // can still be looked up. + emit signalFunctionRemoved(name); + + delete fn->userFn; + fn->userFn = 0; + m_dict.remove(name); + + QDictIterator<Function> it(m_dict); + for (; it.current(); ++it) { + UserFunction *userFn = it.current()->userDefined ? it.current()->userFn : 0; + if(userFn && userFn->sequenceNumber > savedSeqNum) + --it.current()->userFn->sequenceNumber; + } + } +} + +QStringList FunctionManager::functionList(FunctionManager::FunctionType type) +{ + QDictIterator<Function> it(m_dict); + QStringList functions; + + switch(type) { + case Builtin: + for(; it.current(); ++it) + if(!it.current()->userDefined) + functions += it.current()->name; + break; + + case UserDefined: + // We want to return the function names in the order they were + // added. + { + QValueVector<Function *> fnTable(m_dict.count(), 0); + QValueVector<int> sequenceNumberTable(m_dict.count(), -1); + + // First find out what sequence numbers we have. + for(; it.current(); ++it) + if(it.current()->userDefined) { + int id = it.current()->userFn->sequenceNumber; + fnTable[id] = it.current(); + sequenceNumberTable.append(id); + } + + // Now sort the sequence numbers and return the ordered list + qHeapSort(sequenceNumberTable.begin(), sequenceNumberTable.end()); + + for(unsigned i = 0; i < sequenceNumberTable.count(); ++i) + if(sequenceNumberTable[i] >= 0) + functions += fnTable[sequenceNumberTable[i]]->name; + } + break; + + case All: + functions += functionList(Builtin); + functions += functionList(UserDefined); + break; + } + + return functions; +} + +// Applies the function identified by func, using value as a parameter. +Abakus::number_t evaluateFunction(const Function *func, const Abakus::number_t value) +{ + if(func->userDefined) { + // Pull real entry from userFunctionTable + UserFunction *realFunction = func->userFn; + + bool wasSet = ValueManager::instance()->isValueSet(realFunction->varName); + Abakus::number_t oldValue; + if(wasSet) + oldValue = ValueManager::instance()->value(realFunction->varName); + + ValueManager::instance()->setValue(realFunction->varName, value); + Abakus::number_t result = realFunction->fn->value(); + + if(wasSet) + ValueManager::instance()->setValue(realFunction->varName, oldValue); + else + ValueManager::instance()->removeValue(realFunction->varName); + + return result; + } + + return (value.*(func->fn))(); +} + +void setTrigMode(Abakus::TrigMode mode) +{ + Abakus::m_trigMode = mode; +} + +Abakus::TrigMode trigMode() +{ + return Abakus::m_trigMode; +} + +#include "function.moc" diff --git a/src/function.h b/src/function.h new file mode 100644 index 0000000..3b56cbb --- /dev/null +++ b/src/function.h @@ -0,0 +1,122 @@ +#ifndef ABAKUS_FUNCTION_H +#define ABAKUS_FUNCTION_H +/* + * function.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include "numerictypes.h" + +#include <qobject.h> +#include <qstringlist.h> +#include <qstring.h> +#include <qmap.h> +#include <qdict.h> + + + +class BaseFunction; + +struct UserFunction +{ + int sequenceNumber; + BaseFunction *fn; + QString varName; +}; + +// Ugly pointer-to-member typedef ahead +typedef Abakus::number_t (Abakus::number_t::*function_t)() const; + +struct Function { + QString name; + QString description; + + // A function is either builtin or user defined, this union is + // used for both cases. + union { + function_t fn; // Builtin. + UserFunction *userFn; // User defined + }; + + bool returnsTrig; + bool needsTrig; + + bool userDefined; +}; + +void setTrigMode(Abakus::TrigMode mode); +Abakus::TrigMode trigMode(); + +class FunctionManager : public QObject +{ + Q_OBJECT + public: + typedef QDict<Function> functionDict; + + static FunctionManager *instance(); + + Function *function(const QString &name); + + bool isFunction(const QString &name); + bool isFunctionUserDefined(const QString &name); + + bool addFunction(BaseFunction *fn, const QString &dependantVar); + bool addFunction(const QString &name, function_t fn, const QString &desc); + void removeFunction(const QString &name); + + typedef enum { Builtin, UserDefined, All } FunctionType; + + QStringList functionList(FunctionType type); + + signals: + void signalFunctionAdded(const QString &name); + void signalFunctionRemoved(const QString &name); + + private: + FunctionManager(QObject *parent = 0, const char *name = "function manager"); + + static FunctionManager *m_manager; + functionDict m_dict; +}; + +Abakus::number_t evaluateFunction(const Function *func, const Abakus::number_t value); + +// Implemented in lexer.l due to prototype issues. +Abakus::number_t parseString(const char *str); + +// Implemented in lexer.l due to prototype issues. +class Lexer +{ +public: + Lexer(const QString &expr); + ~Lexer(); + + bool hasNext() const; + int nextType(); + + int tokenPos() const; + + // Can call this after nextType to find the associated string value of the + // token. + QString tokenValue() const; + +private: + class Private; + Private *m_private; +}; + +#endif diff --git a/src/hi64-app-abakus.png b/src/hi64-app-abakus.png Binary files differnew file mode 100644 index 0000000..5644c45 --- /dev/null +++ b/src/hi64-app-abakus.png diff --git a/src/hmath.cpp b/src/hmath.cpp new file mode 100644 index 0000000..8dcf346 --- /dev/null +++ b/src/hmath.cpp @@ -0,0 +1,1797 @@ +/* HMath: C++ high precision math routines + Copyright (C) 2004 Ariya Hidayat <ariya.hidayat@gmail.com> + Last update: November 15, 2004 + + This file was copied from the SpeedCrunch program. Please visit + http://speedcrunch.berlios.de/ for more information. + + 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. If not, write to: + The Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02110-1301 USA. + + */ + +#include "hmath.h" +#include "number.h" + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include <qstring.h> + +// 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 20 + +// from number.c, need to be freed somehow +extern bc_num _zero_; +extern bc_num _one_; +extern bc_num _two_; + +class HNumber::Private +{ +public: + bc_num num; + bool nan; +}; + +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; +} + +// 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; + char zero_int; + + /* 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)) + return h_create(); + + /* Adjust numbers and allocate storage and initialize fields. */ + strscale = MIN(strscale, scale); + if (digits == 0) + { + zero_int = TRUE; + digits = 1; + } + + bc_num 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; +} + + +// 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; +} + +// 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; +} + +// 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; +} + +// 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; +} + +// 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() +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); +} + +HNumber::HNumber( const HNumber& hn ) +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); + operator=( hn ); +} + +HNumber::HNumber( int i ) +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); + bc_int2num( &d->num, i ); +} + +HNumber::HNumber( const char* str ) +{ + h_init(); + d = new Private; + d->nan = false; + d->num = h_create(); + + 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 ); + + char* p = s; + for( ;; p++ ) + { + if( *p != '+' ) + if( *p != '-' ) + if( *p != '.' ) + if( !isdigit(*p) ) + break; + } + + int expd = 0; + + if( ( *p == 'e' ) || ( *p == 'E' ) ) + { + *p = '\0'; + expd = atoi( p+1 ); + } + + h_destroy( d->num ); + d->num = h_str2num( s ); + delete [] s; + + if( expd >= HMATH_MAX_PREC || // too large + expd <= -HMATH_MAX_PREC ) // too small + { + d->nan = true; + } + + 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 ); +} + +HNumber HNumber::nan() +{ + HNumber n; + n.d->nan = true; + return n; +} + +HNumber& HNumber::operator=( const HNumber& hn ) +{ + d->nan = hn.d->nan; + h_destroy( d->num ); + d->num = h_copy( hn.d->num ); + 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 ); + + HNumber result; + h_destroy( result.d->num ); + result.d->num = h_div( d->num, num.d->num ); + if(num == HNumber(0)) + result.d->nan = true; + + return result; +} + +HNumber& HNumber::operator/=( const HNumber& num ) +{ + HNumber n = HNumber(*this) / num; + operator=( n ); + return *this; +} + +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 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; c<n->n_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; d<k; d++ ) + *p++ = (char)BCD_CHAR( n->n_value[n->n_len+d] ); + for( int r=n->n_scale; r<prec; r++ ) + *p++ = '0'; + } + + *p = '\0'; + h_destroy( n ); + + return str; +} + +// format number with exponential +char* HMath::formatExp( 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; c<hn.d->num->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; c<hn.d->num->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 = formatExp( hn, prec ); + else if( ( expd < -4 ) && (expd>-HMATH_COMPARE_PREC ) ) + str = formatExp( hn, prec ); + else if ( (expd < 0) && (prec>0) && (expd < -prec) ) + str = formatExp( hn, prec ); + else + str = formatFixed( hn, prec ); + + return str; +} + +QString HMath::formatGenString( const HNumber &n, int prec ) +{ + char *foo = formatGeneral(n, prec); + QString s(foo); + free(foo); + + return s; +} + +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=='e' ) + return formatExp( hn, prec ); + + // fallback to 'g' + return formatGeneral( hn, prec ); +} + +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::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::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::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::raise( const HNumber& n1, int n ) +{ + if( n1.isNan() ) return n1; + + if( n1.isZero() ) return n1; + if( n1 == HNumber(1) ) return n1; + if( n == 0 ) return HNumber(1); + if( n == 1 ) return n1; + + HNumber result = n1; + for( ; n > 1; n-- ) 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(); + + if( n1.isZero() ) return HNumber(0); + if( n1 == HNumber(1) ) return n1; + if( n2.isZero() ) return HNumber(1); + if( n2 == HNumber(1) ) return n1; + + if( n2 == HMath::integer(n2) ) + { + // Evil hack. + char *str = HMath::format( n2 ); + int i = atoi(str); + free (str); + + return HMath::raise( n1, i ); + } + + // x^y = exp( y*ln(x) ) + HNumber 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 + // 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( negative ) + result = HMath::div( HNumber(1), result ); + + return result; +}; + +HNumber HMath::ln( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + if( !x.isPositive() ) + 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() ) + return HNumber::nan(); + + if( !x.isPositive() ) + return HNumber::nan(); + + HNumber result = HMath::ln( x ) / HMath::ln(10); + return result; +} + +// ensure angle is within 0 to 2*pi +// useful for sin, cos +static HNumber simplifyAngle( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + HNumber pi2 = HMath::pi() * 2; + HNumber nn = x / pi2; + HNumber xs = x - HMath::integer(nn)*pi2; + if( xs.isNegative() ) xs += pi2; + + return xs; +} + +HNumber HMath::sin( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // short circuit + if( x.isZero() ) + return x; + + // adjust to small angle for speedup + HNumber xs = simplifyAngle( x ); + + // 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; + } + + HNumber result = sum; + return result; +} + + +HNumber HMath::cos( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // short circuit + if( x.isZero() ) + return HNumber( 1 ); + + // adjust to small angle for speedup + HNumber xs = simplifyAngle( x ); + + // 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; + } + + HNumber result = sum; + return result; +} + +HNumber HMath::tan( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + // tan(x) = cos(x)/sin(x) + + HNumber s = HMath::sin( x ); + if( s.isZero() ) + return s; + + HNumber c = HMath::cos( x ); + if( c.isZero() ) + return HNumber::nan(); + + HNumber result = s / c; + return result; +} + +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() ) + return HNumber::nan(); + + // asin(x) = atan(x/sqrt(1-x*x)); + HNumber d = HMath::sqrt( HNumber(1) - x*x ); + if( d.isZero() ) + { + HNumber result = HMath::pi()/2; + if( x.isNegative() ) + result = HMath::negate( result ); + return result; + } + + HNumber result = HMath::atan( x / d ); + return result; +}; + +HNumber HMath::acos( const HNumber& x ) +{ + if( x.isNan() ) + return HNumber::nan(); + + if( x.isZero() ) + return HMath::pi()/2; + + // acos(x) = atan(sqrt(1-x*x)/x); + HNumber n = HMath::sqrt( HNumber(1) - x*x ); + + HNumber result = HMath::atan( n / x ); + return result; +}; + +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::asinh( const HNumber& x ) +{ + HNumber one(1); + + if(x.isNan()) + return HNumber::nan(); + + return HMath::ln(x + HMath::sqrt(x * x + one)); +} + +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::acosh( const HNumber& x ) +{ + HNumber one(1), zero(0); + + if(x.isNan() || x < one) + return HNumber::nan(); + + // We always return the positive arc hyperbolic cosine. + return HMath::ln(x + HMath::sqrt(x * x - one)); +} + +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::atanh( const HNumber& x ) +{ + HNumber one(1), two(2); + + if(x.isNan() || HMath::abs(x) >= one) + return HNumber::nan(); + + return HMath::ln((one + x) / (one - x)) / two; +} + +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, HNumber num ) +{ + char* str = HMath::formatFixed( num ); + s << str; + delete[] str; + return s; +} + +#ifdef HMATH_TEST + +#include <iostream> +#include <string.h> + +static int hmath_total_tests = 0; +static int hmath_failed_tests = 0; + +#define CHECK(x,y) check_value(__FILE__,__LINE__,#x,x,y) +#define CHECK_FORMAT(f,p,x,y) check_format(__FILE__,__LINE__,#x,x,f,p,y) +#define CHECK_PRECISE(x,y) check_precise(__FILE__,__LINE__,#x,x,y) + +static void check_value( const char *file, int line, const char* msg, +const HNumber&n, const char* expected ) +{ + hmath_total_tests++; + char* result = HMath::formatFixed( n ); + if( strcmp( result, expected ) ) + { + hmath_failed_tests++; + std::cout << file << "["<< line <<"]: " << msg; + std::cout << " Result: " << result; + std::cout << ", "; + std::cout << "Expected: " << expected; + std::cout << std::endl; + } + free( result ); +} + +static void check_format( const char *file, int line, const char* msg, +const HNumber&n, char format, int prec, const char* expected ) +{ + hmath_total_tests++; + char* result = HMath::format( n, format, prec ); + if( strcmp( result, expected ) ) + { + hmath_failed_tests++; + std::cout << file << "["<< line <<"]: " << msg; + std::cout << " Result: " << result; + std::cout << ", "; + std::cout << "Expected: " << expected; + std::cout << std::endl; + } + free( result ); +} + +static void check_precise( const char *file, int line, const char* msg, +const HNumber&n, const char* expected ) +{ + hmath_total_tests++; + char* result = HMath::formatFixed( n, 50 ); + if( strcmp( result, expected ) ) + { + hmath_failed_tests++; + std::cout << file << "["<< line <<"]: " << msg; + std::cout << " Result: " << result; + std::cout << ", "; + std::cout << "Expected: " << expected; + std::cout << std::endl; + } + free( result ); +} + +void test_create() +{ + CHECK( HNumber("1.0"), "1" ); + CHECK( HNumber("2.0"), "2" ); + CHECK( HNumber("1e-3"), "0.001" ); + + // too large or small + CHECK( HNumber("1e200"), "NaN" ); + CHECK( HNumber("1e-200"), "NaN" ); +} + +void test_format() +{ + // fixed decimal digits + CHECK_FORMAT( 'f', 0, HNumber("NaN"), "NaN" ); + CHECK_FORMAT( 'f', 0, HNumber("0"), "0" ); + CHECK_FORMAT( 'f', 0, HNumber("1.1"), "1" ); + CHECK_FORMAT( 'f', 1, HNumber("2.11"), "2.1" ); + CHECK_FORMAT( 'f', 2, HNumber("3.111"), "3.11" ); + CHECK_FORMAT( 'f', 3, HNumber("4.1111"), "4.111" ); + CHECK_FORMAT( 'f', 2, HNumber("3.14"), "3.14" ); + CHECK_FORMAT( 'f', 3, HNumber("3.14"), "3.140" ); + CHECK_FORMAT( 'f', 5, HNumber("3.14"), "3.14000" ); + CHECK_FORMAT( 'f', 7, HNumber("3.14"), "3.1400000" ); + CHECK_FORMAT( 'f', 7, HNumber("-0.001"), "-0.0010000" ); + CHECK_FORMAT( 'f', 8, HNumber("-0.001"), "-0.00100000" ); + CHECK_FORMAT( 'f', 9, HNumber("-0.001"), "-0.001000000" ); + CHECK_FORMAT( 'f', 1, HNumber("4.001"), "4.0" ); + CHECK_FORMAT( 'f', -1, HNumber("4.000000000000000000000000000000000000000000001"), "4" ); + + // exponential format + CHECK_FORMAT( 'e', 0, HNumber("NaN"), "NaN" ); + CHECK_FORMAT( 'e', 0, HNumber("0"), "0e0" ); + CHECK_FORMAT( 'e', 0, HNumber("3.14"), "3e0" ); + CHECK_FORMAT( 'e', 1, HNumber("3.14"), "3.1e0" ); + CHECK_FORMAT( 'e', 2, HNumber("3.14"), "3.14e0" ); + CHECK_FORMAT( 'e', 3, HNumber("3.14"), "3.140e0" ); + CHECK_FORMAT( 'e', 5, HNumber("3.14"), "3.14000e0" ); + CHECK_FORMAT( 'e', 7, HNumber("3.14"), "3.1400000e0" ); + CHECK_FORMAT( 'e', 3, HNumber("-0.001"), "-1.000e-3" ); + CHECK_FORMAT( 'e', 2, HNumber("0.0001"), "1.00e-4" ); + CHECK_FORMAT( 'e', 2, HNumber("0.001"), "1.00e-3" ); + CHECK_FORMAT( 'e', 2, HNumber("0.01"), "1.00e-2" ); + CHECK_FORMAT( 'e', 2, HNumber("0.1"), "1.00e-1" ); + CHECK_FORMAT( 'e', 2, HNumber("1"), "1.00e0" ); + CHECK_FORMAT( 'e', 2, HNumber("10"), "1.00e1" ); + CHECK_FORMAT( 'e', 2, HNumber("100"), "1.00e2" ); + CHECK_FORMAT( 'e', 2, HNumber("1000"), "1.00e3" ); + CHECK_FORMAT( 'e', 2, HNumber("10000"), "1.00e4" ); + CHECK_FORMAT( 'e', 2, HNumber("100000"), "1.00e5" ); + CHECK_FORMAT( 'e', 2, HNumber("1000000"), "1.00e6" ); + CHECK_FORMAT( 'e', 2, HNumber("10000000"), "1.00e7" ); + + // general format + CHECK_FORMAT( 'g', -1, HMath::pi(), "3.14159265358979323846" ); + CHECK_FORMAT( 'g', 3, HNumber("0"), "0.000" ); + CHECK_FORMAT( 'g', 3, HNumber("0.000000001"), "1.000e-9" ); + CHECK_FORMAT( 'g', 3, HNumber("0.00000001"), "1.000e-8" ); + CHECK_FORMAT( 'g', 3, HNumber("0.0000001"), "1.000e-7" ); + CHECK_FORMAT( 'g', 3, HNumber("0.000001"), "1.000e-6" ); + CHECK_FORMAT( 'g', 3, HNumber("0.00001"), "1.000e-5" ); + CHECK_FORMAT( 'g', 3, HNumber("0.0001"), "1.000e-4" ); + CHECK_FORMAT( 'g', 3, HNumber("0.001"), "0.001" ); + CHECK_FORMAT( 'g', 3, HNumber("0.01"), "0.010" ); + CHECK_FORMAT( 'g', 3, HNumber("0.1"), "0.100" ); + CHECK_FORMAT( 'g', 3, HNumber("10"), "10.000" ); + CHECK_FORMAT( 'g', 3, HNumber("100"), "100.000" ); + CHECK_FORMAT( 'g', 3, HNumber("1000"), "1000.000" ); + CHECK_FORMAT( 'g', 3, HNumber("10000"), "10000.000" ); + CHECK_FORMAT( 'g', 3, HNumber("100000"), "100000.000" ); + CHECK_FORMAT( 'g', 3, HNumber("1000000"), "1.000e6" ); + CHECK_FORMAT( 'g', 3, HNumber("10000000"), "1.000e7" ); + CHECK_FORMAT( 'g', 3, HNumber("100000000"), "1.000e8" ); + CHECK_FORMAT( 'g', 3, HNumber("1403.1977"), "1403.198" ); + CHECK_FORMAT( 'g', 3, HNumber("2604.1980"), "2604.198" ); + CHECK_FORMAT( 'g', 3, HNumber("2.47e4"), "24700.000" ); + +} + +void test_op() +{ + // addition + CHECK( HNumber(0)+HNumber(0), "0" ); + CHECK( HNumber(1)+HNumber(0), "1" ); + CHECK( HNumber(1)+HNumber(1), "2" ); + CHECK( HNumber(1)+HNumber(2), "3" ); + CHECK( HNumber(1)+HNumber(10), "11" ); + CHECK( HNumber(1)+HNumber(100), "101" ); + CHECK( HNumber(1)+HNumber(1000), "1001" ); + + // subtraction + CHECK( HNumber(0)-HNumber(0), "0" ); + CHECK( HNumber(1)-HNumber(0), "1" ); + CHECK( HNumber(1)-HNumber(2), "-1" ); + + // division + CHECK( HNumber(1)/HNumber(2), "0.5" ); + CHECK_PRECISE( HNumber(1)/HNumber(3), "0.33333333333333333333333333333333333333333333333333" ); + CHECK_PRECISE( HNumber(2)/HNumber(3), "0.66666666666666666666666666666666666666666666666667" ); + CHECK_PRECISE( HNumber(1)/HNumber(7), "0.14285714285714285714285714285714285714285714285714" ); + CHECK_PRECISE( HNumber(2)/HNumber(7), "0.28571428571428571428571428571428571428571428571429" ); + CHECK_PRECISE( HNumber(3)/HNumber(7), "0.42857142857142857142857142857142857142857142857143" ); + CHECK_PRECISE( HNumber(4)/HNumber(7), "0.57142857142857142857142857142857142857142857142857" ); + CHECK_PRECISE( HNumber(1)/HNumber(9), "0.11111111111111111111111111111111111111111111111111" ); + + // multiplication + CHECK( HNumber(0)*HNumber(0), "0" ); + CHECK( HNumber("1.0")*HNumber("0.0"), "0" ); + CHECK( HNumber(1)*HNumber(1), "1" ); + CHECK( HNumber(3)*HNumber(-4), "-12" ); + CHECK( HNumber(-2)*HNumber(5), "-10" ); + CHECK( HNumber(6)*HNumber(7), "42" ); + CHECK( HNumber("1.5")*HNumber("1.5"), "2.25" ); +} + +void test_functions() +{ + // pi + CHECK( HMath::pi(), "3.14159265358979323846" ); + CHECK_PRECISE( HMath::pi(), "3.14159265358979323846264338327950288419716939937511" ); + + // abs + CHECK( HMath::abs("0"), "0" ); + CHECK( HMath::abs("1"), "1" ); + CHECK( HMath::abs("100"), "100" ); + CHECK( HMath::abs("-100"), "100" ); + CHECK( HMath::abs("-3.14159"), "3.14159" ); + CHECK( HMath::abs("NaN"), "NaN" ); + + // round + CHECK( HMath::round( "3.14" ), "3" ); + CHECK( HMath::round( "1.77" ), "2" ); + CHECK( HMath::round( "3.14159", 4 ), "3.1416" ); + CHECK( HMath::round( "3.14159", 3 ), "3.142" ); + CHECK( HMath::round( "3.14159", 2 ), "3.14" ); + CHECK( HMath::round( "3.14159", 1 ), "3.1" ); + CHECK( HMath::round( "3.14159", 0 ), "3" ); + CHECK( HMath::round( "-2.6041980", 4 ), "-2.6042" ); + CHECK( HMath::round( "-2.6041980", 3 ), "-2.604" ); + CHECK( HMath::round( "-2.6041980", 2 ), "-2.6" ); + CHECK( HMath::round( "-2.6041980", 1 ), "-2.6" ); + CHECK( HMath::round( "-2.6041980", 0 ), "-3" ); + CHECK( HMath::round( "NaN" ), "NaN" ); + + // integer + CHECK( HMath::integer( "0" ), "0" ); + CHECK( HMath::integer( "0.25" ), "0" ); + CHECK( HMath::integer( "0.85" ), "0" ); + CHECK( HMath::integer( "14.0377" ), "14" ); + CHECK( HMath::integer( "-0.25" ), "0" ); + CHECK( HMath::integer( "-0.85" ), "0" ); + CHECK( HMath::integer( "-14.0377" ), "-14" ); + CHECK( HMath::integer( "NaN" ), "NaN" ); + + // frac + CHECK( HMath::frac( "0" ), "0" ); + CHECK( HMath::frac( "3.14159" ), "0.14159" ); + CHECK( HMath::frac( "0.14159" ), "0.14159" ); + CHECK( HMath::frac( "-3.14159" ), "-0.14159" ); + CHECK( HMath::frac( "-0.14159" ), "-0.14159" ); + CHECK( HMath::frac( "NaN" ), "NaN" ); + + // checking function 'sqrt' + CHECK( HMath::sqrt(1), "1" ); + CHECK( HMath::sqrt(4), "2" ); + CHECK( HMath::sqrt(9), "3" ); + CHECK( HMath::sqrt(16), "4" ); + CHECK_PRECISE( HMath::sqrt(2), "1.41421356237309504880168872420969807856967187537695" ); + CHECK_PRECISE( HMath::sqrt(3), "1.73205080756887729352744634150587236694280525381038" ); + CHECK_PRECISE( HMath::sqrt(5), "2.23606797749978969640917366873127623544061835961153" ); + CHECK_PRECISE( HMath::sqrt(7), "2.64575131106459059050161575363926042571025918308245" ); + CHECK_PRECISE( HMath::sqrt(8), "2.82842712474619009760337744841939615713934375075390" ); + CHECK_PRECISE( HMath::sqrt(10), "3.16227766016837933199889354443271853371955513932522" ); + CHECK_PRECISE( HMath::sqrt(11), "3.31662479035539984911493273667068668392708854558935" ); + CHECK_PRECISE( HMath::sqrt(12), "3.46410161513775458705489268301174473388561050762076" ); + CHECK_PRECISE( HMath::sqrt(13), "3.60555127546398929311922126747049594625129657384525" ); + CHECK_PRECISE( HMath::sqrt(14), "3.74165738677394138558374873231654930175601980777873" ); + CHECK_PRECISE( HMath::sqrt(15), "3.87298334620741688517926539978239961083292170529159" ); + CHECK_PRECISE( HMath::sqrt(17), "4.12310562561766054982140985597407702514719922537362" ); + CHECK_PRECISE( HMath::sqrt(18), "4.24264068711928514640506617262909423570901562613084" ); + CHECK_PRECISE( HMath::sqrt(19), "4.35889894354067355223698198385961565913700392523244" ); + CHECK_PRECISE( HMath::sqrt(20), "4.47213595499957939281834733746255247088123671922305" ); + CHECK( HMath::sqrt("0.04"), "0.2" ); + CHECK( HMath::sqrt("0.09"), "0.3" ); + CHECK( HMath::sqrt("0.16"), "0.4" ); + CHECK( HMath::sqrt(-1), "NaN" ); + CHECK( HMath::sqrt("NaN"), "NaN" ); + + // raise + CHECK( HMath::raise(10,-3), "0.001" ); + CHECK( HMath::raise(10,-2), "0.01" ); + CHECK( HMath::raise(10,-1), "0.1" ); + CHECK( HMath::raise(10,0), "1" ); + CHECK( HMath::raise(10,1), "10" ); + CHECK( HMath::raise(10,2), "100" ); + CHECK( HMath::raise(10,3), "1000" ); + CHECK( HMath::raise(10,4), "10000" ); + CHECK( HMath::raise("2","2"), "4" ); + CHECK( HMath::raise("3","3"), "27" ); + CHECK( HMath::raise("4","4"), "256" ); + CHECK_PRECISE( HMath::raise("2","0.1"), "1.07177346253629316421300632502334202290638460497756" ); + CHECK_PRECISE( HMath::raise("2","0.2"), "1.14869835499703500679862694677792758944385088909780" ); + CHECK_PRECISE( HMath::raise("2","0.3"), "1.23114441334491628449939306916774310987613776110082" ); + CHECK( HMath::raise("NaN","0"), "NaN" ); + CHECK( HMath::raise("-1","NaN"), "NaN" ); + + // exp + CHECK_PRECISE( HMath::exp("0.1"), "1.10517091807564762481170782649024666822454719473752" ); + CHECK_PRECISE( HMath::exp("0.2"), "1.22140275816016983392107199463967417030758094152050" ); + CHECK_PRECISE( HMath::exp("0.3"), "1.34985880757600310398374431332800733037829969735937" ); + CHECK_PRECISE( HMath::exp("0.4"), "1.49182469764127031782485295283722228064328277393743" ); + CHECK_PRECISE( HMath::exp("0.5"), "1.64872127070012814684865078781416357165377610071015" ); + CHECK_PRECISE( HMath::exp("0.6"), "1.82211880039050897487536766816286451338223880854644" ); + CHECK_PRECISE( HMath::exp("0.7"), "2.01375270747047652162454938858306527001754239414587" ); + CHECK_PRECISE( HMath::exp("0.8"), "2.22554092849246760457953753139507675705363413504848" ); + CHECK_PRECISE( HMath::exp("0.9"), "2.45960311115694966380012656360247069542177230644008" ); + CHECK_PRECISE( HMath::exp("1.0"), "2.71828182845904523536028747135266249775724709369996" ); + + // ln + CHECK_PRECISE( HMath::ln("0.1"), "-2.30258509299404568401799145468436420760110148862877" ); + CHECK_PRECISE( HMath::ln("0.2"), "-1.60943791243410037460075933322618763952560135426852" ); + CHECK_PRECISE( HMath::ln("0.3"), "-1.20397280432593599262274621776183850295361093080602" ); + CHECK_PRECISE( HMath::ln("0.4"), "-0.91629073187415506518352721176801107145010121990826" ); + CHECK_PRECISE( HMath::ln("0.5"), "-0.69314718055994530941723212145817656807550013436026" ); + CHECK_PRECISE( HMath::ln("0.6"), "-0.51082562376599068320551409630366193487811079644577" ); + CHECK_PRECISE( HMath::ln("0.7"), "-0.35667494393873237891263871124118447796401675904691" ); + CHECK_PRECISE( HMath::ln("0.8"), "-0.22314355131420975576629509030983450337460108554801" ); + CHECK_PRECISE( HMath::ln("0.9"), "-0.10536051565782630122750098083931279830612037298327" ); + CHECK_PRECISE( HMath::ln("1.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::ln("1.1"), "0.09531017980432486004395212328076509222060536530864" ); + CHECK_PRECISE( HMath::ln("1.2"), "0.18232155679395462621171802515451463319738933791449" ); + CHECK_PRECISE( HMath::ln("1.3"), "0.26236426446749105203549598688095439720416645613143" ); + CHECK_PRECISE( HMath::ln("1.4"), "0.33647223662121293050459341021699209011148337531334" ); + CHECK_PRECISE( HMath::ln("1.5"), "0.40546510810816438197801311546434913657199042346249" ); + CHECK_PRECISE( HMath::ln("1.6"), "0.47000362924573555365093703114834206470089904881225" ); + CHECK_PRECISE( HMath::ln("1.7"), "0.53062825106217039623154316318876232798710152395697" ); + CHECK_PRECISE( HMath::ln("1.8"), "0.58778666490211900818973114061886376976937976137698" ); + CHECK_PRECISE( HMath::ln("1.9"), "0.64185388617239477599103597720348932963627777267036" ); + CHECK_PRECISE( HMath::ln("2.0"), "0.69314718055994530941723212145817656807550013436026" ); + CHECK_PRECISE( HMath::ln("3.0"), "1.09861228866810969139524523692252570464749055782275" ); + CHECK_PRECISE( HMath::ln("4.0"), "1.38629436111989061883446424291635313615100026872051" ); + CHECK_PRECISE( HMath::ln("100"), "4.60517018598809136803598290936872841520220297725755" ); + + // log + CHECK( HMath::log("1e-5"), "-5" ); + CHECK( HMath::log("1e-4"), "-4" ); + CHECK( HMath::log("1e-3"), "-3" ); + CHECK( HMath::log("10"), "1" ); + CHECK( HMath::log("100"), "2" ); + CHECK( HMath::log("1000"), "3" ); + CHECK( HMath::log("10000"), "4" ); + CHECK( HMath::log("1e5"), "5" ); + CHECK( HMath::log("1e6"), "6" ); + CHECK( HMath::log("1e7"), "7" ); + CHECK( HMath::log("1e8"), "8" ); + CHECK( HMath::log("1e9"), "9" ); + CHECK( HMath::log("1e10"), "10" ); + CHECK( HMath::log("-1"), "NaN" ); + CHECK( HMath::log("NaN"), "NaN" ); + + // sin + CHECK( HMath::sin( "0" ), "0" ); + CHECK( HMath::sin( HMath::pi()/4 ), "0.7071067811865475244" ); + CHECK( HMath::sin( HMath::pi()/3 ), "0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()/2 ), "1" ); + CHECK( HMath::sin( HMath::pi()/1 ), "0" ); + CHECK( HMath::sin( HMath::pi()*2/3 ), "0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*4/3 ), "-0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*5/3 ), "-0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*6/3 ), "0"); + CHECK( HMath::sin( HMath::pi()*7/3 ), "0.86602540378443864676"); + CHECK( HMath::sin( HMath::pi()*9/3 ), "0"); + CHECK_PRECISE( HMath::sin("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::sin("0.1"), "0.09983341664682815230681419841062202698991538801798" ); + CHECK_PRECISE( HMath::sin("0.2"), "0.19866933079506121545941262711838975037020672954021" ); + CHECK_PRECISE( HMath::sin("0.3"), "0.29552020666133957510532074568502737367783211174262" ); + CHECK_PRECISE( HMath::sin("0.4"), "0.38941834230865049166631175679570526459306018344396" ); + CHECK_PRECISE( HMath::sin("0.5"), "0.47942553860420300027328793521557138808180336794060" ); + CHECK_PRECISE( HMath::sin("0.6"), "0.56464247339503535720094544565865790710988808499415" ); + CHECK_PRECISE( HMath::sin("0.7"), "0.64421768723769105367261435139872018306581384457369" ); + CHECK_PRECISE( HMath::sin("0.8"), "0.71735609089952276162717461058138536619278523779142" ); + CHECK_PRECISE( HMath::sin("0.9"), "0.78332690962748338846138231571354862314014792572031" ); + CHECK_PRECISE( HMath::sin("1.0"), "0.84147098480789650665250232163029899962256306079837" ); + CHECK_PRECISE( HMath::sin("2.0"), "0.90929742682568169539601986591174484270225497144789" ); + CHECK_PRECISE( HMath::sin("3.0"), "0.14112000805986722210074480280811027984693326425227" ); + CHECK_PRECISE( HMath::sin("4.0"), "-0.75680249530792825137263909451182909413591288733647" ); + CHECK_PRECISE( HMath::sin("5.0"), "-0.95892427466313846889315440615599397335246154396460" ); + + // cos + CHECK( HMath::cos( "0" ), "1" ); + CHECK( HMath::cos( HMath::pi()/4 ), "0.7071067811865475244" ); + CHECK( HMath::cos( HMath::pi()/3 ), "0.5"); + CHECK( HMath::cos( HMath::pi()/2 ), "0" ); + CHECK( HMath::cos( HMath::pi()/1 ), "-1" ); + CHECK( HMath::cos( HMath::pi()*2/3 ), "-0.5"); + CHECK( HMath::cos( HMath::pi()*4/3 ), "-0.5"); + CHECK( HMath::cos( HMath::pi()*5/3 ), "0.5"); + CHECK( HMath::cos( HMath::pi()*6/3 ), "1"); + CHECK( HMath::cos( HMath::pi()*7/3 ), "0.5"); + CHECK( HMath::cos( HMath::pi()*9/3 ), "-1"); + CHECK_PRECISE( HMath::cos("0.0"), "1.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::cos("0.1"), "0.99500416527802576609556198780387029483857622541508" ); + CHECK_PRECISE( HMath::cos("0.2"), "0.98006657784124163112419651674816887739352436080657" ); + CHECK_PRECISE( HMath::cos("0.3"), "0.95533648912560601964231022756804989824421408263204" ); + CHECK_PRECISE( HMath::cos("0.4"), "0.92106099400288508279852673205180161402585956931985" ); + CHECK_PRECISE( HMath::cos("0.5"), "0.87758256189037271611628158260382965199164519710974" ); + CHECK_PRECISE( HMath::cos("0.6"), "0.82533561490967829724095249895537603887809103918847" ); + CHECK_PRECISE( HMath::cos("0.7"), "0.76484218728448842625585999019186490926821055037370" ); + CHECK_PRECISE( HMath::cos("0.8"), "0.69670670934716542092074998164232492610178601370806" ); + CHECK_PRECISE( HMath::cos("0.9"), "0.62160996827066445648471615140713350872176136659124" ); + CHECK_PRECISE( HMath::cos("1.0"), "0.54030230586813971740093660744297660373231042061792" ); + CHECK_PRECISE( HMath::cos("2.0"), "-0.41614683654714238699756822950076218976600077107554" ); + CHECK_PRECISE( HMath::cos("3.0"), "-0.98999249660044545727157279473126130239367909661559" ); + CHECK_PRECISE( HMath::cos("4.0"), "-0.65364362086361191463916818309775038142413359664622" ); + + // tan + CHECK( HMath::tan( HMath::pi()/4 ), "1" ); + CHECK( HMath::tan( HMath::pi()/3 ), "1.73205080756887729353"); + CHECK( HMath::tan( HMath::pi()/1 ), "0" ); + CHECK_PRECISE( HMath::tan("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::tan("0.1"), "0.10033467208545054505808004578111153681900480457644" ); + CHECK_PRECISE( HMath::tan("0.2"), "0.20271003550867248332135827164753448262687566965163" ); + CHECK_PRECISE( HMath::tan("0.3"), "0.30933624960962323303530367969829466725781590680046" ); + CHECK_PRECISE( HMath::tan("0.4"), "0.42279321873816176198163542716529033394198977271569" ); + CHECK_PRECISE( HMath::tan("0.5"), "0.54630248984379051325517946578028538329755172017979" ); + CHECK_PRECISE( HMath::tan("0.6"), "0.68413680834169231707092541746333574524265408075678" ); + CHECK_PRECISE( HMath::tan("0.7"), "0.84228838046307944812813500221293771718722125080420" ); + CHECK_PRECISE( HMath::tan("0.8"), "1.02963855705036401274636117282036528416821960677231" ); + CHECK_PRECISE( HMath::tan("0.9"), "1.26015821755033913713457548539574847783362583439629" ); + CHECK_PRECISE( HMath::tan("1.0"), "1.55740772465490223050697480745836017308725077238152" ); + CHECK_PRECISE( HMath::tan("2.0"), "-2.18503986326151899164330610231368254343201774622766" ); + CHECK_PRECISE( HMath::tan("3.0"), "-0.14254654307427780529563541053391349322609228490180" ); + CHECK_PRECISE( HMath::tan("4.0"), "1.15782128234957758313734241826732392311976276736714" ); + + // atan + CHECK_PRECISE( HMath::atan("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::atan("0.1"), "0.09966865249116202737844611987802059024327832250431" ); + CHECK_PRECISE( HMath::atan("0.2"), "0.19739555984988075837004976519479029344758510378785" ); + CHECK_PRECISE( HMath::atan("0.3"), "0.29145679447786709199560462143289119350316759901207" ); + CHECK_PRECISE( HMath::atan("0.4"), "0.38050637711236488630358791681043310449740571365810" ); + CHECK_PRECISE( HMath::atan("0.5"), "0.46364760900080611621425623146121440202853705428612" ); + CHECK_PRECISE( HMath::atan("0.6"), "0.54041950027058415544357836460859991013514825146259" ); + CHECK_PRECISE( HMath::atan("1.0"), "0.78539816339744830961566084581987572104929234984378" ); + CHECK_PRECISE( HMath::atan("-0.1"), "-0.09966865249116202737844611987802059024327832250431" ); + CHECK_PRECISE( HMath::atan("-0.2"), "-0.19739555984988075837004976519479029344758510378785" ); + CHECK_PRECISE( HMath::atan("-0.3"), "-0.29145679447786709199560462143289119350316759901207" ); + CHECK_PRECISE( HMath::atan("-0.4"), "-0.38050637711236488630358791681043310449740571365810" ); + CHECK_PRECISE( HMath::atan("-0.5"), "-0.46364760900080611621425623146121440202853705428612" ); + CHECK_PRECISE( HMath::atan("-0.6"), "-0.54041950027058415544357836460859991013514825146259" ); + CHECK_PRECISE( HMath::atan("-1.0"), "-0.78539816339744830961566084581987572104929234984378" ); + + // asin + CHECK_PRECISE( HMath::asin("0.0"), "0.00000000000000000000000000000000000000000000000000" ); + CHECK_PRECISE( HMath::asin("0.1"), "0.10016742116155979634552317945269331856867597222963" ); + CHECK_PRECISE( HMath::asin("0.2"), "0.20135792079033079145512555221762341024003808140223" ); + CHECK_PRECISE( HMath::asin("0.3"), "0.30469265401539750797200296122752916695456003170678" ); + CHECK_PRECISE( HMath::asin("0.4"), "0.41151684606748801938473789761733560485570113512703" ); + + // acos + CHECK_PRECISE( HMath::acos("0.1"), "1.47062890563333682288579851218705812352990872745792" ); + CHECK_PRECISE( HMath::acos("0.2"), "1.36943840600456582777619613942212803185854661828532" ); + CHECK_PRECISE( HMath::acos("0.3"), "1.26610367277949911125931873041222227514402466798078" ); + CHECK_PRECISE( HMath::acos("0.4"), "1.15927948072740859984658379402241583724288356456053" ); + + // consistency: tan vs atan + CHECK( HMath::atan("0.10033467208545054505808004578111153681900480457644"), "0.1" ); + CHECK( HMath::atan("0.20271003550867248332135827164753448262687566965163"), "0.2" ); + CHECK( HMath::atan("0.30933624960962323303530367969829466725781590680046"), "0.3" ); + CHECK( HMath::atan("0.42279321873816176198163542716529033394198977271569"), "0.4" ); + CHECK( HMath::atan("0.54630248984379051325517946578028538329755172017979"), "0.5" ); + CHECK( HMath::atan("0.68413680834169231707092541746333574524265408075678"), "0.6" ); + CHECK( HMath::atan("0.84228838046307944812813500221293771718722125080420"), "0.7" ); + CHECK( HMath::atan("1.02963855705036401274636117282036528416821960677231"), "0.8" ); + CHECK( HMath::atan("1.26015821755033913713457548539574847783362583439629"), "0.9" ); + CHECK( HMath::atan("1.55740772465490223050697480745836017308725077238152"), "1" ); + + // consistency: sin vs asin for small angle + CHECK( HMath::asin("0.09983341664682815230681419841062202698991538801798" ), "0.1"); + CHECK( HMath::asin("0.19866933079506121545941262711838975037020672954021" ), "0.2"); + CHECK( HMath::asin("0.29552020666133957510532074568502737367783211174262" ), "0.3"); + CHECK( HMath::asin("0.38941834230865049166631175679570526459306018344396" ), "0.4"); + CHECK( HMath::asin("0.47942553860420300027328793521557138808180336794060" ), "0.5"); + CHECK( HMath::asin("0.56464247339503535720094544565865790710988808499415" ), "0.6"); + CHECK( HMath::asin("0.64421768723769105367261435139872018306581384457369" ), "0.7"); + CHECK( HMath::asin("0.71735609089952276162717461058138536619278523779142" ), "0.8"); + + // sinh + CHECK_PRECISE( HMath::sinh("0.1"), "0.10016675001984402582372938352190502351492091687856" ); + CHECK_PRECISE( HMath::sinh("0.2"), "0.20133600254109398762556824301031737297449484262574" ); + CHECK_PRECISE( HMath::sinh("0.3"), "0.30452029344714261895843526700509522909802423268018" ); + CHECK_PRECISE( HMath::sinh("0.4"), "0.41075232580281550854021001384469810435315092436331" ); + CHECK_PRECISE( HMath::sinh("0.5"), "0.52109530549374736162242562641149155910592898261148" ); + CHECK_PRECISE( HMath::sinh("0.6"), "0.63665358214824127112345437546514831902496342592790" ); + CHECK_PRECISE( HMath::sinh("0.7"), "0.75858370183953350345987464759276815415493761421703" ); + CHECK_PRECISE( HMath::sinh("0.8"), "0.88810598218762300657471757318975698055970959688815" ); + CHECK_PRECISE( HMath::sinh("0.9"), "1.02651672570817527595833616197842235379403446513485" ); + CHECK_PRECISE( HMath::sinh("1.0"), "1.17520119364380145688238185059560081515571798133410" ); + + // cosh + CHECK_PRECISE( HMath::cosh("0.1"), "1.00500416805580359898797844296834164470962627785896" ); + CHECK_PRECISE( HMath::cosh("0.2"), "1.02006675561907584629550375162935679733308609889476" ); + CHECK_PRECISE( HMath::cosh("0.3"), "1.04533851412886048502530904632291210128027546467919" ); + CHECK_PRECISE( HMath::cosh("0.4"), "1.08107237183845480928464293899252417629013184957412" ); + CHECK_PRECISE( HMath::cosh("0.5"), "1.12762596520638078522622516140267201254784711809867" ); + CHECK_PRECISE( HMath::cosh("0.6"), "1.18546521824226770375191329269771619435727538261853" ); + CHECK_PRECISE( HMath::cosh("0.7"), "1.25516900563094301816467474099029711586260477992884" ); + CHECK_PRECISE( HMath::cosh("0.8"), "1.33743494630484459800481995820531977649392453816033" ); + CHECK_PRECISE( HMath::cosh("0.9"), "1.43308638544877438784179040162404834162773784130523" ); + CHECK_PRECISE( HMath::cosh("1.0"), "1.54308063481524377847790562075706168260152911236586" ); + +} + +int test_hmath() +{ + hmath_total_tests = 0; + hmath_failed_tests = 0; + + test_create(); + test_format(); + test_op(); + test_functions(); + + std::cout << hmath_total_tests << " total, "; + std::cout << hmath_failed_tests << " failed\n"; + + HMath::finalize(); + return hmath_failed_tests; +}; + +#endif + +// vim: set et sw=2 ts=8: diff --git a/src/hmath.h b/src/hmath.h new file mode 100644 index 0000000..dae4749 --- /dev/null +++ b/src/hmath.h @@ -0,0 +1,359 @@ +/* HMath: C++ high precision math routines + Copyright (C) 2004 Ariya Hidayat <ariya.hidayat@gmail.com> + + This file was copied from the SpeedCrunch program. Please visit + http://speedcrunch.berlios.de/ for more information. + + 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. If not, write to: + The Free Software Foundation, Inc. + 59 Temple Place, Suite 330 + Boston, MA 02110-1301 USA. + + */ + +#ifndef HMATH_H +#define HMATH_H + +#include <iostream> + +class HMath; + +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; + + /*! + * 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& ); + + /*! + * 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(); + +private: + class Private; + Private* d; +}; + +class QString; + +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, 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 exponential format. + * Note that the returned string must be freed. + */ + static char* formatExp( 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 ); + + static QString formatGenString( const HNumber &n, int prec = -1 ); + + /*! + * 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 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 ); + + /*! + * Rounds n to the specified decimal digits. + */ + static HNumber round( const HNumber&n, int prec = 0 ); + + /*! + * Returns the square root of n. If n is negative, returns NaN. + */ + static HNumber sqrt( 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 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 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 arc hyperbolic sine of x. The result is in radians. + */ + static HNumber asinh( const HNumber & x ); + + /*! + * Returns the hyperbolic cosine of x. Note that x must be in radians. + */ + static HNumber cosh( const HNumber& x ); + + /*! + * Returns the arc hyperbolic cosine of x. The result is in radians. + */ + static HNumber acosh( const HNumber & x ); + + /*! + * Returns the hyperbolic tangent of x. Note that x must be in radians. + */ + static HNumber tanh( const HNumber& x ); + + /*! + * Returns the arc hyperbolic tangent of x. The result is in radians. + */ + static HNumber atanh( const HNumber & x ); + + /*! + * 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, HNumber ); + +#endif // HMATH_H + +// vim: set et sw=2 ts=8: diff --git a/src/lexer.ll b/src/lexer.ll new file mode 100644 index 0000000..8ec48e2 --- /dev/null +++ b/src/lexer.ll @@ -0,0 +1,223 @@ +/* + * lexer.ll - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +%option noyywrap +%{ +#define YY_NO_UNPUT + +#include <kdebug.h> + +#include "node.h" +#include "function.h" +#include "parser_yacc.hpp" +#include "result.h" + +int yyCurTokenPos; +int yyThisTokenLength; + +int yyparse(void); +%} + +DIGITS [0-9]+ +HEX [0-9A-Fa-f]+ +%% + + /* Always skip whitespace */ +[ \t]* { yyCurTokenPos += yyThisTokenLength; yyThisTokenLength = yyleng; } + + /* Power operator */ +"**" { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 2; + return POWER; +} + +"^" { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 1; + return POWER; +} + +[sS][eE][tT] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 3; + return SET; +} + +[rR][eE][mM][oO][vV][eE] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 6; + return REMOVE; +} + +[dD][eE][rR][iI][vV] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 5; + return DERIV; +} + + /* Read numbers of the form with at least the decimal point and trailing + * digits, such as .32, -234.45, .0, etc. Numbers are only read in the BEGIN + * state. + */ +{DIGITS}*([\.,]{DIGITS}+)(e[-+]?{DIGITS}+)? { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + + /* Read Hex */ +0x({HEX}+)? { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + + /* Read numbers with at least the integral part, such as +4234, -34e8, etc. + * Numbers are only read in the BEGIN state. + */ +{DIGITS}+([\.,]{DIGITS}*)?(e[-+]?{DIGITS}+)? { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + +[nN][aA][nN] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + +[iI][nN][fF] { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + return NUM; +} + + /* This detects textual input, and if it isn't pre-declared by the parser (in + * other words, if it isn't a function), then it is returned as an identifier. + */ +[a-zA-Z_][a-zA-Z_0-9]* { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = yyleng; + + if(FunctionManager::instance()->isFunction(yytext)) + return FN; + else { + return ID; + } +} + + /* All other characters are returned as-is to the parser, who can accept or + * reject it as needed. + */ +. { + yyCurTokenPos += yyThisTokenLength; + yyThisTokenLength = 1; + return *yytext; +} + +%% + +class Lexer::Private +{ +public: + YY_BUFFER_STATE buffer; + int lastToken, thisToken; + int lastPos, thisPos; + QString lastTokenData, thisTokenData; +}; + +/* Declared in function.h, implemented here in lexer.l since this is where + * all the yy_*() functions and types are defined. + */ +Lexer::Lexer(const QString &expr) : + m_private(new Private) +{ + const char *exprString = expr.latin1(); + + yyCurTokenPos = 0; + yyThisTokenLength = 0; + + m_private->buffer = yy_scan_string(exprString ? exprString : ""); + m_private->lastToken = -1; + m_private->lastPos = -1; + + m_private->thisToken = yylex(); + m_private->thisTokenData = QString(yytext); + + if(yyCurTokenPos != 0) + { + kdError() << "yyCurTokenPos should be 0!!\n"; + } + + m_private->thisPos = yyCurTokenPos; +} + +Lexer::~Lexer() +{ + yy_delete_buffer(m_private->buffer); + delete m_private; +} + +bool Lexer::hasNext() const +{ + return m_private->thisToken > 0; +} + +int Lexer::nextType() +{ + m_private->lastTokenData = m_private->thisTokenData; + m_private->lastPos = m_private->thisPos; + m_private->lastToken = m_private->thisToken; + + m_private->thisToken = yylex(); + m_private->thisTokenData = QString(yytext); + m_private->thisPos = yyCurTokenPos; + + return m_private->lastToken; +} + +QString Lexer::tokenValue() const +{ + return m_private->lastTokenData; +} + +int Lexer::tokenPos() const +{ + return m_private->lastPos; +} + +/* Declared in function.h, implemented here in lexer.l since this is where + * all the yy_*() functions and types are defined. + */ +Abakus::number_t parseString(const char *str) +{ + YY_BUFFER_STATE buffer = yy_scan_string(str); + + yyCurTokenPos = 0; + yyThisTokenLength = 0; + + yyparse(); + yy_delete_buffer(buffer); + + if(Result::lastResult()->type() != Result::Value) + return Abakus::number_t(); + + return Result::lastResult()->result()->value(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 0000000..8300dbb --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,825 @@ +/* + * mainwindow.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include "mainwindow.h" +#include "abakuscommon.h" + +#include <kaccel.h> +#include <kmenubar.h> +#include <kaction.h> +#include <kstdaction.h> +#include <kshortcut.h> +#include <kdebug.h> +#include <kconfig.h> +#include <kglobal.h> +#include <kconfigbase.h> +#include <kactionclasses.h> +#include <kinputdialog.h> + +#include <qlayout.h> +#include <qvbox.h> +#include <qhbox.h> +#include <qradiobutton.h> +#include <qbuttongroup.h> +#include <qsplitter.h> + +#include "editor.h" +#include "evaluator.h" +#include "function.h" +#include "resultlistview.h" +#include "resultlistviewtext.h" +#include "valuemanager.h" +#include "node.h" +#include "rpnmuncher.h" +#include "dcopIface.h" +#include "abakuslistview.h" +#include "result.h" + +MainWindow::MainWindow() : KMainWindow(0, "abakus-mainwindow"), m_popup(0), m_insert(false) +{ + m_mainSplitter = new QSplitter(this); + QWidget *box = new QWidget(m_mainSplitter); + QVBoxLayout *layout = new QVBoxLayout(box); + m_layout = layout; + layout->setSpacing(6); + layout->setMargin(6); + + QWidget *configBox = new QWidget(box); + layout->addWidget(configBox); + + QHBoxLayout *configLayout = new QHBoxLayout(configBox); + + configLayout->addWidget(new QWidget(configBox)); + + QLabel *label = new QLabel(i18n("History: "), configBox); + label->setAlignment(AlignCenter); + configLayout->addWidget(label); + + QButtonGroup *buttonGroup = new QButtonGroup(0); + + QWidget *buttonGroupBox = new QWidget(configBox); + QHBoxLayout *buttonGroupLayout = new QHBoxLayout(buttonGroupBox); + buttonGroupLayout->addStretch(0); + + configLayout->addWidget(buttonGroupBox); + + m_degrees = new QRadioButton(i18n("&Degrees"), buttonGroupBox); + buttonGroup->insert(m_degrees); + buttonGroupLayout->addWidget(m_degrees); + slotDegrees(); + connect(m_degrees, SIGNAL(clicked()), SLOT(slotDegrees())); + + m_radians = new QRadioButton(i18n("&Radians"), buttonGroupBox); + buttonGroup->insert(m_radians); + buttonGroupLayout->addWidget(m_radians); + connect(m_radians, SIGNAL(clicked()), SLOT(slotRadians())); + + m_history = new QVBox(box); + layout->addWidget(m_history); + m_history->setSpacing(6); + m_history->setMargin(0); + + m_result = new ResultListView(m_history); + m_result->setSelectionMode(QListView::NoSelection); + m_result->setHScrollBarMode(ResultListView::AlwaysOff); + connect(m_result, SIGNAL(signalEntrySelected(const QString &)), + SLOT(slotEntrySelected(const QString &))); + connect(m_result, SIGNAL(signalResultSelected(const QString &)), + SLOT(slotResultSelected(const QString &))); + + m_history->setStretchFactor(m_result, 1); + layout->setStretchFactor(m_history, 1); + + QHBox *editBox = new QHBox(box); + layout->addWidget(editBox); + editBox->setSpacing(6); + + m_edit = new Editor(editBox); + m_edit->setFocus(); + editBox->setStretchFactor(m_edit, 1); + + KPushButton *evalButton = new KPushButton(i18n("&Evaluate"), editBox); + + connect(evalButton, SIGNAL(clicked()), SLOT(slotEvaluate())); + + connect(m_edit, SIGNAL(returnPressed()), SLOT(slotReturnPressed())); + connect(m_edit, SIGNAL(textChanged()), SLOT(slotTextChanged())); + + m_listSplitter = new QSplitter(Vertical, m_mainSplitter); + m_fnList = new FunctionListView(m_listSplitter); + m_fnList->addColumn("Functions"); + m_fnList->addColumn("Value"); + + m_varList = new VariableListView(m_listSplitter); + m_varList->addColumn("Variables"); + m_varList->addColumn("Value"); + + connect(FunctionManager::instance(), SIGNAL(signalFunctionAdded(const QString &)), + this, SLOT(slotNewFunction(const QString &))); + connect(FunctionManager::instance(), SIGNAL(signalFunctionRemoved(const QString &)), + this, SLOT(slotRemoveFunction(const QString &))); + + connect(ValueManager::instance(), SIGNAL(signalValueAdded(const QString &, Abakus::number_t)), + this, SLOT(slotNewValue(const QString &, Abakus::number_t))); + connect(ValueManager::instance(), SIGNAL(signalValueChanged(const QString &, Abakus::number_t)), + this, SLOT(slotChangeValue(const QString &, Abakus::number_t))); + connect(ValueManager::instance(), SIGNAL(signalValueRemoved(const QString &)), + this, SLOT(slotRemoveValue(const QString &))); + + setupLayout(); + + setCentralWidget(m_mainSplitter); + +#if KDE_IS_VERSION(3,4,89) + setupGUI(QSize(450, 400), Keys | StatusBar | Save | Create); +#else + setupGUI(Keys | StatusBar | Save | Create); +#endif + + m_dcopInterface = new AbakusIface(); +} + +bool MainWindow::inRPNMode() const +{ + return action<KToggleAction>("toggleExpressionMode")->isChecked(); +} + +bool MainWindow::eventFilter(QObject *o, QEvent *e) +{ + return KMainWindow::eventFilter(o, e); +} + +bool MainWindow::queryExit() +{ + saveConfig(); + return KMainWindow::queryExit(); +} + +void MainWindow::contextMenuEvent(QContextMenuEvent *e) +{ + static KPopupMenu *popup = 0; + + if(!popup) { + popup = new KPopupMenu(this); + action("options_show_menubar")->plug(popup); + } + + if(!action<KToggleAction>("options_show_menubar")->isChecked()) + popup->popup(e->globalPos()); +} + +void MainWindow::polish() +{ + KMainWindow::polish(); + loadConfig(); +} + +void MainWindow::slotReturnPressed() +{ + QString text = m_edit->text(); + + text.replace("\n", ""); + + m_edit->appendHistory(text); + + // Item to insert after + ResultListViewText *after = m_result->lastItem(); + + // Expand $foo references. + QString str = interpolateExpression(text, after); + + QString resultVal; + ResultListViewText *item; + + if(str.isNull()) + return; // Error already has been posted + + m_insert = false; // Assume we failed + + if(inRPNMode()) { + // We're in RPN mode. + Abakus::number_t result = RPNParser::rpnParseString(str); + + if(!RPNParser::wasError()) { + resultVal = result.toString(); + ValueManager::instance()->setValue("ans", result); + m_insert = true; + } + else { + m_insert = false; + resultVal = i18n("Error: %1").arg(RPNParser::errorString()); + } + + // Skip creating list view items if in compact mode. + if(!m_history->isShown()) { + m_edit->setText(resultVal); + QTimer::singleShot(0, m_edit, SLOT(selectAll())); + + return; + } + + item = new ResultListViewText(m_result, str, resultVal, after, false); + } + else { + + // Check to see if it's just a function name, if so, add (ans). + if(FunctionManager::instance()->isFunction(str)) + str += " ans"; + + // Add right parentheses as needed to balance out the expression. + int parenLevel = getParenthesesLevel(str); + for(int i = 0; i < parenLevel; ++i) + str += ')'; + + Abakus::number_t result = parseString(str.latin1()); + + bool compact = !m_history->isShown(); + + switch(Result::lastResult()->type()) { + case Result::Value: + resultVal = result.toString(); + + ValueManager::instance()->setValue("ans", result); + if(!compact) + item = new ResultListViewText(m_result, str, result, after, false); + + m_insert = true; + break; + + case Result::Null: // OK, no result to speak of + resultVal = "OK"; + if(!compact) + item = new ResultListViewText(m_result, str, resultVal, after, true); + break; + + default: + resultVal = Result::lastResult()->message(); + if(!compact) + item = new ResultListViewText(m_result, str, resultVal, after, true); + } + + // Skip creating list view items if in compact mode. + if(compact) { + m_edit->setText(resultVal); + QTimer::singleShot(0, m_edit, SLOT(selectAll())); + + return; + } + } + + m_edit->setText(text); + + m_result->setCurrentItem(item); + m_result->ensureItemVisible(item); + + QTimer::singleShot(0, m_edit, SLOT(selectAll())); +} + +void MainWindow::slotTextChanged() +{ + QString str = m_edit->text(); + + if(str.length() == 1 && m_insert) { + m_insert = false; + + // Don't do anything if in RPN Mode. + if(inRPNMode()) + return; + + if(str.find(QRegExp("^[-+*/^]")) != -1) { + m_edit->setText("ans " + str + " "); + m_edit->moveCursor(QTextEdit::MoveEnd, false); + } + } +} + +void MainWindow::slotEvaluate() +{ + slotReturnPressed(); +} + +void MainWindow::slotUpdateSize() +{ + if(m_newSize != QSize(0, 0)) + resize(m_newSize); + else + resize(width(), minimumSize().height()); +} + +void MainWindow::slotDegrees() +{ + setTrigMode(Abakus::Degrees); + m_degrees->setChecked(true); + if(action("setDegreesMode")) + action<KToggleAction>("setDegreesMode")->setChecked(true); +} + +void MainWindow::slotRadians() +{ + setTrigMode(Abakus::Radians); + m_radians->setChecked(true); + if(action("setRadiansMode")) + action<KToggleAction>("setRadiansMode")->setChecked(true); +} + +int MainWindow::getParenthesesLevel(const QString &str) +{ + int level = 0; + + for(unsigned i = 0; i < str.length(); ++i) + if(str[i] == '(') + ++level; + else if(str[i] == ')') + --level; + + return level; +} + +void MainWindow::loadConfig() +{ + { + KConfigGroup config(KGlobal::config(), "Settings"); + + QString mode = config.readEntry("Trigonometric mode", "Degrees"); + if(mode == "Degrees") { + setTrigMode(Abakus::Degrees); + m_degrees->setChecked(true); + } + else { + setTrigMode(Abakus::Radians); + m_radians->setChecked(true); + } + + bool useRPN = config.readBoolEntry("Use RPN Mode", false); + action<KToggleAction>("toggleExpressionMode")->setChecked(useRPN); + + int precision = config.readNumEntry("Decimal Precision", -1); + if(precision < -1 || precision > 75) + precision = -1; + + Abakus::m_prec = precision; + selectCorrectPrecisionAction(); + } + + { + KConfigGroup config(KGlobal::config(), "Variables"); + + QStringList list = config.readListEntry("Saved Variables"); + for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { + QStringList values = QStringList::split('=', *it); + if(values.count() != 2) { + kdWarning() << "Your configuration file has somehow been corrupted!\n"; + continue; + } + + ValueManager::instance()->setValue(values[0], Abakus::number_t(values[1].latin1())); + } + } + + { + KConfigGroup config(KGlobal::config(), "GUI"); + + bool showHistory = config.readBoolEntry("ShowHistory", true); + action<KToggleAction>("toggleHistoryList")->setChecked(showHistory); + m_history->setShown(showHistory); + + bool showFunctions = config.readBoolEntry("ShowFunctions", true); + action<KToggleAction>("toggleFunctionList")->setChecked(showFunctions); + m_fnList->setShown(showFunctions); + + bool showVariables = config.readBoolEntry("ShowVariables", true); + action<KToggleAction>("toggleVariableList")->setChecked(showVariables); + m_varList->setShown(showVariables); + + bool compactMode = config.readBoolEntry("InCompactMode", false); + compactMode = compactMode || !showHistory; + action<KToggleAction>("toggleCompactMode")->setChecked(compactMode); + + if(compactMode) + QTimer::singleShot(0, this, SLOT(slotToggleCompactMode())); + } + + { + KConfigGroup config(KGlobal::config(), "Functions"); + + QStringList fnList = config.readListEntry("FunctionList"); + for(QStringList::ConstIterator it = fnList.begin(); it != fnList.end(); ++it) + parseString(*it); // Run the function definitions through the parser + } + + populateListViews(); +} + +void MainWindow::saveConfig() +{ + { + KConfigGroup config(KGlobal::config(), "Settings"); + + config.writeEntry("Trigonometric mode", + trigMode() == Abakus::Degrees + ? "Degrees" + : "Radians"); + + config.writeEntry("Use RPN Mode", inRPNMode()); + config.writeEntry("Decimal Precision", Abakus::m_prec); + } + + { + KConfigGroup config(KGlobal::config(), "Variables"); + + QStringList list; + QStringList values = ValueManager::instance()->valueNames(); + QStringList::ConstIterator it = values.begin(); + + // Set precision to max for most accuracy + Abakus::m_prec = 75; + + for(; it != values.end(); ++it) { + if(ValueManager::instance()->isValueReadOnly(*it)) + continue; + + list += QString("%1=%2") + .arg(*it) + .arg(ValueManager::instance()->value(*it).toString()); + } + + config.writeEntry("Saved Variables", list); + } + + { + KConfigGroup config(KGlobal::config(), "GUI"); + bool inCompactMode = action<KToggleAction>("toggleCompactMode")->isChecked(); + + config.writeEntry("InCompactMode", inCompactMode); + + if(!inCompactMode) { + config.writeEntry("ShowHistory", m_history->isShown()); + config.writeEntry("ShowFunctions", m_fnList->isShown()); + config.writeEntry("ShowVariables", m_varList->isShown()); + } + else { + config.writeEntry("ShowHistory", m_wasHistoryShown); + config.writeEntry("ShowFunctions", m_wasFnShown); + config.writeEntry("ShowVariables", m_wasVarShown); + } + } + + { + KConfigGroup config(KGlobal::config(), "Functions"); + + FunctionManager *manager = FunctionManager::instance(); + + QStringList userFunctions = manager->functionList(FunctionManager::UserDefined); + QStringList saveList; + + for(QStringList::ConstIterator it = userFunctions.begin(); it != userFunctions.end(); ++it) { + UnaryFunction *fn = dynamic_cast<UnaryFunction *>(manager->function(*it)->userFn->fn); + QString var = manager->function(*it)->userFn->varName; + QString expr = fn->operand()->infixString(); + + saveList += QString("set %1(%2) = %3").arg(*it).arg(var).arg(expr); + } + + config.writeEntry("FunctionList", saveList); + } +} + +void MainWindow::setupLayout() +{ + KActionCollection *ac = actionCollection(); + + KStdAction::quit(kapp, SLOT(quit()), ac); + KStdAction::showMenubar(this, SLOT(slotToggleMenuBar()), ac); + + KToggleAction *ta = new KToggleAction(i18n("&Degrees"), SHIFT + ALT + Key_D, this, SLOT(slotDegrees()), ac, "setDegreesMode"); + ta->setExclusiveGroup("TrigMode"); + ta->setChecked(trigMode() == Abakus::Degrees); + + ta = new KToggleAction(i18n("&Radians"), SHIFT + ALT + Key_R, this, SLOT(slotRadians()), ac, "setRadiansMode"); + ta->setExclusiveGroup("TrigMode"); + ta->setChecked(trigMode() == Abakus::Radians); + + ta = new KToggleAction(i18n("Show &History List"), SHIFT + ALT + Key_H, this, SLOT(slotToggleHistoryList()), ac, "toggleHistoryList"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("Show &Variables"), SHIFT + ALT + Key_V, this, SLOT(slotToggleVariableList()), ac, "toggleVariableList"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("Show &Functions"), SHIFT + ALT + Key_F, this, SLOT(slotToggleFunctionList()), ac, "toggleFunctionList"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("Activate &Compact Mode"), SHIFT + ALT + Key_C, this, SLOT(slotToggleCompactMode()), ac, "toggleCompactMode"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("Use R&PN Mode"), SHIFT + ALT + Key_P, this, SLOT(slotToggleExpressionMode()), ac, "toggleExpressionMode"); + ta->setChecked(false); + + // Precision actions. + ta = new KToggleAction(i18n("&Automatic Precision"), 0, this, SLOT(slotPrecisionAuto()), ac, "precisionAuto"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(true); + + ta = new KToggleAction(i18n("&3 Decimal Digits"), 0, this, SLOT(slotPrecision3()), ac, "precision3"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("&8 Decimal Digits"), 0, this, SLOT(slotPrecision8()), ac, "precision8"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("&15 Decimal Digits"), 0, this, SLOT(slotPrecision15()), ac, "precision15"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("&50 Decimal Digits"), 0, this, SLOT(slotPrecision50()), ac, "precision50"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + ta = new KToggleAction(i18n("C&ustom Precision..."), 0, this, SLOT(slotPrecisionCustom()), ac, "precisionCustom"); + ta->setExclusiveGroup("Precision"); + ta->setChecked(false); + + new KAction(i18n("Clear &History"), "editclear", SHIFT + ALT + Key_L, m_result, SLOT(clear()), ac, "clearHistory"); + + new KAction(i18n("Select Editor"), "goto", Key_F6, m_edit, SLOT(setFocus()), ac, "select_edit"); +} + +void MainWindow::populateListViews() +{ + QStringList values = ValueManager::instance()->valueNames(); + + Abakus::number_t value = ValueManager::instance()->value("pi"); + new ValueListViewItem(m_varList, "pi", value); + + value = ValueManager::instance()->value("e"); + new ValueListViewItem(m_varList, "e", value); +} + +KAction *MainWindow::action(const char *key) const +{ + return actionCollection()->action(key); +} + +void MainWindow::slotEntrySelected(const QString &text) +{ + m_edit->setText(text); + m_edit->moveCursor(QTextEdit::MoveEnd, false); +} + +void MainWindow::slotResultSelected(const QString &text) +{ + m_edit->insert(text); +} + +void MainWindow::slotToggleMenuBar() +{ + menuBar()->setShown(!menuBar()->isShown()); +} + +void MainWindow::slotToggleFunctionList() +{ + bool show = action<KToggleAction>("toggleFunctionList")->isChecked(); + m_fnList->setShown(show); + + if(!m_history->isShown()) { + m_history->setShown(true); + action<KToggleAction>("toggleHistoryList")->setChecked(true); + slotToggleHistoryList(); + } + + action<KToggleAction>("toggleCompactMode")->setChecked(false); +} + +void MainWindow::slotToggleVariableList() +{ + bool show = action<KToggleAction>("toggleVariableList")->isChecked(); + m_varList->setShown(show); + + if(!m_history->isShown()) { + m_history->setShown(true); + action<KToggleAction>("toggleHistoryList")->setChecked(true); + slotToggleHistoryList(); + } + + action<KToggleAction>("toggleCompactMode")->setChecked(false); +} + +void MainWindow::slotToggleHistoryList() +{ + bool show = action<KToggleAction>("toggleHistoryList")->isChecked(); + m_history->setShown(show); + + action<KToggleAction>("toggleCompactMode")->setChecked(false); +} + +void MainWindow::slotNewFunction(const QString &name) +{ + UserFunction *userFn = FunctionManager::instance()->function(name)->userFn; + UnaryFunction *fn = dynamic_cast<UnaryFunction *>(userFn->fn); + QString fnName = QString("%1(%2)").arg(name, userFn->varName); + QString expr = fn->operand()->infixString(); + + new KListViewItem(m_fnList, fnName, expr); +} + +void MainWindow::slotRemoveFunction(const QString &name) +{ + UserFunction *userFn = FunctionManager::instance()->function(name)->userFn; + QString fnName = QString("%1(%2)").arg(name, userFn->varName); + + QListViewItem *item = 0; + while((item = m_fnList->findItem(fnName, 0)) != 0) + delete item; +} + +void MainWindow::slotNewValue(const QString &name, Abakus::number_t value) +{ + new ValueListViewItem(m_varList, name, value); +} + +void MainWindow::slotChangeValue(const QString &name, Abakus::number_t value) +{ + ValueListViewItem *item = static_cast<ValueListViewItem *>(m_varList->findItem(name, 0)); + + if(item) + item->valueChanged(value); +} + +void MainWindow::slotRemoveValue(const QString &name) +{ + delete m_varList->findItem(name, 0); +} + +void MainWindow::slotToggleCompactMode() +{ + if(action<KToggleAction>("toggleCompactMode")->isChecked()) { + m_wasFnShown = m_fnList->isShown(); + m_wasVarShown = m_varList->isShown(); + m_wasHistoryShown = m_history->isShown(); + + m_fnList->setShown(false); + m_varList->setShown(false); + m_history->setShown(false); + + action<KToggleAction>("toggleFunctionList")->setChecked(false); + action<KToggleAction>("toggleVariableList")->setChecked(false); + action<KToggleAction>("toggleHistoryList")->setChecked(false); + + m_oldSize = size(); + m_newSize = QSize(0, 0); + QTimer::singleShot(0, this, SLOT(slotUpdateSize())); + } + else { + m_fnList->setShown(m_wasFnShown); + m_varList->setShown(m_wasVarShown); + m_history->setShown(m_wasHistoryShown); + + action<KToggleAction>("toggleFunctionList")->setChecked(m_wasFnShown); + action<KToggleAction>("toggleVariableList")->setChecked(m_wasVarShown); + action<KToggleAction>("toggleHistoryList")->setChecked(m_wasHistoryShown); + + m_newSize = m_oldSize; + QTimer::singleShot(0, this, SLOT(slotUpdateSize())); + } +} + +void MainWindow::slotToggleExpressionMode() +{ +} + +QString MainWindow::interpolateExpression(const QString &text, ResultListViewText *after) +{ + QString str(text); + QRegExp stackRE("\\$\\d+"); + int pos; + + while((pos = stackRE.search(str)) != -1) { + QString stackStr = stackRE.cap(); + Abakus::number_t value; + unsigned numPos = stackStr.mid(1).toUInt(); + + if(!m_result->getStackValue(numPos, value)) { + new ResultListViewText(m_result, text, i18n("Marker %1 isn't set").arg(stackStr), after, true); + return QString::null; + } + + str.replace(pos, stackStr.length(), value.toString()); + } + + return str; +} + +void MainWindow::slotPrecisionAuto() +{ + Abakus::m_prec = -1; + redrawResults(); +} + +void MainWindow::slotPrecision3() +{ + Abakus::m_prec = 3; + redrawResults(); +} + +void MainWindow::slotPrecision8() +{ + Abakus::m_prec = 8; + redrawResults(); +} + +void MainWindow::slotPrecision15() +{ + Abakus::m_prec = 15; + redrawResults(); +} + +void MainWindow::slotPrecision50() +{ + Abakus::m_prec = 50; + redrawResults(); +} + +void MainWindow::slotPrecisionCustom() +{ + bool ok = false; + int precision = KInputDialog::getInteger(i18n("Select number of decimal digits to display"), + i18n("Decimal precision:"), Abakus::m_prec, 0, 75, 1, &ok, this); + + if(ok) { + Abakus::m_prec = precision; + redrawResults(); + } + + selectCorrectPrecisionAction(); +} + +void MainWindow::redrawResults() +{ + QListViewItemIterator it(m_result); + + while(it.current()) { + static_cast<ResultListViewText *>(it.current())->precisionChanged(); + ++it; + } + + it = QListViewItemIterator (m_varList); + + while(it.current()) { + static_cast<ValueListViewItem *>(it.current())->valueChanged(); + ++it; + } + + // Because of the way we implemented the menu, it is possible to deselect + // every possibility, so make sure we have at least one selected. + selectCorrectPrecisionAction(); +} + +// This function selects the correct precision action based on the value of +// Abakus::m_prec. Useful for loading settings, or for custom precision +// selection. +void MainWindow::selectCorrectPrecisionAction() +{ + switch(Abakus::m_prec) { + case 3: + action<KToggleAction>("precision3")->setChecked(true); + break; + + case 8: + action<KToggleAction>("precision8")->setChecked(true); + break; + + case 15: + action<KToggleAction>("precision15")->setChecked(true); + break; + + case 50: + action<KToggleAction>("precision50")->setChecked(true); + break; + + case -1: + action<KToggleAction>("precisionAuto")->setChecked(true); + break; + + default: + action<KToggleAction>("precisionCustom")->setChecked(true); + } +} + +#include "mainwindow.moc" + +// vim: set et ts=8 sw=4: diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 0000000..ee24cdd --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,135 @@ +#ifndef ABAKUS_MAINWINDOW_H +#define ABAKUS_MAINWINDOW_H +/* + * mainwindow.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <kmainwindow.h> + +#include "numerictypes.h" + +class QPoint; +class QVBox; +class QCheckBox; +class QRadioButton; +class QBoxLayout; +class QListViewItem; +class QSplitter; +class QTimer; + +//class KComboBox; +class Editor; +class KPopupMenu; +class KAction; +class KListView; +class ResultListView; +class ResultListViewText; + +class AbakusIface; + +// Main window class, handles events and layout and stuff +class MainWindow : public KMainWindow +{ + Q_OBJECT + public: + MainWindow(); + + bool inRPNMode() const; + + protected: + virtual void contextMenuEvent(QContextMenuEvent *e); + virtual bool eventFilter(QObject *o, QEvent *e); + virtual bool queryExit(); + virtual void polish(); + + private slots: + void slotReturnPressed(); + void slotTextChanged(); + void slotEvaluate(); + + void slotPrecisionAuto(); + void slotPrecision3(); + void slotPrecision8(); + void slotPrecision15(); + void slotPrecision50(); + void slotPrecisionCustom(); + + void slotUpdateSize(); + + void slotDegrees(); + void slotRadians(); + + void slotEntrySelected(const QString &text); + void slotResultSelected(const QString &text); + + void slotToggleMenuBar(); + void slotToggleFunctionList(); + void slotToggleVariableList(); + void slotToggleHistoryList(); + void slotToggleCompactMode(); + void slotToggleExpressionMode(); + + void slotNewValue(const QString &name, Abakus::number_t value); + void slotChangeValue(const QString &name, Abakus::number_t value); + void slotRemoveValue(const QString &name); + + void slotNewFunction(const QString &name); + void slotRemoveFunction(const QString &name); + + private: + int getParenthesesLevel(const QString &str); + + void redrawResults(); + void selectCorrectPrecisionAction(); + + void loadConfig(); + void saveConfig(); + void setupLayout(); + void populateListViews(); + QString interpolateExpression(const QString &text, ResultListViewText *after); + + // Donated via JuK + KAction *action(const char *key) const; + + template <class T> T *action(const char *key) const + { + return dynamic_cast<T *>(action(key)); + } + + private: + QVBox *m_history; + QRadioButton *m_degrees; + QRadioButton *m_radians; + Editor *m_edit; + KPopupMenu *m_popup; + ResultListView *m_result; + QString m_lastError; + QBoxLayout *m_layout; + KListView *m_fnList, *m_varList; + QSplitter *m_mainSplitter, *m_listSplitter; + QSize m_newSize, m_oldSize; + + AbakusIface *m_dcopInterface; + + bool m_wasFnShown, m_wasVarShown, m_wasHistoryShown; + bool m_compactMode; + + bool m_insert; +}; + +#endif diff --git a/src/node.cpp b/src/node.cpp new file mode 100644 index 0000000..bb49676 --- /dev/null +++ b/src/node.cpp @@ -0,0 +1,419 @@ +/* + * node.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <kdebug.h> + +#include <math.h> + +#include "node.h" +#include "valuemanager.h" +#include "function.h" + +void Node::deleteNode(Node *node) +{ + if(dynamic_cast<BaseFunction *>(node) != 0) + delete node; +} + +BaseFunction::BaseFunction(const char *name) : + m_name(name) +{ +} + +const Function *BaseFunction::function() const +{ + return FunctionManager::instance()->function(m_name); +} + +UnaryFunction::UnaryFunction(const char *name, Node *operand) : + BaseFunction(name), m_node(operand) +{ +} + +UnaryFunction::~UnaryFunction() +{ + deleteNode(m_node); + m_node = 0; +} + +void UnaryFunction::setOperand(Node *operand) +{ + m_node = operand; +} + +void UnaryFunction::applyMap(NodeFunctor &fn) const +{ + fn(operand()); + fn(this); +} + +QString UnaryFunction::infixString() const +{ + return QString("%1(%2)").arg(name(), operand()->infixString()); +} + +BuiltinFunction::BuiltinFunction(const char *name, Node *operand) : + UnaryFunction(name, operand) +{ +} + +Abakus::number_t BuiltinFunction::value() const +{ + if(function() && operand()) { + Abakus::number_t fnValue = operand()->value(); + return evaluateFunction(function(), fnValue); + } + + return Abakus::number_t(0); +} + +Abakus::number_t BuiltinFunction::derivative() const +{ + Abakus::number_t du = operand()->derivative(); + Abakus::number_t value = operand()->value(); + Abakus::number_t one(1), zero(0); + + if(du == zero) + return du; + + // In case these functions get added later, these derivatives may + // be useful: + // d/dx(asinh u) = (du/dx * 1 / sqrt(x^2 + 1)) + // d/dx(acosh u) = (du/dx * 1 / sqrt(x^2 - 1)) + // d/dx(atanh u) = (du/dx * 1 / (1 - x^2)) + + // This is very unfortunate duplication. + if(name() == "sin") + return value.cos() * du; + else if(name() == "cos") + return -value.sin() * du; + else if(name() == "tan") { + Abakus::number_t cosResult; + + cosResult = value.cos(); + cosResult = cosResult * cosResult; + return one / cosResult; + } + else if(name() == "asinh") { + value = value * value + one; + return du / value.sqrt(); + } + else if(name() == "acosh") { + value = value * value - one; + return du / value.sqrt(); + } + else if(name() == "atanh") { + value = one - value * value; + return du / value; + } + else if(name() == "sinh") { + return du * value.cosh(); + } + else if(name() == "cosh") { + return du * value.sinh(); // Yes the sign is correct. + } + else if(name() == "tanh") { + Abakus::number_t tanh = value.tanh(); + + return du * (one - tanh * tanh); + } + else if(name() == "atan") { + return one * du / (one + value * value); + } + else if(name() == "acos") { + // Same as asin but with inverted sign. + return -(one / (value * value - one).sqrt() * du); + } + else if(name() == "asin") { + return one / (value * value - one).sqrt() * du; + } + else if(name() == "ln") { + return du / value; + } + else if(name() == "exp") { + return du * value.exp(); + } + else if(name() == "log") { + return du / value / Abakus::number_t(10).ln(); + } + else if(name() == "sqrt") { + Abakus::number_t half("0.5"); + return half * value.pow(-half) * du; + } + else if(name() == "abs") { + return (value / value.abs()) * du; + } + + // Approximate it. + + Abakus::number_t epsilon("1e-15"); + Abakus::number_t fxh = evaluateFunction(function(), value + epsilon); + Abakus::number_t fx = evaluateFunction(function(), value); + return (fxh - fx) / epsilon; +} + +DerivativeFunction::~DerivativeFunction() +{ + deleteNode(m_operand); + m_operand = 0; +} + +Abakus::number_t DerivativeFunction::value() const +{ + ValueManager *vm = ValueManager::instance(); + Abakus::number_t result; + + if(vm->isValueSet("x")) { + Abakus::number_t oldValue = vm->value("x"); + + vm->setValue("x", m_where->value()); + result = m_operand->derivative(); + vm->setValue("x", oldValue); + } + else { + vm->setValue("x", m_where->value()); + result = m_operand->derivative(); + vm->removeValue("x"); + } + + return result; +} + +Abakus::number_t DerivativeFunction::derivative() const +{ + kdError() << k_funcinfo << endl; + kdError() << "This function is never supposed to be called!\n"; + + return m_operand->derivative(); +} + +void DerivativeFunction::applyMap(NodeFunctor &fn) const +{ + fn(m_operand); + fn(this); +} + +QString DerivativeFunction::infixString() const +{ + return QString("deriv(%1, %2)").arg(m_operand->infixString(), m_where->infixString()); +} + +UnaryOperator::UnaryOperator(Type type, Node *operand) + : m_type(type), m_node(operand) +{ +} + +UnaryOperator::~UnaryOperator() +{ + deleteNode(m_node); + m_node = 0; +} + +void UnaryOperator::applyMap(NodeFunctor &fn) const +{ + fn(operand()); + fn(this); +} + +QString UnaryOperator::infixString() const +{ + if(dynamic_cast<BinaryOperator *>(operand())) + return QString("-(%1)").arg(operand()->infixString()); + + return QString("-%1").arg(operand()->infixString()); +} + +Abakus::number_t UnaryOperator::derivative() const +{ + switch(type()) { + case Negation: + return -(operand()->derivative()); + + default: + kdError() << "Impossible case encountered for UnaryOperator!\n"; + return Abakus::number_t(0); + } +} + +Abakus::number_t UnaryOperator::value() const +{ + switch(type()) { + case Negation: + return -(operand()->value()); + + default: + kdError() << "Impossible case encountered for UnaryOperator!\n"; + return Abakus::number_t(0); + } +} + +BinaryOperator::BinaryOperator(Type type, Node *left, Node *right) : + m_type(type), m_left(left), m_right(right) +{ +} + +BinaryOperator::~BinaryOperator() +{ + deleteNode(m_left); + m_left = 0; + + deleteNode(m_right); + m_right = 0; +} + +void BinaryOperator::applyMap(NodeFunctor &fn) const +{ + fn(leftNode()); + fn(rightNode()); + fn(this); +} + +QString BinaryOperator::infixString() const +{ + QString op; + + switch(type()) { + case Addition: + op = "+"; + break; + + case Subtraction: + op = "-"; + break; + + case Multiplication: + op = "*"; + break; + + case Division: + op = "/"; + break; + + case Exponentiation: + op = "^"; + break; + + default: + op = "Error"; + } + + QString left = QString(isSimpleNode(leftNode()) ? "%1" : "(%1)").arg(leftNode()->infixString()); + QString right = QString(isSimpleNode(rightNode()) ? "%1" : "(%1)").arg(rightNode()->infixString()); + + return QString("%1 %2 %3").arg(left, op, right); +} + +Abakus::number_t BinaryOperator::derivative() const +{ + if(!leftNode() || !rightNode()) { + kdError() << "Can't evaluate binary operator!\n"; + return Abakus::number_t(0); + } + + Abakus::number_t f = leftNode()->value(); + Abakus::number_t fPrime = leftNode()->derivative(); + Abakus::number_t g = rightNode()->value(); + Abakus::number_t gPrime = rightNode()->derivative(); + + switch(type()) { + case Addition: + return fPrime + gPrime; + + case Subtraction: + return fPrime - gPrime; + + case Multiplication: + return f * gPrime + fPrime * g; + + case Division: + return (g * fPrime - f * gPrime) / (g * g); + + case Exponentiation: + return f.pow(g) * ((g / f) * fPrime + gPrime * f.ln()); + + default: + kdError() << "Impossible case encountered evaluating binary operator!\n"; + return Abakus::number_t(0); + } +} + +Abakus::number_t BinaryOperator::value() const +{ + if(!leftNode() || !rightNode()) { + kdError() << "Can't evaluate binary operator!\n"; + return Abakus::number_t(0); + } + + Abakus::number_t lValue = leftNode()->value(); + Abakus::number_t rValue = rightNode()->value(); + + switch(type()) { + case Addition: + return lValue + rValue; + + case Subtraction: + return lValue - rValue; + + case Multiplication: + return lValue * rValue; + + case Division: + return lValue / rValue; + + case Exponentiation: + return lValue.pow(rValue); + + default: + kdError() << "Impossible case encountered evaluating binary operator!\n"; + return Abakus::number_t(0); + } +} + +bool BinaryOperator::isSimpleNode(Node *node) const +{ + if(dynamic_cast<Identifier *>(node) || + dynamic_cast<NumericValue *>(node) || + dynamic_cast<UnaryOperator *>(node) || + dynamic_cast<BaseFunction *>(node)) + { + return true; + } + + return false; +} + +Identifier::Identifier(const char *name) : m_name(name) +{ +} + +Abakus::number_t Identifier::value() const +{ + return ValueManager::instance()->value(name()); +} + +void Identifier::applyMap(NodeFunctor &fn) const +{ + fn(this); +} + +QString NumericValue::infixString() const +{ + return value().toString(); +} + +// vim: set et ts=8 sw=4: diff --git a/src/node.h b/src/node.h new file mode 100644 index 0000000..e2331ab --- /dev/null +++ b/src/node.h @@ -0,0 +1,219 @@ +#ifndef ABAKUS_NODE_H +#define ABAKUS_NODE_H +/* + * node.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <qstring.h> + +#include "numerictypes.h" + +class Node; +class Function; + +template <class T> class SharedPtr; +typedef SharedPtr<Node> NodePtr; + +/** + * A class that operates on a Node. Called recursively on a node and all + * of its children. + */ +class NodeFunctor +{ + public: + virtual void operator()(const Node *node) = 0; + virtual ~NodeFunctor() { } +}; + +class Node +{ + public: + virtual ~Node() { } + virtual Abakus::number_t value() const = 0; + + // Deletes a node only if it isn't a function, since those are + // typically read-only. + virtual void deleteNode(Node *node); + + // Calls functor() on all subchildren and this. + virtual void applyMap(NodeFunctor &fn) const = 0; + + // Returns an infix representation of the expression. + virtual QString infixString() const = 0; + + // Returns the derivative of the node. + virtual Abakus::number_t derivative() const = 0; +}; + +class BaseFunction : public Node +{ + public: + BaseFunction(const char *name); + + virtual QString name() const { return m_name; } + virtual const Function *function() const; + + private: + QString m_name; + const Function *m_function; +}; + +class UnaryFunction : public BaseFunction +{ + public: + UnaryFunction(const char *name, Node *operand); + virtual ~UnaryFunction(); + + virtual Node *operand() const { return m_node; } + virtual void setOperand(Node *operand); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const; + + private: + Node *m_node; +}; + +// Calculates the approximate derivative of its operand. +class DerivativeFunction : public Node +{ + public: + DerivativeFunction(Node *operand, Node *where) : + m_operand(operand), m_where(where) { } + ~DerivativeFunction(); + + virtual Abakus::number_t value() const; + + virtual void applyMap(NodeFunctor &fn) const; + + // Returns an infix representation of the expression. + virtual QString infixString() const; + + virtual Abakus::number_t derivative() const; + + private: + Node *m_operand; + Node *m_where; +}; + +class UserDefinedFunction : public UnaryFunction +{ + public: + UserDefinedFunction(const char *name, Node *operand) : UnaryFunction(name, operand) { }; + + virtual Abakus::number_t value() const { return operand()->value(); } + virtual Abakus::number_t derivative() const { return operand()->derivative(); } +}; + +class BuiltinFunction : public UnaryFunction +{ + public: + BuiltinFunction(const char *name, Node *operand); + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const; +}; + +class UnaryOperator : public Node +{ + public: + typedef enum { Negation } Type; + + UnaryOperator(Type type, Node *operand); + virtual ~UnaryOperator(); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const; + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const; + + virtual Type type() const { return m_type; } + virtual Node *operand() const { return m_node; } + + private: + Type m_type; + Node *m_node; +}; + +class BinaryOperator : public Node +{ + public: + typedef enum { Addition, Subtraction, Multiplication, Division, Exponentiation } Type; + + BinaryOperator(Type type, Node *left, Node *right); + virtual ~BinaryOperator(); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const; + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const; + + virtual Type type() const { return m_type; } + virtual Node *leftNode() const { return m_left; } + virtual Node *rightNode() const { return m_right; } + + private: + bool isSimpleNode(Node *node) const; + + Type m_type; + Node *m_left, *m_right; +}; + +class Identifier : public Node +{ + public: + Identifier(const char *name); + + virtual void applyMap(NodeFunctor &fn) const; + virtual QString infixString() const { return name(); } + + virtual Abakus::number_t value() const; + virtual Abakus::number_t derivative() const + { + if(name() == "x") + return "1"; + else + return "0"; + } + + virtual QString name() const { return m_name; } + + private: + QString m_name; +}; + +class NumericValue : public Node +{ + public: + NumericValue(const Abakus::number_t value) : m_value(value) { } + + virtual void applyMap(NodeFunctor &fn) const { fn(this); } + virtual QString infixString() const; + + virtual Abakus::number_t value() const { return m_value; } + virtual Abakus::number_t derivative() const { return "0"; } + + private: + Abakus::number_t m_value; +}; + +#endif + +// vim: set et sw=4 ts=8: diff --git a/src/number.c b/src/number.c new file mode 100644 index 0000000..3ba4297 --- /dev/null +++ b/src/number.c @@ -0,0 +1,1809 @@ +/* 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 02110-1301 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 <stdio.h> +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h>/* 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; index<num->n_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; index<num->n_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<len; i++) printf ("%c",BCD_CHAR(num[i])); + printf ("\n"); +} + +// vim: set et sw=2 ts=8: diff --git a/src/number.h b/src/number.h new file mode 100644 index 0000000..9f5e5a7 --- /dev/null +++ b/src/number.h @@ -0,0 +1,169 @@ +/* number.h: Arbitrary precision numbers header file. */ +/* + 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 02110-1301 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 + +*************************************************************************/ + +#ifndef _NUMBER_H_ +#define _NUMBER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#undef _PROTOTYPE + +#ifndef NUMBER__STDC__ +#define NUMBER__STDC__ +#endif + +typedef enum {PLUS, MINUS} sign; + +typedef struct bc_struct *bc_num; + +typedef struct bc_struct + { + sign n_sign; + int n_len; /* The number of digits before the decimal point. */ + int n_scale; /* The number of digits after the decimal point. */ + int n_refs; /* The number of pointers to this number. */ + bc_num n_next; /* Linked list for available list. */ + char *n_ptr; /* The pointer to the actual storage. + If NULL, n_value points to the inside of + another number (bc_multiply...) and should + not be "freed." */ + char *n_value; /* The number. Not zero char terminated. + May not point to the same place as n_ptr as + in the case of leading zeros generated. */ + } bc_struct; + + +/* The base used in storing the numbers in n_value above. + Currently this MUST be 10. */ + +#define BASE 10 + +/* Some useful macros and constants. */ + +#define CH_VAL(c) (c - '0') +#define BCD_CHAR(d) (d + '0') + +#ifdef MIN +#undef MIN +#undef MAX +#endif +#define MAX(a,b) ((a)>(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 + +// vim: set et sw=2 ts=8: diff --git a/src/numerictypes.cpp b/src/numerictypes.cpp new file mode 100644 index 0000000..f2aaf7a --- /dev/null +++ b/src/numerictypes.cpp @@ -0,0 +1,205 @@ +/* + * numerictypes.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include "numerictypes.h" +#include "hmath.h" + +#include <kdebug.h> +#include <kglobal.h> +#include <klocale.h> + +Abakus::TrigMode Abakus::m_trigMode = Abakus::Degrees; +int Abakus::m_prec = -1; + +#if HAVE_MPFR + +namespace Abakus +{ + +QString convertToString(const mpfr_ptr &number) +{ + char *str = 0; + QRegExp zeroKiller ("0*$"); + mp_exp_t exp; + int desiredPrecision = Abakus::m_prec; + QString decimalSymbol = KGlobal::locale()->decimalSymbol(); + + if(desiredPrecision < 0) + desiredPrecision = 8; + + // This first call is to see approximately how many digits of precision + // the fractional part has. + str = mpfr_get_str (0, &exp, 10, desiredPrecision, number, GMP_RNDN); + + // Check for ginormously small numbers. + if(exp < -74) + return "0"; + + if(exp < -2 || exp > desiredPrecision) + { + // Use exponential notation. + QString numbers (str); + mpfr_free_str(str); + + QString sign, l, r; + if(numbers[0] == '-') + { + sign = "-"; + l = numbers[1]; + r = numbers.right(numbers.length() - 2); + } + else + { + l = numbers[0]; + r = numbers.right(numbers.length() - 1); + } + + // Remove trailing zeroes. + if(Abakus::m_prec < 0) + r.replace(zeroKiller, ""); + + // But don't display numbers like 2.e10 either. + if(r.isEmpty()) + r = "0"; + + r.append(QString("e%1").arg(exp - 1)); + + return sign + l + decimalSymbol + r; + } + else + { + mpfr_free_str(str); + + // This call is to adjust the result so that the fractional part has at + // most m_prec digits of precision. + str = mpfr_get_str (0, &exp, 10, exp + desiredPrecision, number, GMP_RNDN); + } + + QString result = str; + mpfr_free_str(str); + str = 0; + + int position = exp; + QString l, r, sign; + + if(position < 0) { // Number < 0.1 + l.fill('0', -position); + + if(result[0] == '-') { + sign = "-"; + r = result.right(result.length() - 1); + } + else + r = result; + + r = l + r; + l = '0'; + } + else { // Number >= 0.1 + if(result[0] == '-') { + l = result.mid(1, position); + sign = "-"; + position++; + } + else + l = result.left(position); + + r = result.right(result.length() - position); + } + + // Remove trailing zeroes. + r.replace(zeroKiller, ""); + + // Don't display numbers of the form .23 + if(l.isEmpty()) + l = "0"; + + // If we have an integer don't display the decimal part. + if(r.isEmpty()) + return sign + l; + + return sign + l + decimalSymbol + r; +} + +} // namespace Abakus + +Abakus::number_t::value_type setupPi() +{ + static mpfr_t pi; + + mpfr_init2 (pi, 250); + mpfr_const_pi (pi, GMP_RNDN); + + return pi; +} + +Abakus::number_t::value_type setupExponential() +{ + static mpfr_t exponential; + mpfr_t one; + + mpfr_init2 (exponential, 250); + mpfr_init_set_ui (one, 1, GMP_RNDN); + mpfr_exp (exponential, one, GMP_RNDN); + mpfr_clear (one); + + return exponential; +} + +const Abakus::number_t::value_type Abakus::number_t::PI = setupPi(); +const Abakus::number_t::value_type Abakus::number_t::E = setupExponential(); + +#else + +// Converts hmath number to a string. + +namespace Abakus +{ + +QString convertToString(const HNumber &num) +{ + QString str = HMath::formatGenString(num, m_prec); + QString decimalSymbol = KGlobal::locale()->decimalSymbol(); + str.replace('.', decimalSymbol); + + QStringList parts = QStringList::split("e", str); + QRegExp zeroKiller("(" + QRegExp::escape(decimalSymbol) + + "\\d*[1-9])0*$"); // Remove trailing zeroes. + QRegExp zeroKiller2("(" + QRegExp::escape(decimalSymbol) + ")0*$"); + + str = parts[0]; + str.replace(zeroKiller, "\\1"); + str.replace(zeroKiller2, "\\1"); + if(str.endsWith(decimalSymbol)) + str.truncate(str.length() - 1); // Remove trailing period. + + if(parts.count() > 1 && parts[1] != "0") + str += QString("e%1").arg(parts[1]); + + return str; +} + +} // namespace Abakus. + +const Abakus::number_t::value_type Abakus::number_t::PI = HMath::pi(); +const Abakus::number_t::value_type Abakus::number_t::E = HMath::exp(1); + +#endif /* HAVE_MPFR */ + +// vim: set et ts=8 sw=4: diff --git a/src/numerictypes.h b/src/numerictypes.h new file mode 100644 index 0000000..7a82d06 --- /dev/null +++ b/src/numerictypes.h @@ -0,0 +1,693 @@ +#ifndef ABAKUS_NUMERICTYPES_H +#define ABAKUS_NUMERICTYPES_H +/* + * numerictypes.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <sstream> +#include <string> + +#include <qstring.h> +#include <qstringlist.h> +#include <qregexp.h> + +#include "hmath.h" +#include "config.h" + +#if HAVE_MPFR +#include <mpfr.h> +#endif + +namespace Abakus +{ + +/* What trigonometric mode we're in. */ +typedef enum { Degrees, Radians } TrigMode; + +/* Shared application-wide */ +extern TrigMode m_trigMode; + +/* Precision to display at. */ +extern int m_prec; + +/** + * Representation of a number type. Includes the basic operators, along with + * built-in functions such as abs() and mod(). + * + * You need to actually define it using template specializations though. You + * can add functions in a specialization, it may be worth it to have the + * functions declared here as well so that you get a compiler error if you + * forget to implement it. + * + * Note that since we're using a specialization, and then typedef'ing the + * new specialized class to number_t, that means we only support one type of + * number at a time, and the choice is made at compile-time. + */ +template <typename T> +class number +{ +public: + /// Default ctor and set-and-assign ctor wrapped in one. + number(const T& t = T()); + + /// Copy constructor. + number(const number &other); + + /// Create number from textual representation, useful for ginormously + /// precise numbers. + number(const char *str); + + /// Convienience constructor to create a number from an integer. + explicit number(int i); + + /// Assignment operator. Be sure to check for &other == this if necessary! + number<T> &operator =(const number<T> &other); + + // You need to implement the suite of comparison operators as well, along + // with the negation operator. Sorry. + + bool operator!=(const number<T> &other) const; + bool operator==(const number<T> &other) const; + bool operator<(const number<T> &other) const; + bool operator>(const number<T> &other) const; + bool operator<=(const number<T> &other) const; + bool operator>=(const number<T> &other) const; + + number<T> operator -() const; + + // These functions must be implemented by all specializations to be used. + // Note that when implementing these functions, the implicit value is the + // value that this object is wrapping. E.g. you'd call the function on + // a number object, kind of like 3.sin() if you were using Ruby. + + // Trigonometric, must accept values in degrees. + number<T> sin() const; + number<T> cos() const; + number<T> tan() const; + + // Inverse trigonometric, must return result in Degrees if necessary. + number<T> asin() const; + number<T> acos() const; + number<T> atan() const; + + // Hyperbolic trigonometric (doesn't use Degrees). + number<T> sinh() const; + number<T> cosh() const; + number<T> tanh() const; + + // Inverse hyperbolic trigonometric (doesn't use degrees). + number<T> asinh() const; + number<T> acosh() const; + number<T> atanh() const; + + /// @return Number rounded to closest integer less than or equal to value. + number<T> floor() const; + + /// @return Number rounded to closest integer greater than or equal to value. + number<T> ceil() const; + + /// @return Number with only integer component of result. + number<T> integer() const; + + /// @return Number with only fractional component of result. + number<T> frac() const; + + /** + * @return Number rounded to nearest integer. What to do in 'strange' + * situations is specialization-dependant, I don't really care enough to + * mandate one or the other. + */ + number<T> round() const; + + /// @return Absolute value of number. + number<T> abs() const; + + /// @return Square root of number. + number<T> sqrt() const; + + /// @return Natural-base logarithm of value. + number<T> ln() const; + + /// @return base-10 logarithm of value. + number<T> log() const; + + /// @return Natural base raised to the power given by our value. + number<T> exp() const; + + /// @return Our value raised to the \p exponent power. Would be nice if + /// it supported even exponents on negative numbers correctly. + number<T> pow(const number<T> &exponent); + + /// @return value rounded to double precision. + double asDouble() const; + + /// @return Textual representation of the number, adjusted to the user's + /// current precision. + QString toString() const; + + /// @return Our value. + T value() const; +}; + +// You should also remember to overload the math operators for your +// specialization. These generic ones should work for templates wrapping a +// type that C++ already has operators for. + +template<typename T> +inline number<T> operator+(const number<T> &l, const number<T> &r) +{ + return number<T>(l.value() + r.value()); +} + +template<typename T> +inline number<T> operator-(const number<T> &l, const number<T> &r) +{ + return number<T>(l.value() - r.value()); +} + +template<typename T> +inline number<T> operator*(const number<T> &l, const number<T> &r) +{ + return number<T>(l.value() * r.value()); +} + +template<typename T> +inline number<T> operator/(const number<T> &l, const number<T> &r) +{ + return number<T>(l.value() / r.value()); +} + +#if HAVE_MPFR + +/** + * Utility function to convert a MPFR number to a string. This is declared + * this way so that when it changes we don't have to recompile all of Abakus. + * + * This function obeys the precision settings of the user. This means that if + * you change the precision between function calls, you may get different + * results, even on the same number! + * + * But, don't use this directly, you should be using + * number<mpfr_ptr>::toString() instead! + * + * @param number MPFR number to convert to string. + * @return The number converted to a string, in US Decimal format at this time. + * @see number<>::toString() + */ +QString convertToString(const mpfr_ptr &number); + +/** + * This is a specialization of the number<> template for the MPFR numeric type. + * It uses a weird hack in that it is declared as specializing mpfr_ptr instead + * of mpfr_t like is used everywhere in MPFR's public API. + * + * This is because mpfr_t does not seem to play well with C++ templates (it + * is implemented internally as a 1-length array to get pointer semantics + * while also allocating memory. + * + * What this means is that should you ever have to deal with allocating + * memory, you need to use allocate space for it (mpfr_ptr is a pointer to + * __mpfr_struct). + * + * I don't like using the internal API this way, but I have little choice. + * + * @author Michael Pyne <michael.pyne@kdemail.net> + */ +template<> +class number<mpfr_ptr> +{ +public: + typedef mpfr_ptr value_type; + + static const mp_rnd_t RoundDirection = GMP_RNDN; + + number(const value_type& t) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set(m_t, t, RoundDirection); + } + + number(const number<value_type> &other) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set(m_t, other.m_t, RoundDirection); + } + + number(const char *str) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set_str (m_t, str, 10, RoundDirection); + } + + explicit number(int i) + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init_set_si(m_t, (signed long int) i, RoundDirection); + } + + /// Construct a number with a value of NaN. + number() + { + m_t = (mpfr_ptr) new __mpfr_struct; + mpfr_init(m_t); + } + + ~number() + { + mpfr_clear(m_t); + delete (__mpfr_struct *) m_t; + } + + number<value_type> &operator=(const number<value_type> &other) + { + if(&other == this) + return *this; + + mpfr_clear (m_t); + mpfr_init_set (m_t, other.m_t, RoundDirection); + + return *this; + } + + bool operator!=(const number<value_type> &other) const + { + return mpfr_equal_p(m_t, other.m_t) == 0; + } + + bool operator==(const number<value_type> &other) const + { + return mpfr_equal_p(m_t, other.m_t) != 0; + } + + bool operator<(const number<value_type> &other) const + { + return mpfr_less_p(m_t, other.m_t) != 0; + } + + bool operator>(const number<value_type> &other) const + { + return mpfr_greater_p(m_t, other.m_t) != 0; + } + + bool operator<=(const number<value_type> &other) const + { + return mpfr_lessequal_p(m_t, other.m_t) != 0; + } + + bool operator>=(const number<value_type> &other) const + { + return mpfr_greaterequal_p(m_t, other.m_t) != 0; + } + + number<value_type> operator -() const + { + number<value_type> result(m_t); + mpfr_neg(result.m_t, result.m_t, RoundDirection); + + return result; + } + + // internal + number<value_type> asRadians() const + { + if(m_trigMode == Degrees) + { + number<value_type> result(m_t); + mpfr_t pi; + + mpfr_init (pi); + mpfr_const_pi (pi, RoundDirection); + mpfr_mul (result.m_t, result.m_t, pi, RoundDirection); + mpfr_div_ui (result.m_t, result.m_t, 180, RoundDirection); + + mpfr_clear (pi); + + return result; + } + else + return m_t; + } + + // internal + number<value_type> toTrig() const + { + // Assumes num is in radians. + if(m_trigMode == Degrees) + { + number<value_type> result(m_t); + mpfr_t pi; + + mpfr_init (pi); + mpfr_const_pi (pi, RoundDirection); + mpfr_mul_ui (result.m_t, result.m_t, 180, RoundDirection); + mpfr_div (result.m_t, result.m_t, pi, RoundDirection); + + mpfr_clear (pi); + + return result; + } + else + return m_t; + } + +/* There is a lot of boilerplate ahead, so define a macro to declare and + * define some functions for us to forward the call to MPFR. + */ +#define DECLARE_IMPL_BASE(name, func, in, out) number<value_type> name() const \ +{ \ + number<value_type> result = in; \ + mpfr_##func (result.m_t, result.m_t, RoundDirection); \ + \ + return out; \ +} + +// Normal function, uses 2 rather than 3 params +#define DECLARE_NAMED_IMPL2(name, func) number<value_type> name() const \ +{ \ + number<value_type> result = m_t; \ + mpfr_##func (result.m_t, result.m_t); \ + \ + return result; \ +} + +// Normal function, but MPFL uses a different name than abakus. +#define DECLARE_NAMED_IMPL(name, func) DECLARE_IMPL_BASE(name, func, m_t, result) + +// Normal function, just routes call to MPFR. +#define DECLARE_IMPL(name) DECLARE_NAMED_IMPL(name, name) + +// Trig function, degrees in +#define DECLARE_TRIG_IN_IMPL(name) DECLARE_IMPL_BASE(name, name, asRadians(), result) + +// Trig function, degrees out +#define DECLARE_TRIG_OUT_IMPL(name) DECLARE_IMPL_BASE(name, name, m_t, result.toTrig()) + +// Now declare our functions. + DECLARE_TRIG_IN_IMPL(sin) + DECLARE_TRIG_IN_IMPL(cos) + DECLARE_TRIG_IN_IMPL(tan) + + DECLARE_IMPL(sinh) + DECLARE_IMPL(cosh) + DECLARE_IMPL(tanh) + + DECLARE_TRIG_OUT_IMPL(asin) + DECLARE_TRIG_OUT_IMPL(acos) + DECLARE_TRIG_OUT_IMPL(atan) + + DECLARE_IMPL(asinh) + DECLARE_IMPL(acosh) + DECLARE_IMPL(atanh) + + DECLARE_NAMED_IMPL2(floor, floor) + DECLARE_NAMED_IMPL2(ceil, ceil) + DECLARE_NAMED_IMPL(integer, rint) + DECLARE_IMPL(frac) + DECLARE_NAMED_IMPL2(round, round) + + DECLARE_IMPL(abs) + DECLARE_IMPL(sqrt) + DECLARE_NAMED_IMPL(ln, log) + DECLARE_NAMED_IMPL(log, log10) + DECLARE_IMPL(exp) + + // Can't use macro for this one, it's sorta weird. + number<value_type> pow(const number<value_type> &exponent) + { + number<value_type> result = m_t; + + mpfr_pow(result.m_t, result.m_t, exponent.m_t, RoundDirection); + return result; + } + + double asDouble() const + { + return mpfr_get_d(m_t, RoundDirection); + } + + // Note that this can be used dangerously, be careful. + value_type value() const { return m_t; } + + QString toString() const + { + // Move this to .cpp to avoid recompiling as I fix it. + return convertToString(m_t); + } + + static number<value_type> nan() + { + // Doesn't apply, but the default value when initialized happens + // to be nan. + return number<value_type>(); + } + + static const value_type PI; + static const value_type E; + +private: + mpfr_ptr m_t; +}; + +// Specializations of math operators for mpfr. + +template<> +inline number<mpfr_ptr> operator+(const number<mpfr_ptr> &l, const number<mpfr_ptr> &r) +{ + number<mpfr_ptr> result; + mpfr_add(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + +template<> +inline number<mpfr_ptr> operator-(const number<mpfr_ptr> &l, const number<mpfr_ptr> &r) +{ + number<mpfr_ptr> result; + mpfr_sub(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + +template<> +inline number<mpfr_ptr> operator*(const number<mpfr_ptr> &l, const number<mpfr_ptr> &r) +{ + number<mpfr_ptr> result; + mpfr_mul(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + +template<> +inline number<mpfr_ptr> operator/(const number<mpfr_ptr> &l, const number<mpfr_ptr> &r) +{ + number<mpfr_ptr> result; + mpfr_div(result.value(), l.value(), r.value(), GMP_RNDN); + + return result; +} + + // Abakus namespace continues. + typedef number<mpfr_ptr> number_t; + +#else + +// Defined in numerictypes.cpp for ease of reimplementation. +QString convertToString(const HNumber &num); + +/** + * Specialization for internal HMath library, used if MPFR isn't usable. + * + * @author Michael Pyne <michael.pyne@kdemail.net> + */ +template<> +class number<HNumber> +{ +public: + typedef HNumber value_type; + + number(const HNumber& t = HNumber()) : m_t(t) + { + } + explicit number(int i) : m_t(i) { } + number(const number<HNumber> &other) : m_t(other.m_t) { } + + number(const char *s) : m_t(s) { } + + bool operator!=(const number<HNumber> &other) const + { + return m_t != other.m_t; + } + + bool operator==(const number<HNumber> &other) const + { + return m_t == other.m_t; + } + + bool operator<(const number<HNumber> &other) const + { + return m_t < other.m_t; + } + + bool operator>(const number<HNumber> &other) const + { + return m_t > other.m_t; + } + + bool operator<=(const number<HNumber> &other) const + { + return m_t <= other.m_t; + } + + bool operator>=(const number<HNumber> &other) const + { + return m_t >= other.m_t; + } + + number<HNumber> &operator=(const number<HNumber> &other) + { + m_t = other.m_t; + return *this; + } + + HNumber asRadians() const + { + if(m_trigMode == Degrees) + return m_t * PI / HNumber("180.0"); + else + return m_t; + } + + HNumber toTrig(const HNumber &num) const + { + // Assumes num is in radians. + if(m_trigMode == Degrees) + return num * HNumber("180.0") / PI; + else + return num; + } + + number<HNumber> sin() const + { + return HMath::sin(asRadians()); + } + + number<HNumber> cos() const + { + return HMath::cos(asRadians()); + } + + number<HNumber> tan() const + { + return HMath::tan(asRadians()); + } + + number<HNumber> asin() const + { + return toTrig(HMath::asin(m_t)); + } + + number<HNumber> acos() const + { + return toTrig(HMath::acos(m_t)); + } + + number<HNumber> atan() const + { + return toTrig(HMath::atan(m_t)); + } + + number<HNumber> floor() const + { + if(HMath::frac(m_t) == HNumber("0.0")) + return integer(); + if(HMath::integer(m_t) < HNumber("0.0")) + return HMath::integer(m_t) - 1; + return integer(); + } + + number<HNumber> ceil() const + { + return floor().value() + HNumber(1); + } + +/* There is a lot of boilerplate ahead, so define a macro to declare and + * define some functions for us to forward the call to HMath. + */ +#define DECLARE_IMPL(name) number<value_type> name() const \ +{ return HMath::name(m_t); } + + DECLARE_IMPL(frac) + DECLARE_IMPL(integer) + DECLARE_IMPL(round) + + DECLARE_IMPL(abs) + + DECLARE_IMPL(sqrt) + + DECLARE_IMPL(ln) + DECLARE_IMPL(log) + DECLARE_IMPL(exp) + + DECLARE_IMPL(sinh) + DECLARE_IMPL(cosh) + DECLARE_IMPL(tanh) + + DECLARE_IMPL(asinh) + DECLARE_IMPL(acosh) + DECLARE_IMPL(atanh) + + HNumber value() const { return m_t; } + + double asDouble() const { return toString().toDouble(); } + + number<HNumber> operator-() const { return HMath::negate(m_t); } + + // TODO: I believe this doesn't work for negative numbers with even + // exponents. Which breaks simple stuff like (-2)^2. :( + number<HNumber> pow(const number<HNumber> &exponent) + { + return HMath::raise(m_t, exponent.m_t); + } + + QString toString() const + { + return convertToString(m_t); + } + + static number<HNumber> nan() + { + return HNumber::nan(); + } + + static const HNumber PI; + static const HNumber E; + +private: + HNumber m_t; +}; + + // Abakus namespace continues. + typedef number<HNumber> number_t; + +#endif /* HAVE_MPFR */ + +}; // namespace Abakus + +#endif /* ABAKUS_NUMERICTYPES_H */ + +// vim: set et ts=8 sw=4: diff --git a/src/parser.yy b/src/parser.yy new file mode 100644 index 0000000..5a93621 --- /dev/null +++ b/src/parser.yy @@ -0,0 +1,386 @@ +/* + * parser.yy - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +%{ + +/* Add necessary includes here. */ +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "result.h" +#include "node.h" +#include "function.h" +#include "valuemanager.h" + +extern char *yytext; + +extern int gCheckIdents; + +int yylex(void); +int yyerror(const char *); + +%} + +%union { + Node *node; + NumericValue *value; + UnaryFunction *fn; + Identifier *ident; +} + +%token <value> NUM +%type <node> EXP FACTOR TERM S EXPONENT NUMBER VALUE FINAL +%type <fn> FUNC +%token <fn> FN +%token <ident> ID +%type <ident> IDENT ASSIGN +%token POWER "**" +%token SET "set" +%token REMOVE "remove" +%token DERIV "deriv" + +%% + +/** + * Parser design: + * + * This is pretty standard stuff for the calculator part (read in tokens from + * the lexer, and form a syntax tree using the Node* objects). The unusual + * part is that due to the design of bison, we don't actually return a value + * normally to the calling function. + * + * Instead, we make use of the static Result::setLastResult() call in order + * to notify the calling function of the result of the parse. There are + * different statuses you can set, including Error (with a message), Null + * (which indicates that some action happened that doesn't generate a result), + * and Value (with a Node* that holds the result). + * + * If you are done parsing before reaching the FINAL token, you can call: + * YYACCEPT: Done, parsed successfully. + * YYERROR : Done, there was an error. + * + * Note that if you let the parse bubble back up to FINAL, then the result + * will always be a Value. + */ + +FINAL: { gCheckIdents = 1; } S { + Result::setLastResult(NodePtr($2)); + $$ = 0; +} + +S: EXP { $$ = $1; } + +// Rudimentary error handling +S: error '=' { + Result::setLastResult(i18n("This is an invalid assignment.")); + + YYABORT; +} + +// Can't assign to a function. +S: FUNC '=' { + QString s(i18n("You can't assign to function %1").arg($1->name())); + Result::setLastResult(s); + + YYABORT; +} + +// This is a function prototype. abakus currently only supports one-argument +// functions. +ASSIGN: '(' { --gCheckIdents; } IDENT ')' '=' { + $$ = $3; +} + +// Blocking a variable with the name deriv is a slight feature regression +// since normally functions and variables with the same name can coexist, but +// I don't want to duplicate code all over the place. +S: SET DERIV { + QString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv")); + Result::setLastResult(s); + + YYABORT; +} + +S: DERIV '=' { + QString s(i18n("Function %1 is built-in and cannot be overridden.").arg("deriv")); + Result::setLastResult(s); + + YYABORT; +} + +S: SET FUNC ASSIGN EXP { + ++gCheckIdents; + + // We're trying to reassign an already defined function, make sure it's + // not a built-in. + QString funcName = $2->name(); + QString ident = $3->name(); + FunctionManager *manager = FunctionManager::instance(); + + if(manager->isFunction(funcName) && !manager->isFunctionUserDefined(funcName)) { + QString s(i18n("Function %1 is built-in and cannot be overridden.").arg(funcName)); + Result::setLastResult(s); + + YYABORT; + } + + if(manager->isFunction(funcName)) + manager->removeFunction(funcName); + + BaseFunction *newFn = new UserDefinedFunction(funcName, $4); + if(!manager->addFunction(newFn, ident)) { + QString s(i18n("Unable to define function %1 because it is recursive.").arg(funcName)); + Result::setLastResult(s); + + YYABORT; + } + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// IDENT is the same as FUNC, except that the lexer has determined that IDENT +// is not already a FUNC. +S: SET IDENT ASSIGN EXP { + ++gCheckIdents; + + QString funcName = $2->name(); + QString ident = $3->name(); + + // No need to check if the function is already defined, because the + // lexer checked for us before returning the IDENT token. + BaseFunction *newFn = new UserDefinedFunction(funcName, $4); + FunctionManager::instance()->addFunction(newFn, ident); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// Remove a defined function. +S: REMOVE FUNC '(' ')' { + FunctionManager::instance()->removeFunction($2->name()); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// Can't remove an ident using remove-func syntax. +S: REMOVE IDENT '(' ')' { + // This is an error + Result::setLastResult(Result(i18n("Function %1 is not defined.").arg($2->name()))); + YYABORT; +} + +// This happens when the user tries to remove a function that's not defined. +S: REMOVE IDENT '(' IDENT ')' { + // This is an error + Result::setLastResult(Result(i18n("Function %1 is not defined.").arg($2->name()))); + YYABORT; +} + +S: REMOVE IDENT { + ValueManager *manager = ValueManager::instance(); + + if(manager->isValueSet($2->name()) && !manager->isValueReadOnly($2->name())) { + manager->removeValue($2->name()); + + Result::setLastResult(Result::Null); + YYACCEPT; + } + else { + QString s; + if(manager->isValueSet($2->name())) + s = i18n("Can't remove predefined variable %1.").arg($2->name()); + else + s = i18n("Can't remove undefined variable %1.").arg($2->name()); + + Result::setLastResult(s); + + YYABORT; + } +} + +S: SET IDENT '=' EXP { + ValueManager *vm = ValueManager::instance(); + + if(vm->isValueReadOnly($2->name())) { + if($2->name() == "pi" && $4->value() == Abakus::number_t("3.0")) + Result::setLastResult(i18n("This isn't Indiana, you can't just change pi")); + else + Result::setLastResult(i18n("%1 is a constant").arg($2->name())); + + YYABORT; + } + + ValueManager::instance()->setValue($2->name(), $4->value()); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +// Set a variable. +S: IDENT '=' EXP { + ValueManager *vm = ValueManager::instance(); + + if(vm->isValueReadOnly($1->name())) { + if($1->name() == "pi" && $3->value() == Abakus::number_t("3.0")) + Result::setLastResult(i18n("This isn't Indiana, you can't just change pi")); + else + Result::setLastResult(i18n("%1 is a constant").arg($1->name())); + + YYABORT; + } + + ValueManager::instance()->setValue($1->name(), $3->value()); + + Result::setLastResult(Result::Null); + YYACCEPT; +} + +S: NUMBER '=' { + Result::setLastResult(i18n("Can't assign to %1").arg($1->value().toString())); + YYABORT; +} + +// Can't call this as a function. +TERM: IDENT '(' { + Result::setLastResult(i18n("%1 isn't a function (or operator expected)").arg($1->name())); + YYABORT; +} + +// Can't do this either. +TERM: IDENT IDENT { + Result::setLastResult(i18n("Missing operator")); + YYABORT; +} + +TERM: IDENT NUMBER { + Result::setLastResult(i18n("Missing operator")); + YYABORT; +} + +TERM: NUMBER NUMBER { + Result::setLastResult(i18n("Missing operator")); + YYABORT; +} + +S: error { + Result::setLastResult(i18n("Sorry, I can't figure it out.")); + YYABORT; +} + +/** + * Here be the standard calculator-parsing part. Nothing here should be too + * fancy. + */ +EXP: EXP '+' FACTOR { $$ = new BinaryOperator(BinaryOperator::Addition, $1, $3); } +EXP: EXP '-' FACTOR { $$ = new BinaryOperator(BinaryOperator::Subtraction, $1, $3); } +EXP: FACTOR { $$ = $1; } + +FACTOR: FACTOR '*' EXPONENT { $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $3); } +FACTOR: FACTOR '/' EXPONENT { $$ = new BinaryOperator(BinaryOperator::Division, $1, $3); } +FACTOR: EXPONENT { $$ = $1; } + +EXPONENT: TERM POWER EXPONENT { $$ = new BinaryOperator(BinaryOperator::Exponentiation, $1, $3); } +EXPONENT: TERM { $$ = $1; } + +TERM: '+' VALUE { $$ = $2; } +TERM: '-' VALUE { $$ = new UnaryOperator(UnaryOperator::Negation, $2); } +TERM: '(' EXP ')' { $$ = $2; } +TERM: '-' '(' EXP ')' { $$ = new UnaryOperator(UnaryOperator::Negation, $3); } + +TERM: VALUE { $$ = $1; } + +VALUE: NUMBER { $$ = $1; } + +NUMBER: NUM { + KLocale *locale = KGlobal::locale(); + QChar decimal = locale->decimalSymbol()[0]; + + // Replace current decimal separator with US Decimal separator to be + // evil. + unsigned len = strlen(yytext); + for(unsigned i = 0; i < len; ++i) + if(yytext[i] == decimal) + yytext[i] = '.'; + + Abakus::number_t value(yytext); + + $$ = new NumericValue(value); +} + +TERM: DERIV { --gCheckIdents; } '(' EXP ',' { ++gCheckIdents; } EXP ')' { + $$ = new DerivativeFunction($4, $7); +} + +TERM: FUNC TERM { + $1->setOperand($2); + $$ = $1; +} + +/* Handle implicit multiplication */ +TERM: NUMBER FUNC TERM { + $2->setOperand($3); + $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $2); +} + +TERM: NUMBER '(' EXP ')' { + $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $3); +} + +TERM: NUMBER IDENT { + if(gCheckIdents > 0 && !ValueManager::instance()->isValueSet($2->name())) { + Result::setLastResult(i18n("Unknown variable %1").arg($2->name())); + YYABORT; + } + + $$ = new BinaryOperator(BinaryOperator::Multiplication, $1, $2); +} + +VALUE: IDENT { + if(gCheckIdents <= 0 || ValueManager::instance()->isValueSet($1->name())) + $$ = $1; + else { + Result::setLastResult(i18n("Unknown variable %1").arg($1->name())); + YYABORT; + } +} + +IDENT: ID { + $$ = new Identifier(yytext); +} + +FUNC: FN { + /* No check necessary, the lexer has already checked for us. */ + $$ = new BuiltinFunction(yytext, 0); +} + +%% + +int gCheckIdents = 0; + +int yyerror(const char *) +{ + return 0; +} diff --git a/src/result.cpp b/src/result.cpp new file mode 100644 index 0000000..4aa06ca --- /dev/null +++ b/src/result.cpp @@ -0,0 +1,33 @@ +/* + * result.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include "result.h" + +Result *Result::m_lastResult = new Result; + +Result::Result(const QString &message) : m_type(Error), m_message(message) +{ +} + +Result::Result(NodePtr node) : m_node(node), m_type(Value) +{ +} + +Result::Result(Type type) : m_type(type) +{ +} diff --git a/src/result.h b/src/result.h new file mode 100644 index 0000000..682324c --- /dev/null +++ b/src/result.h @@ -0,0 +1,77 @@ +#ifndef ABAKUS_RESULT_H +#define ABAKUS_RESULT_H +/* + * result.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <qstring.h> + +#include "node.h" +#include "sharedptr.h" + +/** + * A conceptual result from an expression parse. Used to determine if the + * parse succeeded or failed. If it succeeded it will have a node value you + * can query as the answer. + */ +class Result +{ +public: + typedef enum { Error, Null, Value } Type; + + /** + * Default constructor, which constructs a "failed" Result. + */ + Result(const QString &message = ""); + + /** + * Node constructor, which constructs a "succeeded" result. + */ + Result(NodePtr node); + + /** + * Constructor, constructs a "null" result. This means that the + * operation was successful, but did not result in a normal value. + */ + Result(Type type); + + bool failed() const { return m_type == Error; } + + Type type() const { return m_type; } + + QString message() const { return m_message; } + + const NodePtr result() const { return m_node; } + NodePtr result() { return m_node; } + + static Result *lastResult() { return m_lastResult; } + static void setLastResult(const Result &result) + { + *m_lastResult = result; + } + +private: + NodePtr m_node; + Type m_type; + QString m_message; + static Result *m_lastResult; +}; + +#endif /* ABAKUS_RESULT_H */ + +// vim: set et ts=8 sw=4: diff --git a/src/resultlistview.cpp b/src/resultlistview.cpp new file mode 100644 index 0000000..abe76c1 --- /dev/null +++ b/src/resultlistview.cpp @@ -0,0 +1,149 @@ +/* + * resultlistview.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <kdebug.h> +#include <kpopupmenu.h> +#include <klocale.h> + +#include <qclipboard.h> +#include <qapplication.h> +#include <qevent.h> +#include <qcursor.h> +#include <qdragobject.h> +#include <qheader.h> + +#include "resultlistview.h" +#include "resultlistviewtext.h" +#include "dragsupport.h" + +using DragSupport::makePixmap; +using namespace ResultList; + +ResultListView::ResultListView(QWidget *parent, const char *name) : + KListView(parent, name), m_itemRightClicked(0) +{ + connect(this, SIGNAL(doubleClicked(QListViewItem *, const QPoint &, int)), + SLOT(slotDoubleClicked(QListViewItem *, const QPoint &, int))); + + addColumn(i18n("Expression")); + addColumn(i18n("Result")); + addColumn(i18n("Shortcut")); + + header()->hide(); // I hate that header + header()->setStretchEnabled(ResultColumn, true); + + setDragEnabled(true); + setItemMargin(2); + setColumnAlignment(ResultColumn, AlignLeft); + setColumnAlignment(ShortcutColumn, AlignHCenter); + setColumnWidthMode(ResultColumn, Maximum); + setSortColumn(-1); +} + +bool ResultListView::getStackValue(unsigned stackPosition, Abakus::number_t &result) +{ + QListViewItem *it = firstChild(); + for(; it; it = it->itemBelow()) { + ResultListViewText *resultItem = dynamic_cast<ResultListViewText *>(it); + if(!resultItem->wasError() && resultItem->stackPosition() == stackPosition) { + result = Abakus::number_t(resultItem->resultText().latin1()); + return true; + } + } + + return false; +} + +QDragObject *ResultListView::dragObject() +{ + QPoint viewportPos = viewport()->mapFromGlobal(QCursor::pos()); + ResultListViewText *item = itemUnderCursor(); + + if(item) { + QString text = item->resultText(); + + int column = header()->sectionAt(viewportPos.x()); + + if(column == ExpressionColumn) + text = item->expressionText(); + + QDragObject *drag = new QTextDrag(text, this); + drag->setPixmap(makePixmap(text, font())); + + return drag; + } + + return 0; +} + +void ResultListView::contextMenuEvent(QContextMenuEvent *e) +{ + m_itemRightClicked = itemUnderCursor(); + KPopupMenu *menu = constructPopupMenu(m_itemRightClicked); + + menu->popup(e->globalPos()); +} + +void ResultListView::slotDoubleClicked(QListViewItem *item, const QPoint &, int c) +{ + ResultListViewText *textItem = dynamic_cast<ResultListViewText *>(item); + if(!textItem) + return; + + if(c == ExpressionColumn) + emit signalEntrySelected(textItem->expressionText()); + else if(c == ResultColumn) + emit signalResultSelected(textItem->resultText()); +} + +KPopupMenu *ResultListView::constructPopupMenu(const ResultListViewText *item) +{ + KPopupMenu *menu = new KPopupMenu(this, "list view context menu"); + + menu->insertItem(i18n("Clear &History"), this, SLOT(clear()), ALT+Key_R); + + int id = menu->insertItem(i18n("Copy Result to Clipboard"), this, SLOT(slotCopyResult())); + if(!item || item->wasError()) + menu->setItemEnabled(id, false); + + return menu; +} + +void ResultListView::slotCopyResult() +{ + if(!m_itemRightClicked) + return; + + QClipboard *clipboard = QApplication::clipboard(); + + clipboard->setText(m_itemRightClicked->resultText(), QClipboard::Clipboard); +} + +ResultListViewText *ResultListView::lastItem() const +{ + return static_cast<ResultListViewText *>(KListView::lastItem()); +} + +ResultListViewText *ResultListView::itemUnderCursor() const +{ + QPoint viewportPos = viewport()->mapFromGlobal(QCursor::pos()); + QListViewItem *underCursor = itemAt(viewportPos); + return static_cast<ResultListViewText *>(underCursor); +} + +#include "resultlistview.moc" diff --git a/src/resultlistview.h b/src/resultlistview.h new file mode 100644 index 0000000..891443c --- /dev/null +++ b/src/resultlistview.h @@ -0,0 +1,64 @@ +#ifndef ABAKUS_RESULTLISTVIEW_H +#define ABAKUS_RESULTLISTVIEW_H +/* + * resultlistview.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <klistview.h> +#include "numerictypes.h" + +class KPopupMenu; +class QLabel; +class QDragObject; +class ResultListViewText; + +namespace ResultList { + enum { ExpressionColumn = 0, ResultColumn, ShortcutColumn }; +} + +class ResultListView : public KListView +{ + Q_OBJECT + + public: + ResultListView(QWidget *parent = 0, const char *name = "result list view"); + + bool getStackValue(unsigned stackPosition, Abakus::number_t &result); + + ResultListViewText *lastItem() const; + + protected: + virtual void contextMenuEvent(QContextMenuEvent *e); + virtual QDragObject *dragObject(); + + signals: + void signalEntrySelected(const QString &text); + void signalResultSelected(const QString &text); + + private slots: + void slotDoubleClicked(QListViewItem *item, const QPoint & /* Ignored */, int c); + void slotCopyResult(); + + private: + KPopupMenu *constructPopupMenu(const ResultListViewText *item); + ResultListViewText *itemUnderCursor() const; + + ResultListViewText *m_itemRightClicked; +}; + +#endif diff --git a/src/resultlistviewtext.cpp b/src/resultlistviewtext.cpp new file mode 100644 index 0000000..cbf7bfb --- /dev/null +++ b/src/resultlistviewtext.cpp @@ -0,0 +1,135 @@ +/* + * resultlistviewtext.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <kdebug.h> + +#include <qregexp.h> +#include <qpainter.h> +#include <qfontmetrics.h> +#include <qfont.h> +#include <qpalette.h> + +#include "resultlistviewtext.h" + +using namespace ResultList; + +ResultListViewText::ResultListViewText(KListView *listView, + const QString &text, + const QString &result, + ResultListViewText *after, + bool isError) + : KListViewItem(listView, after, text, result), m_text(text), + m_result(result), m_wasError(isError), m_stackPosition(0) +{ + // This is some kind of non-result answer, don't bother worrying about the + // stack status, it hasn't changed. +} + +ResultListViewText::ResultListViewText(KListView *listView, + const QString &text, + const Abakus::number_t &result, + ResultListViewText *after, + bool isError) + : KListViewItem(listView, after, text), m_text(text), + m_result(result.toString()), m_wasError(isError), m_stackPosition(0), + m_value(result) +{ + if(after) { + ResultListViewText *item = static_cast<ResultListViewText *>(listView->firstChild()); + for (; item && item != this; item = static_cast<ResultListViewText *>(item->itemBelow())) { + if(!item->wasError()) { + item->setStackPosition(item->stackPosition() + 1); + item->repaint(); + } + } + } + + setStackPosition(0); + + // Call this manually to be rid of trailing zeroes. + setText(ResultColumn, m_value.toString()); +} + +void ResultListViewText::setStackPosition(unsigned pos) +{ + setText(ShortcutColumn, QString("$%1").arg(pos)); + m_stackPosition = pos; +} + +void ResultListViewText::precisionChanged() +{ + if(m_wasError) + return; + + m_result = m_value.toString(); + setText(ResultColumn, m_result); +} + +void ResultListViewText::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align) +{ + QColorGroup group(cg); + + // XXX: The Qt::red may not provide good contrast with weird color schemes. + // If so I apologize. + if(m_wasError && column == ResultColumn) + group.setColor(QColorGroup::Text, m_result == "OK" ? cg.link() : Qt::red); + + if(column == ResultColumn) { + QFont f = p->font(); + f.setBold(true); + p->setFont(f); + } + + if(column == ShortcutColumn) { + QFont f = p->font(); + f.setItalic(true); + f.setPointSize(QMIN(f.pointSize() * 9 / 11, 10)); + p->setFont(f); + } + + KListViewItem::paintCell(p, group, column, width, align); +} + +int ResultListViewText::width(const QFontMetrics &fm, const QListView *lv, int c) const +{ + // Simulate painting the text to get accurate results. + if(c == ResultColumn) { + QFont f = lv->font(); + f.setBold(true); + return KListViewItem::width(QFontMetrics(f), lv, c); + } + + if(c == ShortcutColumn) { + QFont f = lv->font(); + f.setItalic(true); + f.setPointSize(QMIN(f.pointSize() * 9 / 11, 10)); + return KListViewItem::width(QFontMetrics(f), lv, c); + } + + return KListViewItem::width(fm, lv, c); +} + +void ResultListViewText::setText(int column, const QString &text) +{ + if(!m_wasError && column == ResultColumn) { + KListViewItem::setText(column, m_value.toString()); + return; + } + + KListViewItem::setText(column, text); +} diff --git a/src/resultlistviewtext.h b/src/resultlistviewtext.h new file mode 100644 index 0000000..d624b0b --- /dev/null +++ b/src/resultlistviewtext.h @@ -0,0 +1,70 @@ +#ifndef ABAKUS_RESULTLISTVIEWTEXT_H +#define ABAKUS_RESULTLISTVIEWTEXT_H +/* + * resultlistviewtext.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include "resultlistview.h" +#include "numerictypes.h" + +class QPainter; +class QColorGroup; +class QFontMetrics; + +// This class shows the results shown in the MainWindow result pane. +class ResultListViewText : public KListViewItem +{ + public: + ResultListViewText(KListView *listView, + const QString &text, + const QString &result, + ResultListViewText *after, + bool isError = false); + + ResultListViewText(KListView *listView, + const QString &text, + const Abakus::number_t &result, + ResultListViewText *after, + bool isError = false); + + QString expressionText() const { return m_text; } + QString resultText() const { return m_result; } + + bool wasError() const { return m_wasError; } + unsigned stackPosition() const { return m_stackPosition; } + + void setStackPosition(unsigned pos); + + // Redisplays the text by calling value.toString again. + void precisionChanged(); + + // Reimplemented from KListViewItem + virtual void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align); + virtual int width(const QFontMetrics &fm, const QListView *lv, int c) const; + + // Reimplemented to remove trailing zeroes from results. + virtual void setText(int column, const QString &text); + + private: + QString m_text, m_result; + bool m_wasError; + unsigned m_stackPosition; + Abakus::number_t m_value; +}; + +#endif diff --git a/src/rpnmuncher.cpp b/src/rpnmuncher.cpp new file mode 100644 index 0000000..ad75495 --- /dev/null +++ b/src/rpnmuncher.cpp @@ -0,0 +1,267 @@ +/* + * rpnmuncher.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <math.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qvaluestack.h> +#include <qregexp.h> +#include <qstring.h> +#include <qstringlist.h> + +#include "rpnmuncher.h" +#include "valuemanager.h" +#include "function.h" + +/** + * Holds either a textual identifier, or a numeric value. + */ +class Operand +{ + public: + Operand() : m_isValue(true), m_value(0) { } + Operand(const QString &ident) : m_isValue(false), m_text(ident) { } + Operand(Abakus::number_t value) : m_isValue(true), m_value(value) { } + + Abakus::number_t value() const + { + if(m_isValue) + return m_value; + + return ValueManager::instance()->value(m_text); + } + + operator Abakus::number_t() const + { + return value(); + } + + QString text() const { return m_text; } + + private: + bool m_isValue; + QString m_text; + Abakus::number_t m_value; +}; + +typedef enum { Number = 256, Func, Ident, Power, Set, Remove, Pop, Clear, Unknown } Token; + +static int tokenize (const QString &token); + +QString RPNParser::m_errorStr; +bool RPNParser::m_error(false); +OperandStack RPNParser::m_stack; + +struct Counter +{ + ~Counter() { + Abakus::number_t count( static_cast<int>(RPNParser::stack().count()) ); + ValueManager::instance()->setValue("stackCount", count); + } +}; + +Abakus::number_t RPNParser::rpnParseString(const QString &text) +{ + QStringList tokens = QStringList::split(QRegExp("\\s"), text); + Counter counter; // Will update stack count when we leave proc. + (void) counter; // Avoid warnings about it being unused. + + // Used in the case statements below + Operand l, r; + FunctionManager *manager = FunctionManager::instance(); + Function *fn = 0; + + m_error = false; + m_errorStr = QString::null; + + for(QStringList::ConstIterator it = tokens.begin(); it != tokens.end(); ++it) { + switch(tokenize(*it)) + { + case Number: + m_stack.push(Abakus::number_t((*it).latin1())); + break; + + case Pop: + if(m_stack.isEmpty()) { + m_error = true; + m_errorStr = i18n("Can't pop from an empty stack."); + return Abakus::number_t::nan(); + } + + m_stack.pop(); + break; + + case Clear: + m_stack.clear(); + break; + + case Func: + if(m_stack.count() < 1) { + m_error = true; + m_errorStr = i18n("Insufficient operands for function %1").arg(*it); + return Abakus::number_t::nan(); + } + + fn = manager->function(*it); + + l = m_stack.pop(); + m_stack.push(evaluateFunction(fn, l)); + break; + + case Ident: + m_stack.push(*it); + break; + + case Set: + case Remove: + m_error = true; + m_errorStr = i18n("The set and remove commands can only be used in normal mode."); + return Abakus::number_t::nan(); + break; + + case Power: + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for exponentiation operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value().pow(r)); + break; + + case Unknown: + m_error = true; + m_errorStr = i18n("Unknown token %1").arg(*it); + return Abakus::number_t::nan(); + break; + + case '=': + r = m_stack.pop(); + l = m_stack.pop(); + ValueManager::instance()->setValue(l.text(), r); + + m_stack.push(l); + break; + + case '+': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for addition operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() + r.value()); + break; + + case '-': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for subtraction operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() - r.value()); + break; + + case '*': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for multiplication operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() * r.value()); + break; + + case '/': + if(m_stack.count() < 2) { + m_error = true; + m_errorStr = i18n("Insufficient operands for division operator."); + return Abakus::number_t::nan(); + } + + r = m_stack.pop(); + l = m_stack.pop(); + m_stack.push(l.value() / r.value()); + break; + + default: + // Impossible case happened. + kdError() << "Impossible case happened in " << k_funcinfo << endl; + m_error = true; + m_errorStr = "Bug found in program, please report."; + return Abakus::number_t::nan(); + } + } + + // TODO: Should this be an error? + if(m_stack.isEmpty()) + return Abakus::number_t::nan(); + + return m_stack.top(); +} + +static int tokenize (const QString &token) +{ + bool isOK; + + token.toDouble(&isOK); + if(isOK) + return Number; + + if(token == "**" || token == "^") + return Power; + + if(FunctionManager::instance()->isFunction(token)) + return Func; + + if(token.lower() == "set") + return Set; + + if(token.lower() == "pop") + return Pop; + + if(token.lower() == "clear") + return Clear; + + if(token.lower() == "remove") + return Remove; + + if(QRegExp("^\\w+$").search(token) != -1 && + QRegExp("\\d").search(token) == -1) + { + return Ident; + } + + if(QRegExp("^[-+*/=]$").search(token) != -1) + return token[0]; + + return Unknown; +} + +// vim: set et sw=4 ts=8: diff --git a/src/rpnmuncher.h b/src/rpnmuncher.h new file mode 100644 index 0000000..71c340e --- /dev/null +++ b/src/rpnmuncher.h @@ -0,0 +1,44 @@ +#ifndef ABAKUS_RPNMUNCHER_H +#define ABAKUS_RPNMUNCHER_H +/* + * rpnmuncher.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +class QString; +class Operand; + +template<class T> class QValueStack; +typedef QValueStack<Operand> OperandStack; + +#include "numerictypes.h" + +class RPNParser +{ + public: + static Abakus::number_t rpnParseString(const QString &text); + static bool wasError() { return m_error; } + static QString errorString() { return m_errorStr; } + static OperandStack &stack() { return m_stack; } + + private: + static QString m_errorStr; + static bool m_error; + static OperandStack m_stack; +}; + +#endif diff --git a/src/sharedptr.h b/src/sharedptr.h new file mode 100644 index 0000000..2837870 --- /dev/null +++ b/src/sharedptr.h @@ -0,0 +1,122 @@ +#ifndef ABAKUS_SHARED_PTR_H +#define ABAKUS_SHARED_PTR_H +/* + * sharedptr.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <kdebug.h> + +template <class T> +class SharedPtr +{ +public: + typedef T value_type; + + class Data + { + public: + Data(T *ptr = 0, unsigned refCount = 1) : m_ptr(ptr), m_refCount(refCount) + { + } + + void deref() + { + --m_refCount; + + if(!m_refCount) { + delete m_ptr; + m_ptr = 0; + } + } + + void ref() + { + ++m_refCount; + } + + T *ptr() { return m_ptr; } + const T* ptr() const { return m_ptr; } + + unsigned refCount() const { return m_refCount; } + + private: + T *m_ptr; + unsigned m_refCount; + }; + + SharedPtr() : m_data(new Data) + { + } + + SharedPtr(T* ptr) : m_data(new Data(ptr)) + { + } + + SharedPtr(const SharedPtr<T> &other) : m_data(other.m_data) + { + m_data->ref(); + } + + ~SharedPtr() + { + m_data->deref(); + } + + SharedPtr<T> &operator=(const SharedPtr<T> &other) + { + if(&other == this) + return *this; + + m_data->deref(); + m_data = other.m_data; + m_data->ref(); + + return *this; + } + + T *operator ->() + { + return m_data->ptr(); + } + + const T *operator ->() const + { + return m_data->ptr(); + } + + T &operator *() + { + return *m_data->ptr(); + } + + const T& operator *() const + { + return *m_data->ptr(); + } + + bool isNull() const { return m_data->ptr() == 0; } + + unsigned refCount() const { return m_data->refCount(); } + +private: + Data *m_data; +}; + +#endif /* ABAKUS_SHARED_PTR_H */ + +// vim: set et ts=8 sw=4: diff --git a/src/valuemanager.cpp b/src/valuemanager.cpp new file mode 100644 index 0000000..db8ef99 --- /dev/null +++ b/src/valuemanager.cpp @@ -0,0 +1,105 @@ +/* + * valuemanager.cpp - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ +#include <kdebug.h> +#include <klocale.h> + +#include <qregexp.h> + +#include "numerictypes.h" +#include "valuemanager.h" + +ValueManager *ValueManager::m_manager = 0; + +ValueManager *ValueManager::instance() +{ + if(!m_manager) + m_manager = new ValueManager; + + return m_manager; +} + +ValueManager::ValueManager(QObject *parent, const char *name) : + QObject(parent, name) +{ + m_values.insert("pi", Abakus::number_t::PI); + m_values.insert("e", Abakus::number_t::E); +} + +Abakus::number_t ValueManager::value(const QString &name) const +{ + return m_values[name]; +} + +bool ValueManager::isValueSet(const QString &name) const +{ + return m_values.contains(name); +} + +bool ValueManager::isValueReadOnly(const QString &name) const +{ + QRegExp readOnlyValues("^(ans|pi|e|stackCount)$"); + + return name.find(readOnlyValues) != -1; +} + +void ValueManager::setValue(const QString &name, const Abakus::number_t value) +{ + if(m_values.contains(name) && this->value(name) != value) + emit signalValueChanged(name, value); + else if(!m_values.contains(name)) + emit signalValueAdded(name, value); + + m_values.replace(name, value); +} + +void ValueManager::removeValue(const QString &name) +{ + if(m_values.contains(name)) + emit signalValueRemoved(name); + + m_values.remove(name); +} + +void ValueManager::slotRemoveUserVariables() +{ + QStringList vars = valueNames(); + + for(QStringList::ConstIterator var = vars.constBegin(); var != vars.constEnd(); ++var) + if(!isValueReadOnly(*var)) + removeValue(*var); +} + +QStringList ValueManager::valueNames() const +{ + return m_values.keys(); +} + +QString ValueManager::description(const QString &valueName) +{ + if(valueName == "e") + return i18n("Natural exponential base - 2.7182818"); + if(valueName == "pi") + return i18n("pi (π) - 3.1415926"); + + return QString(); +} + +#include "valuemanager.moc" + +// vim: set et ts=8 sw=4 encoding=utf-8: diff --git a/src/valuemanager.h b/src/valuemanager.h new file mode 100644 index 0000000..f57016c --- /dev/null +++ b/src/valuemanager.h @@ -0,0 +1,69 @@ +#ifndef ABAKUS_VALUEMANAGER_H +#define ABAKUS_VALUEMANAGER_H +/* + * valuemanager.h - part of abakus + * Copyright (C) 2004, 2005 Michael Pyne <michael.pyne@kdemail.net> + * + * 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02110-1301 USA + */ + +#include <qobject.h> +#include <qmap.h> +#include <qstring.h> +#include <qstringlist.h> + +#include "numerictypes.h" + +class ValueManager : public QObject +{ + Q_OBJECT + public: + typedef QMap<QString, Abakus::number_t> valueMap; + + static ValueManager *instance(); + + Abakus::number_t value(const QString &name) const; + + bool isValueSet(const QString &name) const; + bool isValueReadOnly(const QString &name) const; + + void setValue(const QString &name, const Abakus::number_t value); + void removeValue(const QString &name); + + QStringList valueNames() const; + + /** + * Returns a textual description of a constant built-into abakus. + */ + static QString description(const QString &valueName); + + signals: + void signalValueAdded(const QString &name, Abakus::number_t value); + void signalValueRemoved(const QString &name); + void signalValueChanged(const QString &name, Abakus::number_t newValue); + + public slots: + void slotRemoveUserVariables(); + + private: + ValueManager(QObject *parent = 0, const char *name = "value manager"); + + static ValueManager *m_manager; + valueMap m_values; +}; + +#endif + +// vim: set et sw=4 ts=8: |