This is a quote from SICP about coercion. This section talks about the arithmetic package with ordinary numbers, rational numbers and complex numbers and doing cross type operations on them (E.G. adding a complex number to an ordinary number.)
This coercion scheme has many advantages over the method of defining
explicit cross-type operations, as outlined above. Although we still
need to write coercion procedures to relate the types (possibly N^2
procedures for a system with N types), we need to write only one
procedure for each pair of types rather than a different procedure for
each collection of types and each generic operation.
I'm confused on this line:
"possibly N^2 procedures for a system with N types"
Let's take the arithmetic package example. The operations that deel with two ordinary numbers (scheme-number scheme-number), two rational numbers (rational rational) and two complex numbers (complex complex) are the same types, so they are not included in the coercion procedures.
We have three types, these are the coercion procedures I can think of with just two arguments.
(scheme-number rational)
(scheme-number complex)
(rational scheme-number)
(rational complex)
(complex scheme-number)
(complex rational)
These are not n^2 coercion procedures. There are only six coercion procedures here, not 9. I think I'm not really understanding this part of the text at all. Can someone explain what I missed?
Finally, here's a footnote regarding this part of the text.
If we are clever, we can usually get by with fewer than N^2 coercion
procedures. For instance, if we know how to convert from type 1 to
type 2 and from type 2 to type 3, then we can use this knowledge to
convert from type 1 to type 3. This can greatly decrease the number of
coercion procedures we need to supply explicitly when we add a new
type to the system.'
From what I understand if we can convert an ordinary number to a rational number, then convert that rational number into a complex number, we wouldn't need a procedure that converts an ordinary into a complex. Is that right?
Can anyone explain this more clearly?
The first part I understood as O(N^2): if there are 10 types, we'll need circa 100 operations (in reality, 90). As for the second part, you are right: we can build up a composable coercion. However, as far as I could think of its implementation, this will require building up a directed graph with nodes = types and edges = coercions. And then finding a right coercions will involve walking through the graph to find the path from one node to another (not cheap!).
PS. To make things even more complicated: both complex and rationals can act as containers for other types, e.g. complex of rationals, complex of integers, rational of integers (simplest version), rational of polynomials, etc. Then the question of coercion becomes even worse: consider adding 1+2i, 2/3 and 3.0 - first everything needs to be converted into their respective complex representations (1+2i, 2/3+0/1i, 3.0+0.0i) and only then coerce it all to floats inside complex... TL;DR: numbers coercion is a nightmare, I'm glad I don't have to do it myself!
Related
Integer comparison, although seemingly a simple matter, can involve some unexpected implications, hard to notice from the code itself, for the unskilled eye. Take the following piece of code for example: -1 > 10U;. Applying the rules for implicit integral conversion (that I think were introduced in C++20), it turns out to be equivalent to static_cast<unsigned>(-1) > 10U; (For 32-bit unsigned integer -1 is equivalent to 0xFFFFFFFF).
C++20 introduces std::cmp_* functions to achieve correct comparison behavior even between integer values of different signedness and size. Before c++20, when you had to write an algorithm that does some integer comparisons for some purpose, either you had to write your own comparison functions, or use integer types of the same signedness (or play along with the implicit integral conversions, that I think were implementation-dependant).
This is where you are faced with the design choice of using the same signedness for all integers that compare together (read: between them), or use the new functions from C++20. For example, sometimes it makes perfect sense to use an unsigned type (like std::size_t) to represent sizes (that are never negative), but then you might need to calculate the difference between the sizes of two objects, or make sure they differ by at most some amount (again, never negative).
Using the same signedness for all types that compare together in this scenario would mean to use signed integers, because you need to be able to compute the difference of two of these values without knowing which one is bigger (1 - 2 = 0xFFFFFFFF). But that means you lose half of the possible integer representations, paying for a feature (a sign bit) you never really use.
Using the C++20 comparison functions sacrifices some ease of reading, and also demands you to write a little more code (x <= 7 vs std::cmp_less_equal(x, 7)). Besides from these facts, are there other differences or advantages that arise from the use of one alternative over the other? Are there any situations where one of them would be preferable? I'm specially interested in performance-critical code. What impact does this choice have on performance?
So I have this problem: I am making calculations with each element in a vector. Depending on the numbers in the vector the resulting vector may contain rationals, floats, scientific floats, big integers, integers. I need to convert all of them to floating point numbers or rounded integers. The resulting text is an SVG that will be sent to the client and those numbers are part of a path. Can I do it with something built in or should I roll my own function?
The problems with format are that it is a thin wrapper around the java Formatter class. This means that big ints are not handled since clojure has its own implementation.
On the other hand cl-format (which should be the primary choice actually) formats everything well except for rationals - 4/5 is converted to 4/5.0. Maybe I am doing something wrong with cl-format.
I tried type hinting the whole vectors (which will be needed anyway) as doubles, but the results keep returning as rationals.
Please help if I am missing something.
It is not the job of a formatter to convert your datatypes, you should ensure the data has the type you want to print before handing it to the formatter.
Type hinting is not a type conversion or coercion, it is a hint to the compiler about what args are most likely to come in at runtime.
The function you want is double.
user=> (double 42)
42.0
user=> (map double [1.0 1 1.1M 2/3])
(1.0 1.0 1.1 0.6666666666666667)
Also, if you are type hinting the entire vector, you should not be using a vector, you should be using double-array. Vectors are not specialized in any way by the type of their contents.
Since there are two ways of implementing an AP fractional number, one is to emulate the storage and behavior of the double data type, only with more bytes, and the other is to use an existing integer APA implementation for representing a fractional number as a rational i.e. as a pair of integers, numerator and denominator, which of the two ways are more likely to deliver efficient arithmetic in terms of performance? (Memory usage is really of minor concern.)
I'm aware of the existing C/C++ libraries, some of which offer fractional APA with "floats" and other with rationals (none of them features fixed-point APA, however) and of course I could benchmark a library that relies on "float" implementation against one that makes use of rational implementation, but the results would largely depend on implementation details of those particular libraries I would have to choose randomly from the nearly ten available ones. So it's more theoretical pros and cons of the two approaches that I'm interested in (or three if take into consideration fixed-point APA).
The question is what you mean by arbitrary precision that you mention in the title. Does it mean "arbitrary, but pre-determined at compile-time and fixed at run-time"? Or does it mean "infinite, i.e. extendable at run-time to represent any rational number"?
In the former case (precision customizable at compile-time, but fixed afterwards) I'd say that one of the most efficient solutions would actually be fixed-point arithmetic (i.e. none of the two you mentioned).
Firstly, fixed-point arithmetic does not require any dedicated library for basic arithmetic operations. It is just a concept overlaid over integer arithmetic. This means that if you really need a lot of digits after the dot, you can take any big-integer library, multiply all your data, say, by 2^64 and you basically immediately get fixed-point arithmetic with 64 binary digits after the dot (at least as long as arithmetic operations are concerned, with some extra adjustments for multiplication and division). This is typically significantly more efficient than floating-point or rational representations.
Note also that in many practical applications multiplication operations are often accompanied by division operations (as in x = y * a / b) that "compensate" for each other, meaning that often it is unnecessary to perform any adjustments for such multiplications and divisions. This also contributes to efficiency of fixed-point arithmetic.
Secondly, fixed-point arithmetic provides uniform precision across the entire range. This is not true for either floating-point or rational representations, which in some applications could be a significant drawback for the latter two approaches (or a benefit, depending on what you need).
So, again, why are you considering floating-point and rational representations only. Is there something that prevents you from considering fixed-point representation?
Since no one else seemed to mention this, rationals and floats represent different sets of numbers. The value 1/3 can be represented precisely with a rational, but not a float. Even an arbitrary precision float would take infinitely many mantissa bits to represent a repeating decimal like 1/3. This is because a float is effectively like a rational but where the denominator is constrained to be a power of 2. An arbitrary precision rational can represent everything that an arbitrary precision float can and more, because the denominator can be any integer instead of just powers of 2. (That is, unless I've horribly misunderstood how arbitrary precision floats are implemented.)
This is in response to your prompt for theoretical pros and cons.
I know you didn't ask about memory usage, but here's a theoretical comparison in case anyone else is interested. Rationals, as mentioned above, specialize in numbers that can be represented simply in fractional notation, like 1/3 or 492113/203233, and floats specialize in numbers that are simple to represent in scientific notation with powers of 2, like 5*2^45 or 91537*2^203233. The amount of ascii typing needed to represent the numbers in their respective human-readable form is proportional to their memory usage.
Please correct me in the comments if I've gotten any of this wrong.
Either way, you'll need multiplication of arbitrary size integers. This will be the dominant factor in your performance since its complexity is worse than O(n*log(n)). Things like aligning operands, and adding or subtracting large integers is O(n), so we'll neglect those.
For simple addition and subtraction, you need no multiplications for floats* and 3 multiplications for rationals. Floats win hands down.
For multiplication, you need one multiplication for floats and 2 multiplications for rational numbers. Floats have the edge.
Division is a little bit more complex, and rationals might win out here, but it's by no means a certainty. I'd say it's a draw.
So overall, IMHO, the fact that addition is at least O(n*log(n)) for rationals and O(n) for floats clearly gives the win to a floating-point representation.
*It is possible that you might need one multiplication to perform addition if your exponent base and your digit base are different. Otherwise, if you use a power of 2 as your base, then aligning the operands takes a bit shift. If you don't use a power of two, then you may also have to do a multiplication by a single digit, which is also an O(n) operation.
You are effectively asking the question: "I need to participate in a race with my chosen animal. Should I choose a turtle or a snail ?".
The first proposal "emulating double" sounds like staggered precision: using an array of doubles of which the sum is the defined number. There is a paper from Douglas M. Priest "Algorithms for Arbitrary Precision Floating Point Arithmetic" which describes how to implement this arithmetic. I implemented this and my experience is very bad: The necessary overhead to make this run drops the performance 100-1000 times !
The other method of using fractionals has severe disadvantages, too: You need to implement gcd and kgv and unfortunately every prime in your numerator or denominator has a good chance to blow up your numbers and kill your performance.
So from my experience they are the worst choices one can made for performance.
I recommend the use of the MPFR library which is one of the fastest AP packages in C and C++.
Rational numbers don't give arbitrary precision, but rather the exact answer. They are, however, more expensive in terms of storage and certain operations with them become costly and some operations are not allowed at all, e.g. taking square roots, since they do not necessarily yield a rational answer.
Personally, I think in your case AP floats would be more appropriate.
I have developed my own BigInteger library in C++, for didactic purpose, initially I have used base 10 and it works fine for add, subtract and multiply, but for some algorithms such as exponentiation, modular exponentiation and division it appears to be more appropriate use base 2.
I am thinking restart my project from scratch and I would know what base do you think is more adequate and why?. Thanks in advance!
If you look at most BigNum type libraries you will see that they are built on top of the existing "SmallNum" data types. These "SmallNum" data types (short, int, long, float, double, ...) are in binary for too many reasons to count. Rather than a vector of base 10 digits, your code will be much faster (much, much, much faster!) if work with a vector of (for example) unsigned ints.
This is one of those places where performance does count. Suppose you use a BigNum package to solve a problem that could be solved without resorting to BigNums. Even the best BigNum library will be much slower (much, much slower) than will a simplistic, non-BigNum approach. If you try to solve a problem that is beyond the bounds of the standard representations that performance penalty will make things even worse.
The best way to overcome this inherent penalty is to take as much advantage of the builtin types as you possibly can.
A base the same as the word size on your target machine, where you have word x word = doubleword as a primitive. The primitive operations work out neatly in terms of machine instructions.
A base 10 representation makes sense for a BigDecimal type, where you need to need to represent decimal fractions exactly (for financial calculations and the like).
I can't see much benefit to using a base 10 representation for a BigInteger. It makes string conversion really easy, but at the expense of making mathematical operations much more complicated. This doesn't seem like a good tradeoff in most cases.
While writing several math utilities I bumped into need to implement generic utility that can perform comparisons between any two fundamental arithmetic types. As I began coding, it became clear that this operation is not as straightforward as it seems, since I need correct handling of corner cases, especially when the types have different precision, i.e. rounding strategy during conversion between types becomes important. Consider:
float a1 = 4.8f;
int a2 = 4;
assert(a2 != (int) a1); //fails erroneously since we truncated a1
float b1 = 40000000.0f; //can represent only 40000000 and 40000004 accurately
long b2 = 40000002;
assert(b1 != (float) b2); //fails erroneously since we now truncated b2
The above can be implemented using c++0x type traits to automatically select the appropriate algorithm according to the template arguments supplied to the comparison function. However, this is quite complex and there's quite a lot of places where bugs can creep, so I don't think inventing everything myself is worthwhile. Does anyone know a library that implements the above correctly?
You may want to look into GNU's MP Bignum library at http://gmplib.org/. From their page:
GMP is a free library for arbitrary precision arithmetic, operating on
signed integers, rational numbers, and floating point numbers. There
is no practical limit to the precision except the ones implied by the
available memory in the machine GMP runs on. GMP has a rich set of
functions, and the functions have a regular interface.
GMP is carefully designed to be as fast as possible, both for small
operands and for huge operands. The speed is achieved by using
fullwords as the basic arithmetic type, by using fast algorithms, with
highly optimised assembly code for the most common inner loops for a
lot of CPUs, and by a general emphasis on speed.