Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

This might seem like an overly simple question, but I need to specify custom plot tick marks as integers (no trailing decimal point) if they are approximately integers, but not if they are not. Using Rationalize on all the tick values won't work because I don't want ticks in the form of $\frac{3}{2}$.

Consider:

roundif = If[Chop[# - Floor[#]] == 0, Rationalize[#], #] & 

Some tests to show it works as intended:

roundif /@ {-1., -1, 0, 0.5,  1500, 1501., 1501.2}

(* {-1, -1, 0, 0.5, 1500, 1501, 1501.2} *)

roundif /@ Range[-3, 3, 0.5]

(*  {-3, -2.5, -2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3} *)

(Of course, I could make it a normal SetDelayed function and make its Attributes include Listable.)

Timing seems to be linear in the length of the list and the number of times it is performed.

testdata = Range[-30, 30, 0.5];   
Do[roundif /@ testdata, {10000}]; // AbsoluteTiming    
{5.8656000, Null}

Is this the most efficient way to do this? Have I missed some subtlety?

share|improve this question
3  
Is this likely to be a bottle neck in your application? –  Ajasja Jun 26 '12 at 8:21
    
@Ajasja, well, no, I'm more concerned that I've missed some subtle edge case. –  Verbeia Jun 26 '12 at 8:26
add comment

5 Answers

up vote 19 down vote accepted

Stan Wagon presents a little utility function in his book Mathematica in Action called IntegerChop[]. Here's a slightly wrinkled version:

IntegerChop = With[{r = Round[#]}, r + Chop[# - r]] &;

You might wish to do comparisons yourself (the computer I am using does not have Mathematica).


Here are some benchmarks:

Do[roundif /@ testdata, {10000}]; // AbsoluteTiming  
(* ==> {4.7382710, Null} *)

Do[IntegerChop /@ testdata, {10000}]; // AbsoluteTiming
(* ==> {4.6512660, Null} *)

So basically no difference.

share|improve this answer
4  
@Ajasja Since Round and Chop are listable, IntegerChop also is, so you could do IntegerChop[testdata], which might be a bit faster –  rm -rf Jun 26 '12 at 15:05
    
@R.M quite right, and on my tests, mapping it (/@) is an order of magnitude slower than just using the function (@) or IntegerChop[data]. –  Verbeia Jun 27 '12 at 10:09
add comment

EDIT

As Chris pointed out Floor used this way fails for cases where the value is slightly less than a whole number, whereas Round works. I shall edit the remainder of my answer to correct this oversight.


If you are using x == 0 you shouldn't need Chop since it is already making a numeric comparison:

If[# - Round[#] == 0, Round[#], #] &

Or simply:

If[Round@# == #, Round@#, #] &

The code from Jerry's answer is actually faster with Function rather than With as there is less overhead:

With[{r = Round[#]}, r + Chop[# - r]] & /@ Range[0, 1*^6, 0.5]; // Timing

# + Chop[#2 - #]&[Round@#, #] & /@ Range[0, 1*^6, 0.5];        // Timing

{3.697, Null}

{2.683, Null}

Either form is built from Listable functions and is therefore listable, able to be applied directly to the list without Map:

With[{r = Round[#]}, r + Chop[# - r]] & @ Range[0, 1*^6, 0.5]; // Timing

# + Chop[#2 - #]&[Round@#, #] & @ Range[0, 1*^6, 0.5];        // Timing

{0.499, Null}

{0.483, Null}

share|improve this answer
    
I hadn't even read the last part of your answer, and missed the simplest and fastest solution, and started compiling. Shameful +1!! –  Rojo Jun 26 '12 at 14:35
    
@Rojo Thanks, and sorry. :-o I'll try to make the best part of my answers more visible in the future. –  Mr.Wizard Jun 27 '12 at 1:54
1  
Is your edited function significantly different from IntegerChop? –  TomD Jun 27 '12 at 16:51
    
@TomD I updated my answer; I hope it is now of more value than before. –  Mr.Wizard Jun 30 '12 at 6:25
add comment

Ok, if you want it faster still, and your close to integer numbers are machine-size integers - here are two equivalent implementations - in Mathematica compiled to C, and Java. It is an interesting problem to compare performance, we will observe that Java code is speed-equivalent to C code here, modulo small extra time needed for data transfer.

The idea is to obtain a list of integers from those numbers close to ones, and a list of their positions (this is close in spirit to what @Rojo did in his now deleted answer). But then, I will create a copy of the original list and modify it in-place with Part. So, our top-level function is then

ClearAll[roundClose];
roundClose[data_, f_] :=
  Module[{copy = data},
   (copy[[#[[2]]]] = #[[1]]) &[ f[copy]];
   copy];

where f is a function which returns a list {ints, positions}.

Using Compile

Here is a function using Compile:

fn = 
  Compile[{{data, _Real, 1}},
    Module[{i = 1, ctr = 0, ints = Table[0, {Length[data]}], 
        pos =  Table[0, {Length[data]}]},
      Do[
        If[data[[i]] == Floor[data[[i]]],
          ints[[++ctr]] = Round[data[[i]]];
          pos[[ctr]] = i
        ],
        {i, Length[data]}
      ];
      {Take[ints, ctr], Take[pos, ctr]}
    ],
    CompilationTarget -> "C",
    RuntimeOptions -> "Speed"]

Using Java

Assuming that we have the Java reloader loaded, we compile this class:

JCompileLoad@
"import java.util.Arrays;

public class RoundCloseToInteger{
    public static int [][] roundClose(double [] nums ){
        int[] resultNums = new int[nums.length];
        int[] resultPos = new int[nums.length];
        int ctr = 0;        
        for(int i=0;i<nums.length;i++){
            double num = nums[i];           
            if(num ==((double)Math.floor(num))) {               
                 resultNums[ctr]=(int)Math.round(num);
                 resultPos[ctr++]=i+1;          
            }
        }
        resultNums = Arrays.copyOf(resultNums,ctr);
        resultPos = Arrays.copyOf(resultPos,ctr);
        return new int[][]{resultNums,resultPos};       
    }
 }"

The function to be used is then RoundCloseToInteger`roundClose.

Benchmarks

Here is a test data:

ld = Range[0, 1*^6, 0.5];

Testing now:

(r1 = Chop[# - Floor@#] + Floor@# &@ld);//AbsoluteTiming
(r2 = roundClose[ld,RoundCloseToInteger`roundClose]);//AbsoluteTiming
(r3 = roundClose[ld,fn]);//AbsoluteTiming

(*
   {1.6113282,Null}
   {0.5341797,Null}
   {0.4873047,Null}
*)

r1==r2==r3

(*  True  *)

Remarks

We get roughly the same 3x speed-up with both compiled to C and Java versions, w.r.t. the code of @Mr.Wizard. The reason for it is that, for such light-weight operations as Floor or Round, the time scales linearly with the numbers of runs through the list, which is 3 for the code of @Mr.Wizard and only 1 for the present code.

We can see that for such long lists, Java code is pretty much speed-equivalent to C code generated by Compile. One can experiment with data transfer and confirm that the timing difference is of the same order as needed to transfer back and forth the data. However, the smaller the size of the list, the more overhead will be encoutered for java calls, in proportion to the total running time.

I included the Java solution because it is a good and simple case study to see the relative speed on (Compile-generated) C vs Java, in a simple setting. To my mind, this shows that Java is a viable alternative. One advantage of Java is that it is cross-platform, meaning that once you compiled a given class on some machine, you can bring it to a computer not equipped with C compiler, and it will run there, and also, you won't face a compilation to C overhead.

share|improve this answer
    
+1. Btw, do you see a better way to do the simple following operation than what I did with Extract? Given 3 lists of equal length, build an equal sized fourth list such that the third is an indicator of whether to take the element from the first or second list. e.g l1={a, b, c};l2={aa, bb, cc}; indicator={1, 2, 1}; output is {a, bb, c} –  Rojo Jun 26 '12 at 23:19
    
@Rojo Thanks. How about Unitize[indicator - 2]*l1 + Unitize[indicator - 1]*l2? –  Leonid Shifrin Jun 26 '12 at 23:28
    
Thanks! That was the kind of code I had the hunch I wasn't seing –  Rojo Jun 26 '12 at 23:31
    
Great answer; +1! I was recently surprised to find that scimark2 gives significantly higher benchmark numbers running under Java 7 than the C version of the same code does after being compiled with gcc 4.6.1 using optimized switches. It looks like the days of Java (and other JVM languages such as Scala or Clojure) being slower than C are now just a memory. –  Oleksandr R. Jun 28 '12 at 23:49
    
@OleksandrR. Thanks! As to Java vs C, I think the reasons may include better optimizations possible for HotSpot JIT (because it has all the run-time information), and also modern garbage collectors may be superior to manual memory management for complex programs. A similar (in terms of optimization) situation happens with Python used with the PyPy project, since they are able to optimize globally, while C compilers can not optimize across the libraries boudaries. –  Leonid Shifrin Jun 29 '12 at 9:02
show 2 more comments

I like this for readability:

roundif = IntegerPart@# + Chop@FractionalPart@# &

It's also listable and fairly fast.

share|improve this answer
add comment

Some of the above don't work in some cases due to machine approximation, e.g.

x = 6250*0.292

1825.

If[# - ⌊#⌋ == 0, Round@#, #] &[x]

1825.

Chop[# - ⌊#⌋] + ⌊#⌋ &[x]

1825.

IntegerPart@# + Chop@FractionalPart@# &[x]

1825.

But Stan Wagon's method works:

With[{r = Round[#]}, r + Chop[# - r]] &[x]

1825

share|improve this answer
2  
Good catch. I didn't think this through very well as my version misses values that are slightly less than a whole number. –  Mr.Wizard Jun 27 '12 at 9:43
2  
Very well spotted, and shows the flaw in my original version as well. –  Verbeia Jun 27 '12 at 10:10
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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