2

As pointed out in this post, Mathematica has a special version of Round that

Round rounds numbers of the form x.5 toward the nearest even integer.

A comment by David G suggest that why not have differnt options Direction → {"HalfDown","HalfUp","HalfEven","HalfOdd","Stochastic"}

These days I need a version of Round to HalfUp. I write a quite ugly and slow function as below

myRound[x_, d_] := Module[{},
  c1 = (1./d)*10;
  c2 = 1./d;
  theDigit = Last@IntegerDigits@IntegerPart[x*c1];
  If[theDigit >= 5,
   Internal`StringToDouble@ToString@N[(IntegerPart[x*c2] + 1)/c2],
   Internal`StringToDouble@ToString@N[(IntegerPart[x*c2])/c2]]]

speed test

In[267]:= 
myRound[#, 0.01] & /@ RandomReal[1., 1000000]; // AbsoluteTiming

Out[267]= {30.7072, Null}

In[268]:= 
Round[#, 0.01] & /@ RandomReal[1., 1000000]; // AbsoluteTiming

Out[268]= {0.285921, Null}

So I am wondering if someone on this site already have developed an efficient toolkit for round matters?

matheorem
  • 17,132
  • 8
  • 45
  • 115

1 Answers1

4

I offer the following solution

r2[x_, a_] := x - Mod[x, a, -(a/2)]

We can verify that it has the desired result, using PiecewiseExpand

PiecewiseExpand[r2[x, a], -2 a < x < 2 a && a > 0]

Performance is only a little slower than the built-in Round

list = RandomReal[{0, 1}, 1000000];

AbsoluteTiming[Round[list, 0.1];]
(* {0.0079, Null} *)

AbsoluteTiming[r2[list, 0.1];]
(* {0.009414, Null} *)
mikado
  • 16,741
  • 2
  • 20
  • 54
  • Wow, great solution. But there is a issue with hidden fractions as pointed out by https://mathematica.stackexchange.com/q/65298/4742 . But if we use InternalStringToDouble@ToString`, the performance will drop down. – matheorem Mar 17 '19 at 13:22
  • I figured out a trick, though I don't know why it works. The efficient solution to hidden fraction may be r2[x_, a_] := (IntegerPart[(x/a - Mod[x/a, 1, -(1/2)])]*10^12)/ 10.^(12 - 1/Log[a, 10]) – matheorem Mar 17 '19 at 13:23
  • I just realize that your r2 doesn't round 5 up. I mean r2[1.265,0.01] doesn't give 1.27. How to fix it? – matheorem Mar 17 '19 at 13:44
  • 4
    @matheorem Technically, 1.265 in binary floating point corresponds to the fraction 5697053528623677/4503599627370496 which less than 1265/1000. The issue is due to rounding decimal to binary. Thus it is a problem of inputting the number you meant, not a problem with r2[]. – Michael E2 Mar 17 '19 at 15:14
  • Hi, @MichaelE2. So how to solve the problem. I definitely need some function takes 1.265 input and gives 1.27. I am already mad with those fussy 00000002and 999999998. Do you have robust round function? – matheorem Mar 17 '19 at 15:21
  • @matheorem Would N@r2[Rationalize[x], Rationalize[a]] work? It becomes less robust if x is the result of a computation in which rounding error has been propagated and magnifiied. – Michael E2 Mar 17 '19 at 15:27
  • @MichaelE2 the problem of using precise computation like Rationalize will be too slow. I need to apply round to a very large list. – matheorem Mar 17 '19 at 15:28
  • 2
    @matheorem The problem with imprecise calculation is that unwanted errors creep in, such as what you're complaining about. :) How about this?: r2[x_, a_] := x (1 + Sign[x] $MachineEpsilon) - Mod[x (1 + Sign[x] $MachineEpsilon), a, -(a/2)] – Michael E2 Mar 17 '19 at 15:40
  • @MichaelE2 Still not correct. r2[8.12, 0.01] gives 8.120000000000001` – matheorem Mar 17 '19 at 16:02
  • @matheorem Rounding error from 0.01 being converted to binary (compare Round[8.12, 0.01 ]). I doubt all problems can be avoided if you wish to use binary floating-point for underlying computations. – Michael E2 Mar 17 '19 at 17:07
  • 2
    @matheorem, I think you need to consider why you know the input is exactly 1.265 rather than a little more or a little less, and why the rounding is important to you. If you know the 3rd decimal place is exact, I suggest you multiply all your numbers by 1000, and work with integers. – mikado Mar 17 '19 at 18:38
  • @mikado Actually working with integer is also not robust. Try IntegerPart[5.06*100] it givse 5.05 – matheorem Mar 18 '19 at 02:14
  • @MichaelE2 How to use binary floating-point for underlying computations? – matheorem Mar 18 '19 at 02:14
  • 1
    @matheorem That's what happens (automatically) when you use real (floating-point) numbers on all common CPUs. – Michael E2 Mar 18 '19 at 02:52
  • @matheorem I was suggesting something like r2[Round[1.265*1000], 10] or r2[Round[5.06*1000], 10] both of which give the right answer. – mikado Mar 18 '19 at 21:33