Java Forum / General / November 2005
Math.abs
markelmel@yahoo.com - 09 Nov 2005 13:37 GMT Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math definition absolut value is always positive. In this case, Math.abs returns negative value. Why it is implemented in Java in this way? Can anybody explain me this?
Thomas Hawtin - 09 Nov 2005 13:50 GMT > Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math > definition absolut value is always positive. In this case, Math.abs > returns negative value. Why it is implemented in Java in this way? Can > anybody explain me this? By maths, Integer.MAX_VALUE+1 > 0 as well.
If you were to write Math.abs, what value would return for Integer.MIN_VALUE?
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Thomas G. Marshall - 09 Nov 2005 18:06 GMT Thomas Hawtin coughed up:
>> Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math >> definition absolut value is always positive. In this case, Math.abs >> returns negative value. Why it is implemented in Java in this way? Can >> anybody explain me this? > > By maths, Integer.MAX_VALUE+1 > 0 as well. You mean, of course, by math as a /concept/ [only] that is true, right? Just checking.....
> If you were to write Math.abs, what value would return for > Integer.MIN_VALUE? > > Tom Hawtin
 Signature Puzzle: You are given a deck of cards all face down except for 10 cards mixed in which are face up. If you are in a pitch black room, how do you divide the deck into two piles (may be uneven) that each contain the same number of face-up cards? Answer (rot13): Sebz naljurer va gur qrpx, qrny bhg gra pneqf naq syvc gurz bire.
