Tuesday, 7 February 2012

JDK helper Math methods

Update 2013-08-16: The three methods discussed below - incrementExact, decrementExact and negateExact - have all made it into JDK 8.

How far should simple helper methods go in the JDK? Specifically for Maths.

JDK helper Math methods

A discussion is occurring on the OpenJDK core-libs list to provide some maths helper methods. But there is a disagreement as to which methods should be provided.

The following methods appear to be going in (to java.math.Math, as static methods):

  • addExact(int, int)
  • subtractExact(int, int)
  • multiplyExact(int, int)
  • toIntExact(long)

All the methods are intended to throw an exception on overflow, so you would use addExact instead of a + b if you care that the result fits into an int.

Furthermore, some of these methods are designed such that the JVM may be able to provide better low-level implementations dependent on the processor. The same methods with long as the parameter type are also included.

The discussion has arisen around some additional methods:

  • negateExact(int)
  • incrementExact(int)
  • decrementExact(int)

These are currently going to be rejected on the grounds that "they don't pull their weight in the API". So, this blog post is to canvas a wider opinion.

The proposed alternatives are either that the caller should just write the code themselves, by testing against Integer.MIN_VALUE, or to indirectly use one of the other methods. (If you're wondering how negating an integer could go wrong, read this.)

So, here are the three discussed options for negation:

  int x = ...
  
  // option A - dedicated method
  int a = Math.negateExact(x);
  
  // option B - code at the call-site
  if (x == Integer.MIN_VALUE) {
    throw new ArithmeticException();
  }
  int b = -x;
  
  // option C - indirectly use another API
  int a = Math.subtractExact(0, x);

And here are the options for increment:

  int x = ...
  
  // option A - dedicated method
  int a = Math.incrementExact(x);
  
  // option B - code at the call-site
  if (x == Integer.MAX_VALUE) {
    throw new ArithmeticException();
  }
  int b = x++;
  
  // option C - indirectly use another API
  int a = Math.addExact(x, 1);

For me, I find option A in both cases to be a lot clearer when reading the code, as it says three things - that I have understood the corner case, done something about it, and documented that thinking via the inserted method. Sure there is more code in the library (the JDK) to enable this, but isn't that really the point of libraries? To provide the common code so we don't have to? The alternatives are far less appealing to me.

The counter argument is that the more methods that the JDK provides, the harder it is for users to find things. And the JDK gets larger (bigger jar file) as a result.

So, dear reader, its over to you :-) How far is too far when adding methods to the JDK? Should negate, increment and decrement be provided in the JDK to help protect us against errors, or can/should we use the alternatives above?

