1

I'm developing a PHP library to work with big numbers (at this moment only with a "Decimal" class, but I want to add new classes, to handle Integers, Rationals, and Complex numbers). In any case, the problem is the following:

On one hand, PHP hasn't method overloading. I can use "magic methods", but I want to keep type hinting in method signatures. Even if I have a common interface, every number type deserves specific handling because its internals will be completely different, so finally it appears as a natural idea to add type-specific methods to allow interoperability between those classes (Decimal, Integer, Rational...).

On the other hand, there are virtually infinite functions that someone may want to apply to a number. Currently, I'm implementing many of such functions in every class to directly handle the internal properties and avoiding the creation and destruction of intermediate objects (a good example is the sin, exp or cos computing, because they have a lot of intermediate computations).

Then, keeping type specific methods increases the class complexity, and keeping the function methods in the number classes also increases the class complexity.

The problem is that I don't have any good idea about how to extract the function algorithms outside the "number classes" without loosing much performance and without violating the properties encapsulation.

Anyone knows a "good" pattern to follow?

Important to note: I think the problem is not trivial mainly because numbers aren't well handled in a hierarchical way, there are a lot of overlappings between number sets, and using interfaces isn't enough (see this question about "self-types" to see why i say that). I don't want a "perfect" solution, but one good enough that allows me to design a good library for theory purposes, not only for numerical computing.


The case: imagine a class that represents a number, call it Number . Suppose that, in addition to basic arithmetic operations, you have a lot of one-variable functions: sin, cos, tan, arcsin, arccos, arctan, exp, logarithm, square root, gamma, riesz, airy's function, and so on... and add to it many other two-variable functions, and maybe even more complicated functions (with theoretical applications, not randomly defined) with 3 or more variables.

One possibility is to extract the algorithms to compute such functions to other classes in order to keep the class size under control. The problem of this approach is that then you have to expose the internals number classes if you want performance, or loosing a lot of performance if you want to respect SOLID.

I have three points that I want to control: classes size (to improve readability), SOLID principles (because the library has to grow a lot and ensure quality and testability), computation performance (because the library's purpose is to be useful, not a toy).


Proposed Idea:

Finally I've arrived to the conclusion that I should abstract the functions using specific (and instanciable) classes. This will allow me to implement new interesting features like function composition, second order functions, computing limits, integrals and differentiations in a symbolic way... That's a lot of work, but in first place I'll implement only the pillars to make it possible.

Functions will work in first place directly with number objects, and where it's worth I'll add a custom inner representation to boost the performance. Translating the object to the function's inner representation will add some overhead, but less than creating and destructing Number intermediate instances (or I think so, I'll benchmark this type of changes to ensure the complexity increase it's not a waste of time and code lines).

Since PHP don't have parametric classes, part of the contract will be checked at runtime with specific methods, if possible at "function-construction time" rather than at "function-evaluation time". For example, I'm thinking on adding two getters to obtain the return type and the argument "types" ¹ and n-arity. About the second mentioned getter, this will be only for "corner cases", since I prefer to subclass and add a specific and hinted method.


  1. Here I use the word types, but I'm referring to number sets, it's not the same even it seems so. The addition of Function classes and instances allow me to create type-specific methods without having to worry about class size (the number of methods will be small even if I have type-specific methods).
6
  • 1
    I think you're going to need to be more specific in your question, by way of an example. Based on your question in its current form, you're going to need all those method overloads anyway, so the question seems to boil down to "how do I organize my underlying method refactoring," which we can't answer without knowing more. Having large classes isn't necessarily a bad thing if the size is justified. Commented Apr 6, 2015 at 15:21
  • For an example of such a class, see here: msdn.microsoft.com/en-us/library/system.math(v=vs.110).aspx Commented Apr 6, 2015 at 15:23
  • I don't think you'll find a solution. You've correctly identified two things: 1) a common interface restricts you to "least common denominator" of functionality so to speak, and 2) "built-in" functions can often be implemented more efficiently, so the less of them you have, the more users of your code need to re-implement inefficiently. If you want good performance then you have to accept that you're going to have a minimum number of built-in operations for each numeric type. As you said, you don't want people having to implement their own transcendental functions.
    – Doval
    Commented Apr 6, 2015 at 15:25
  • @RobertHarvey I've modified the question, I don't know if it's enough concrete.
    – castarco
    Commented Apr 6, 2015 at 16:36
  • 1
    You've clarified what we already knew. Provide a code example, and then state your proposed refactoring. Perhaps we can comment on that. Commented Apr 6, 2015 at 16:37

1 Answer 1

1

You are way too much worrying about performance without actually having measured it.

Let your Number class just be a facade, and delegate each computational function to a internal helper class on its own, like a SquareRootCalculator, a SinCalculator, and so on. If you are really going to implement that calculations right from the ground, each of it is complex enough to justify a class on its own. Delegation has typically only a neglectable performance impact, so unless you this becomes a real, measurable problem, just try it out.

Moreover, since you wrote "the library's purpose is to be useful, not a toy" - did you consider to use some of the already existing PHP libraries for big numbers or complex numbers? Or if there is none which suits your needs, did you consider to encapsulate one an older, mature library written in C for this task (I am sure there are some very good and very fast libraries available for free)? That would not only prevent your PHP classes from becoming too complex, but also prevent you from reinventing the wheel.

4
  • I've already measured it, creating intermediate objects is slow. In any case, I've arrived to the conclusion that I'll abstract the function concept in Function classes. Those classes will have their own inner representations, the "translation" will add overhead, but less than creating intermediate objects in every algorithm step. Using existent PHP libraries isn't an option since my library is one of the most complete libraries in this area, and I want to extend it to "copy" many of Sagemath's (Python) features (but also implement other unique features not already implemented in Sagemath).
    – castarco
    Commented Apr 7, 2015 at 6:37
  • @castarco: well, I don't know what you measured in comparison to what, and how many "object creations" you expect per function call (I was thinking of one helper object per type of calculation, could be reused for many calculations of the same type). But maybe that is exactly what you have in mind with your Funtion classes? So is your problem solved that way, or do you still have a question?
    – Doc Brown
    Commented Apr 7, 2015 at 7:17
  • my worry about objects creation is because intermediate results, not because creating a "function object". Computing something like sin, or cos involves working with series (infinite sums). Evidently the computation stops before reaching the infinite step, but in any case this involves a lot of intermediate results (I work with arbitrary precision). I think I'll follow the described path (I've modified the question), but I still have doubts about the better way of defining a shared interface for Functionobjects.
    – castarco
    Commented Apr 7, 2015 at 12:57
  • @castarco: AFAIK Sagemath is build using NumPy, and NumPy is implemented using C code for performance reasons. Did you consider to follow a similar path?
    – Doc Brown
    Commented Apr 7, 2015 at 13:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.