Daniel Dyer - 09 Nov 2005 13:53 GMT > Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math > definition absolut value is always positive. In this case, Math.abs > returns negative value. Why it is implemented in Java in this way? Can > anybody explain me this? This problem is mentioned in "Java Puzzlers" by Josh Bloch and Neal Gafter. It's because it is not possible to represent the absolute value of Integer.MIN_VALUE as a 32-bit integer (2147483647 is the highest positive int, which is one less than the absolute value of the lowest negative int, -2147483648).
So it is not possible for this method to return the right answer (unless the answer was promoted to a long, but even then the long version of the method still wouldn't work). It might be better if the method threw an exception in this case rather than failing silently.
Dan.
 Signature Daniel Dyer http://www.dandyer.co.uk
markelmel@yahoo.com - 10 Nov 2005 06:59 GMT Thanks very much. I my opinion it would be better if the method threw an exception rather then giving "wrong" answer.
Roedy Green - 10 Nov 2005 07:08 GMT >Thanks very much. >I my opinion it would be better if the method threw an exception rather >then giving "wrong" answer. To do that would make abs take maybe 5 times longer for something that almost never happens.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Roedy Green - 10 Nov 2005 07:09 GMT On Thu, 10 Nov 2005 07:08:16 GMT, Roedy Green <my_email_is_posted_on_my_website@munged.invalid> wrote, quoted or indirectly quoted someone who said :
>>Thanks very much. >>I my opinion it would be better if the method threw an exception rather >>then giving "wrong" answer. > >To do that would make abs take maybe 5 times longer for something that >almost never happens. if it is important to you, you can write your own abs or wrap Math.abs that behaves the way you want.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Benji - 10 Nov 2005 07:21 GMT > Thanks very much. > I my opinion it would be better if the method threw an exception rather > then giving "wrong" answer. But when you're working with ints, you fundamentally have to deal with overflow problems. just like Math.abs(MIN_INT) should be a positive number, MIN_INT-1 should be a negative number, but it's not. There are some languages that allow mathmatical operations that never overflow (at the expense of speed), but java is not one of them. It's very consistent of the abs function to give a technically incorrect answer.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Eric Sosman - 10 Nov 2005 17:43 GMT markelmel@yahoo.com wrote On 11/10/05 01:59,:
> Thanks very much. > I my opinion it would be better if the method threw an exception rather > then giving "wrong" answer. It would be easy to add a test to Math.abs(), but that wouldn't even scratch the surface of the "wrong answer" issue. What about `Integer.MIN_VALUE * 42'? Or what about `i + j'?
Computers that trap on integer overflow have been built, but they seem to have fallen out of fashion. Lacking hardware support, I put it to you that adding overflow-detecting code to every `int' calculation would slow every program down enormously. (Perhaps an optimizer could eliminate some of the checking, but I bet an awful lot would remain.) Also, since Java mostly lacks the `unsigned' types found in some other languages, Java relies on "silent overflow" to obtain some of their benefits.
 Signature Eric.Sosman@sun.com
Chris Uppal - 11 Nov 2005 15:42 GMT > Lacking hardware > support, I put it to you that adding overflow-detecting code > to every `int' calculation would slow every program down > enormously. Probably not "enormously". "Measurably", I think, is a fairer description. There are programming languages which don't suffer from integer overflow, and they are not hugely slower than Java.
It's waaaay too late now, but I think that using primitive types /by default/ was a mistake in Java's design (possibly swayed by a desire to run on resource-limited devices). "Proper", non-overflowing, real object, integers can be implemented pretty efficiently (as has been known for decades). And if the Java guys had wanted to allow primitive types /too/, as an optimised form available to careful and knowledgable programmers, then I don't see much wrong with that.
-- chris
Mark Thornton - 11 Nov 2005 19:53 GMT >>Lacking hardware >>support, I put it to you that adding overflow-detecting code [quoted text clipped - 14 lines] > > -- chris There was also a desire to have an easy migration from C/C++. Having basic arithmetic behave differently would have made this more difficult. Especially with those algorithms that take deliberate advantage of the way arithmetic works in such languages.
Mark Thornton
Chris Uppal - 12 Nov 2005 10:56 GMT > There was also a desire to have an easy migration from C/C++. Having > basic arithmetic behave differently would have made this more difficult. That sounds plausible.
> Especially with those algorithms that take deliberate advantage of the > way arithmetic works in such languages. Which makes the (bloody inconvenient -- at best) ommision of unsigned types even harder to comprehend.
/Signed/ bytes -- phah !
-- chris
Thomas G. Marshall - 12 Nov 2005 14:05 GMT Chris Uppal coughed up:
>> There was also a desire to have an easy migration from C/C++. Having >> basic arithmetic behave differently would have made this more difficult. [quoted text clipped - 11 lines] > > -- chris I agree, however, I have to wonder if they were attempting to (except for characters) stave off any issues re: signed + unsigned combinations. {shrug}
 Signature Sometimes life just sucks and then you live.
Chris Uppal - 13 Nov 2005 11:40 GMT [me:]
> > Which makes the (bloody inconvenient -- at best) ommision of unsigned > > types > > even harder to comprehend. [...]
> I agree, however, I have to wonder if they were attempting to (except for > characters) stave off any issues re: signed + unsigned combinations. I have heard that suggestion before, but for me there's no meat in it. The (perfectly real) problem arises in C because of silent conversion between signed and unsigned. But given the Java designers' track record, I don't think they'd have allowed that. I.e. converting between signed and unsigned representations of the same width would require an explicit cast, as would converting from signed to unsigned of any sizes.
Incidentally, another possible reason -- this has just occured to me as I write -- is be that they might have wanted to avoid doubling the number of arithmetic bytecodes. Pure speculation, of course...
-- chris
Thomas Hawtin - 13 Nov 2005 23:18 GMT >>>Which makes the (bloody inconvenient -- at best) ommision of unsigned >>>types >>>even harder to comprehend.
> Incidentally, another possible reason -- this has just occured to me as I > write -- is be that they might have wanted to avoid doubling the number of > arithmetic bytecodes. Pure speculation, of course... I think we did this a few months ago. Most arithmetic operations don't care if they work on signed or unsigned operands. You would need some way to handle testing/branching and conversion. An obvious solution is to emit the same bytecodes as if the code was written in Java. Array loads/stores could be doubled up, as load byte/boolean from array (baload) is.
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Chris Uppal - 14 Nov 2005 10:39 GMT [me:]
> > Incidentally, another possible reason -- this has just occured to me as > > I write -- is be that they might have wanted to avoid doubling the > > number of arithmetic bytecodes. Pure speculation, of course... > > I think we did this a few months ago. Most arithmetic operations don't > care if they work on signed or unsigned operands. Good point. Thanks for the correction.
-- chris
Hendrik Maryns - 14 Nov 2005 10:29 GMT Chris Uppal schreef: <snip>
>>Especially with those algorithms that take deliberate advantage of the >>way arithmetic works in such languages. [quoted text clipped - 3 lines] > > /Signed/ bytes -- phah ! As someone who only knows C(++) from school books and horror stories about memory leak, I wonder what the particular advantage of unsigned types is, except that a bit higher positive numbers fit in them (which seems not very big an advantage to me)?
H.
 Signature Hendrik Maryns
================== www.lieverleven.be http://aouw.org
Roedy Green - 14 Nov 2005 10:40 GMT On Mon, 14 Nov 2005 11:29:06 +0100, Hendrik Maryns <hendrik_maryns@despammed.com> wrote, quoted or indirectly quoted someone who said :
>As someone who only knows C(++) from school books and horror stories >about memory leak, I wonder what the particular advantage of unsigned >types is, except that a bit higher positive numbers fit in them (which >seems not very big an advantage to me)? 1. any 8-bit char processing almost always wants unsigned bytes.
2. Any endian fiddles requires unsigned bytes.
3. any cryptography work wants unsigned bytes. I can't think of a time when I used the signed feature of byte even once it my entire Java programming career.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Chris Uppal - 14 Nov 2005 13:36 GMT [me:]
> > Which makes the (bloody inconvenient -- at best) ommision of unsigned > > types even harder to comprehend. [quoted text clipped - 5 lines] > types is, except that a bit higher positive numbers fit in them (which > seems not very big an advantage to me)? If you stay entirely within the world of Java then, as you say, it probably doesn't make too much difference. At least the extra bit's worth of integer range isn't too important. Personally I really /hate/ having to do any kind of bit-twiddling with signed quantities -- it's unnatural, confusing, and hard to debug. Not everyone has to do much bit-twiddling, though, especially if they /are/ sticking entirely to a closed Java world.
But sticking to the Java world is an unreasonable requirement. It requires that you never need to exchange any data with the rest of the world, and never have to implement any kind of data format that was designed by someone not using Java, and never have to read books/articles/standard/etc which are not aimed specifically at a Java readership.
The rest of the world can express 4G in 32 bits. So they do so. The rest of the world can express 255 in 8 bits. So they do so. Dealing with such situations requires mangled Java code in one way or another.
-- chris
Chris Uppal - 14 Nov 2005 13:42 GMT I wrote:
> If you stay entirely within the world of Java then, as you say, it > probably doesn't make too much difference. At least the extra bit's > worth of integer range isn't too important. Personally I really /hate/ > having to do any kind of bit-twiddling with signed quantities -- it's > unnatural, confusing, and hard to debug. Forgot to add: also working with unsigned quantities (assuming they are suitable for the application) avoids problems like the one that gave rise to this thread. The greater mathematical tractability of unsigned quantities may be an advantage in some situations. I don't know if such situations occur any more frequently than the need for bit-twiddling, though.
-- chris
Thomas G. Marshall - 14 Nov 2005 13:45 GMT Chris Uppal coughed up:
> [me:] >>> Which makes the (bloody inconvenient -- at best) ommision of unsigned [quoted text clipped - 15 lines] > of bit-twiddling with signed quantities -- it's unnatural, confusing, and > hard to debug. And as horrifying as this may sound, you will often find engineers not even trained in the notion of sign extension when changing data type sizes. It can be a moderate danger to your maintenance.
> Not everyone has to do much bit-twiddling, though, > especially if they /are/ sticking entirely to a closed Java world. [quoted text clipped - 14 lines] > > -- chris
 Signature Unix users who vehemently argue that the "ln" command has its arguments reversed do not understand much about the design of the utilities. "ln arg1 arg2" sets the arguments in the same order as "mv arg1 arg2". Existing file argument to non-existing argument. And in fact, mv itself is implemented as a link followed by an unlink.
Roedy Green - 12 Nov 2005 04:08 GMT On Fri, 11 Nov 2005 15:42:22 -0000, "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly quoted someone who said :
>Probably not "enormously". "Measurably", I think, is a fairer description. >There are programming languages which don't suffer from integer overflow, and >they are not hugely slower than Java. How about "considerably".
quoting from http://mindprod.com/jgloss/overflow.html
Overflow occurs when the result of an integer multiply, add or subtract cannot fit in 32 bits. Java just quietly drops the high order bits. There is no exception. If you need to detect overflow, you would use long operands and examine the high order 32 bits of the result. If there was no overflow, they would be all 0 or all 1. To detect overflow from two longs would require testing the range of the operands before the operation and/or testing the sign of the result. Why does Java ignore overflow? Most computer hardware has no ability to automatically generate an interrupt on overflow. And some hardware has no ability to detect it. Java would have to explicitly test for it on every add, subtract and multiply, greatly slowing down production. Further ex-C programmers are very used to this cavalier ignoring of overflow, and commonly write code presuming that high order bits will be quietly dropped
The Pentium has hardware overflow detect but no hardware interrupt. So if Java were to support overflow detect, inside the JVM implementation would need to add a JO *jump on overflow" instruction after every add and subtract, and special code to look at the high order bits of the 32x32->64 bit multiply. 64/32->32 bit division might need special handling.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Chris Uppal - 13 Nov 2005 13:46 GMT > > Probably not "enormously". "Measurably", I think, is a fairer > > description. There are programming languages which don't suffer > > from integer overflow, and they are not hugely slower than Java. > > How about "considerably". I doubt it in theory, and there is plenty of counter-evidence in fact.
Consider adding two numbers. The numbers must have come from somewhere (2 logical machine operations). The result must go somewhere (1 logical machine op). The numbers must be added together (1 logical machine op). So that's 4 logical operations. Adding a branch-on-overflow would add one more logical machine operation. So, at this very abstract level, we see a 25% increase.
Next remember that no application spends all its time adding numbers together. So you should divide the above marginal cost by an application dependent scaling factor. I have real difficulty believing that the appropriate scaling would ever be less than about 2, and would nearly always be higher -- a /lot/ higher.
Already we are looking at small numbers. But now condsider that we are also working with real machines, with instuction piplining, speculative execution, branch prediction (and/or hinting), caching, and so on. I haven't tried to work through the details for any particular machine architecture (beyond my competance), but it seems highly unlikely that the /real/ underlying cost is anything like the 25% I derived above. In fact, to me it seems plausible that the marginal cost could be exactly zero in many cases (i.e specific sequences of JITer-emmited code), at least unless the numbers actually /did/ overflow (which would stall the pipeline).
I won't buy "considerably" without considerable evidence. I won't buy "measurably" without measurable evidence. I won't buy "enormously" at all ;-)
-- chris
Roedy Green - 13 Nov 2005 17:03 GMT On 13 Nov 2005 13:46:48 GMT, "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly quoted someone who said :
>Consider adding two numbers. If a machine has no overflow detect, I have posted the code you need to detect it. It is takes considerable overhead. Can you improve on that?
I am not talking about the ease of adding overflow detect in hardware. Obviously that is not that hard since you can do the entire operation and set the condition codes in one cycle for an add in Pentium.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Roedy Green - 09 Nov 2005 13:53 GMT >Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math >definition absolut value is always positive. In this case, Math.abs >returns negative value. Why it is implemented in Java in this way? Can >anybody explain me this? because there is no corresponding +ve. The way it was defined, it can be efficiently implemented without making a special case out of MIN_VALUE.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Richard F.L.R.Snashall - 09 Nov 2005 20:18 GMT > Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math > definition absolut value is always positive. In this case, Math.abs > returns negative value. Why it is implemented in Java in this way? Can > anybody explain me this? Get yourself a one's complement machine; it'll go away!;-)
Thomas G. Marshall - 09 Nov 2005 20:35 GMT markelmel@yahoo.com coughed up:
> Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math > definition absolut value is always positive. In this case, Math.abs > returns negative value. Why it is implemented in Java in this way? Can > anybody explain me this? To help you understand what is going on here, let's make up a 3 bit signed integer.
If it were unsigned, it would have as unsigned values, from 0 to 7:
000 001 010 011 100 101 119 111
Since it is signed, we need to have half of these represent negative numbers. At first, you might think that this would be from -4 to +4, but when including 0 this would be 9 values, and only 8 can fit in 3 bits. So it goes from -4 to +3, as follows:
100 101 110 111 000 001 010 011
Notice that the most significant bit is the left most bit and is also called the "sign bit" because if it's on, the number is negative.
Now given your example, look what would happen if we took the max value of 3....
011
and added 1 to it. The result would be a wraparound to -4!
100
Yikes. This means that your test of (max+1) is actually yielding a negative number!
Thomas G. Marshall - 09 Nov 2005 20:36 GMT Thomas G. Marshall coughed up:
> markelmel@yahoo.com coughed up: >> Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math [quoted text clipped - 8 lines] > > 000 001 010 011 100 101 119 111 Arg. clearly a quick typo. 119 is really 110. Figgers.
> Since it is signed, we need to have half of these represent negative > numbers. At first, you might think that this would be from -4 to +4, [quoted text clipped - 17 lines] > Yikes. This means that your test of (max+1) is actually yielding a > negative number! markelmel@yahoo.com - 10 Nov 2005 06:55 GMT Thanks very much. It is very good explanation.
Thomas G. Marshall napisal(a):
> Thomas G. Marshall coughed up: > > markelmel@yahoo.com coughed up: [quoted text clipped - 33 lines] > > Yikes. This means that your test of (max+1) is actually yielding a > > negative number! Eric Sosman - 10 Nov 2005 17:35 GMT markelmel@yahoo.com wrote On 11/09/05 08:37,:
> Why is Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE? By math > definition absolut value is always positive. In this case, Math.abs > returns negative value. Why it is implemented in Java in this way? Can > anybody explain me this? "By math definition" there is no such thing as a minimum integer at all. This should alert you to the fact that a Java `int' is not a mathematical integer. Knowing that, you should not expect an `int' to imitate all properties of an integer with perfect fidelity.
 Signature Eric.Sosman@sun.com
Richard F.L.R.Snashall - 10 Nov 2005 18:42 GMT > "By math definition" there is no such thing as a > minimum integer at all. This should alert you to the > fact that a Java `int' is not a mathematical integer. > Knowing that, you should not expect an `int' to imitate > all properties of an integer with perfect fidelity. Sure there is. The definition of the "integers" is simply a particular set of integers. The math operations of + and * are the inherited operations. What isn't inherited is the closed-ness of those operations.
Eric Sosman - 10 Nov 2005 19:54 GMT Richard F.L.R.Snashall wrote On 11/10/05 13:42,:
>> "By math definition" there is no such thing as a >>minimum integer at all. This should alert you to the [quoted text clipped - 3 lines] > > Sure there is. [...] Care to write it out for us? (When you've finished, I'll tack on a `- 1' at the end.)
 Signature Eric.Sosman@sun.com
Richard F.L.R.Snashall - 10 Nov 2005 20:30 GMT > Richard F.L.R.Snashall wrote On 11/10/05 13:42,: > [quoted text clipped - 8 lines] > Care to write it out for us? (When you've finished, > I'll tack on a `- 1' at the end.) For a two's complement machine, the machine integers are simply the intersection of the integers with the half-open interval [-2^(n-1),2^(n-1)), where n is the number of bits in the particular integer type. As such, a + and a * can be derived from those of the integers (or real numbers), provided the result is within the set. It's only when the "mathematical" answer is not in the set that we end up with all this nonsense and are forced to go to new definitions.
Oliver Wong - 11 Nov 2005 15:22 GMT >> Richard F.L.R.Snashall wrote On 11/10/05 13:42,: >> [quoted text clipped - 17 lines] > "mathematical" answer is not in the set that we end up > with all this nonsense and are forced to go to new definitions. So obviously the argument over whether or not there is a minimum integer arose because of a misunderstanding of what one meant by "integer" (i.e. was it the mathematical concept or the computer-science concept?)
What I wanted to bring up is that we don't need to "go to new definitions" in the case of the + (and -) operations if we consider the "machine-integers" to form a group.
I haven't examined it rigorously, but I believe all 3 (and a half) properties of a group hold:
1) Associativity: for all a, b, c in our group G: (a + b) + c = a + (b + c)
Trivially (?) true.
2) Identity: There is an element e such that for all a, e + a = a + e = a
In our case, e = 0.
3) Inverse element: For all a in G, there is an element b such that a + b = b + a = e.
This trivially is true for all elements greater than Integer.MIN_INTEGER. (i.e. if a != Integer.MIN_INTEGER, then the inverse of a is -a). I believe Integer.MIN_INTEGER is it's own inverse. Consider a 2-bit architecture, so the integers are -2, -1, 0 and 1. -2 + -2 is -2 decremented twice. -2 decremented once becomes 1. -2 decremented a second time becomes 0, which is e.
3.5) Closure: For all a and b in G, a + b is in G.
This is true assuming we allow standard Java overflow/underflow behaviour.
It seems that the integers and the multiplication does NOT form a group, as we don't have closure.
- Oliver
Richard F.L.R.Snashall - 11 Nov 2005 18:45 GMT > This is true assuming we allow standard Java overflow/underflow > behaviour. > > It seems that the integers and the multiplication does NOT form a group, > as we don't have closure. But then Ada (apologies if that is a cuss word in this group:-0) calls it modular arithmetic.
Roedy Green - 12 Nov 2005 04:11 GMT On Fri, 11 Nov 2005 13:45:20 -0500, "Richard F.L.R.Snashall" <rflrs@notnotrcn.com> wrote, quoted or indirectly quoted someone who said :
>> It seems that the integers and the multiplication does NOT form a group, >> as we don't have closure. It could have been handled by defining an overflow int to be a long result. But that would have slowed down the basic operations considerably
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Googmeister - 12 Nov 2005 15:57 GMT > On Fri, 11 Nov 2005 13:45:20 -0500, "Richard F.L.R.Snashall" > <rflrs@notnotrcn.com> wrote, quoted or indirectly quoted someone who [quoted text clipped - 6 lines] > result. But that would have slowed down the basic operations > considerably In mathematics (set of all integers, *) is not a group either because of the inverse operation. However, (set of all integers, +, *) is a commutative ring.
In Java, (int, *) is closed. It's not a group because of the inverse operation. However, (int, +, *) is a commutative ring since it does integer arithmetic mod 2^32 (where the elements are named -2^31 to 2^31 - 1 instead of the usual 0 to 2^32 - 1).
Oliver Wong - 14 Nov 2005 15:45 GMT > In mathematics (set of all integers, *) is not a group either because > of the inverse operation. Yes, I realized that (slapping my forehead), a few minutes after submitting my post.
> In Java, (int, *) is closed. It's not a group because of the > inverse operation. However, (int, +, *) is a commutative > ring since it does integer arithmetic mod 2^32 (where the > elements are named -2^31 to 2^31 - 1 instead of the usual > 0 to 2^32 - 1). So you see? Java's arithmatic doesn't require all sorts of weird new definitions. You just have to realize that it's working with finite rings instead of infinite ones.
- Oliver
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|