19 comments:

  1. As someone who has used such methods in practice, I strongly support their addition to the JDK.

    ReplyDelete
  2. This is one of the smallest corner cases I can imagine in the Java world. If a developer doesn't understand the implications of binary representations of large numbers beyond +-(2 ^ (bit-width - 1)), it's just a but more to learn. And they would have to learn that in order to use these proposed functions anyway.

    I'm much more worried about the lack of progress regarding multi-tenant VMs and other useful features, like cleaning up Java from useless deprecated features.

    ReplyDelete
  3. Since the above seems to focus on integer types, how about just getting a log2 implementation? It is surprising this does not already exist, given its pervasiveness within CS and algorithms (bit-counting, binary search, compression etc.).

    The current JDK has no log2(...) but recommends using Math.log(x)/Math.log(2), even if it is 10+ times slower than a more generic Java implementation and 15+ times slower than an optimized C implementation.

    ReplyDelete
  4. I'd like to see the extra methods added, but it would make sense to add them to a separate class (e.g. java.lang.ExactMath). This would enable you to drop the "Exact" suffix from the method names and would match the approach taken by the existing StrictMath class.

    ReplyDelete
  5. I support Ian Philils idea. Put them in a separate class, suitable for static imports (but keep the explicit names).

    These are small utility methods that don't add to the JRE size, only to size of the API documentation, and putting them in a separate class should remedy that.

    There will always be developers that are "incompetent" in some areas. Providing methods like these will make life simpler for them and especially for their co-workers and customers.

    And yes, we should have the log2-function as well, and more. Putting them in a Logs class would probably make sense.

    ReplyDelete
  6. These methods all seem pretty niche. I assume they're mostly limited to HPC cases where performance and reliability is a problem. If you need them, surely you can write your own in all of ten minutes? Having them written in Java and part of the API isn't going to make them faster is it?

    ReplyDelete
  7. I agree with Ian Phillips, when I first heard about these methods I wondered why it wasn't put in a dedicated class.

    As for the mentioned methods, I think negateExact() has some value, but the increment/decrement case is better handled with addExact/substractExact (option C).

    ReplyDelete
  8. Wouldn't it make more sense to add these as static methods on Integer / Long like the planned support for unsigned arithmetic?

    I think option C would be ok, implementation-wise I hope that hotspot recognizes the underlying code patterns instead of the method calls, so the open-coded version would still have the same performance.

    If there are dedicated methods then you might also consider a method `absExact`.

    ReplyDelete
  9. @Rubin, when building a skyscraper, the foundations matter! While junior developers may not know about overflow, the presence of a method in code they read added previously by a senior developer is helpful in the learning process. They can go and look at the Javadoc, and understand why the senior dev used it, and thus enhance their own skills.

    @Casper, there are many missing methods in the JDK, log2 looks like one of them.

    @Someone, not HPC, but anyone that cares when writing an API to ensure that bad inputs don't yield bad outputs. In my case, its most things like adding a number of days to a date, and ensuring it doesn't wrap into a date in the far past! The add/subtract/multiply methods can be optimised and thus go faster.

    @Ian,dpnmn, Emmanuel, having a separate class might be a good idea. The current plan is to duplicate these methods on Math and StrictMath, yet they'll do the same thing. The view to have only negate in the JDK and not increment/decrement is one worthy of more comments.

    ReplyDelete
  10. What is the plan in regards to extension methods? Ideally I'd like to be able to bring ExtendedMath into scope and operate naturally on the types themselves. This works out great in the C# world and composes rather nicely.

    ReplyDelete
  11. Option A!

    Especially in utility classes, more methods are better. Plus, a method does a better job of documenting intentions and behavior than the Java Language Spec, which is very disconnected from code.

    ReplyDelete
  12. I have no problem with adding methods that help developers - especially junior developers - write better, more correct code.

    I'd also like to suggest consideration of other helper methods in this vein such as:

    Long x = ...
    if (StrictMath.canCastToInt(x) {
    switch ((int) x)...
    }

    ReplyDelete
  13. java.lang.Math
    java.lang.StrictMath
    and now
    java.lang.ExactMath

    not sure, if people are still going to understand which to use where?;-/
    Unfortunately even many people at Oracle or in the OpenJDK can't fully cope with modularization but if any of that is targeted towards Java 8 and not merged back into Java 7 I would prefer to see that in a more modular version of java.math.

    Stephen also suggested future improvements over BigDecimal, maybe also for the "math" package. Clogging java.lang with yet another *Math class does not sound like a good idea though.

    ReplyDelete
  14. Regarding the need for these methods, compared to all the ridiculous methods like ulp() or IEEEremainder() already in Math and StrictMath a few more may not make it much worse.

    However, I don't really understand the value of some. E.g. why would Long.intValue() backed by a given long value return a less precise int than toIntExact(long)??
    I heard somebody say certain APIs are not going to satisfy 100% of all users, but I can't estimate the percentage in huge demand for such helper methods.

    I guess there are people who could think of better ways the Math vs. StrictMath misery may have been solved, than a blunt Copy&Paste with identical method signature, but otherwise no proper way of chosing a method, but not having to care if I call either one or the other class. Seems ugly.

    ReplyDelete
  15. @Casper, Java isn't getting extension methods. Its getting default implementations of interface methods.

    @Werner, toIntExact(long) checks to see if the long value is larger than will fit into an int.

    ReplyDelete
  16. I strongly support these methods addition as long as these distinct from other available JDK methods.

    ReplyDelete
  17. I was bit surprised to find inverse of hyperbolic functions missing from java Math even though the hyperbolic functions are implemented, for example atanh is missing. Having such functions implemented would seem more valuable to me than these helper methods being discussed.

    Gary

    ReplyDelete
  18. get 'em all in! too bad you can't just retrofit them to all existing code, although I assume that < 0,0001% of cases actually use the under/overflow of numbers.

    ReplyDelete
  19. The truth is, I don't like any of the proposed choices that much. Math expressions should be implemented with operators, that we already have in the language. Why not use annotations?

    // option D - Use annotations
    @ExactMath
    int a = -x;

    // option D - Use annotations
    @ExactMath
    int a = x++;

    @ExactMath could be applied to a whole method, and everything inside the method should be compiled using the "exact" semantics.

    Rivas

    ReplyDelete