6

I'm writing an application which requires rounding labels to the nearest 'nice' number. I'll put some code below to demonstrate this, but my issue is that I was using a series of else ifs to find this number but I cannot be sure of the upper limit so this isn't really a good strategy. Are there any known algorithms or resources which could help me?

    if (diff <= 1) {
        roundAmount = 0.2;
    } else if (diff <= 5) {
        roundAmount = 1;
    } else if (diff <= 10) {
        roundAmount = 2;
    } else if (diff <= 25) {
        roundAmount = 5;
    } else if (diff <= 50) {
        roundAmount = 10;
    } else if (diff <= 100) {
        roundAmount = 20;
    } else if (diff <= 250) {
        roundAmount = 50;
    } else if (diff <= 500) {
        roundAmount = 100;
    } else if (diff <= 1000){
        roundAmount = 200;
    } etc...
user650309
  • 2,639
  • 7
  • 28
  • 47
  • You can only apply an algorithm if you can define specific rules for what 'nice' rounding actually *is*. Do you have such rules in mind? Its difficult to tell from your posted code. – Qwerky Oct 11 '11 at 11:29
  • How would that rounding work, i.e. how could those 'nice' number be defined? For example: 51 would be rounded to 20 (is `roundAmount` the nice number? ) whereas 50 is rounded to 10? – Thomas Oct 11 '11 at 11:30
  • At this moment in time I want the rounding to be a fifth of the next highest nice number. A more clear example might be that if I have the numbers 1.2 , 1.9, 3.65, 4.1, 4.67 I would like labels drawn at 1, 2, 3, 4, 5. – user650309 Oct 11 '11 at 11:42
  • you mentioned nearest round number.Are those nearest? I dont think so. – Android Killer Oct 11 '11 at 11:42
  • Are you sure about } else if (diff <= 1000){ roundAmount = 250; } ? If that should've been 200, the answer seems to me roundamount = diff/5.0 – Jonathan Oct 11 '11 at 11:28
  • This should be posted as a comment, as it does not answer the question. – Wooble Oct 11 '11 at 11:30
  • @Jonathan: Although I agree with you this should be a _comment_ only as it's not really an answer to the question. – home Oct 11 '11 at 11:31
  • It should have been 200, but diff/5 is not what I need. For example, if diff == 895.345 I still want roundAmount=250 – user650309 Oct 11 '11 at 11:38

3 Answers3

12

You can use Math.log10 to normalize all values before doing your "nice number" search, something like this:

[Edit] I just realized you are using Java instead of C#, so I modified the code a bit. I don't have a compiler around to test it, but you should get the general idea anyway:

static double getNicerNumber(double val)
{
    // get the first larger power of 10
    var nice = Math.pow(10, Math.ceiling(Math.log10(val)));

    // scale the power to a "nice enough" value
    if (val < 0.25 * nice)
        nice = 0.25 * nice;
    else if (val < 0.5 * nice)
        nice = 0.5 * nice;

    return nice;
}

// test program:
static void main(string[] args)
{
    double[] values = 
    {
        0.1, 0.2, 0.7,
        1, 2, 9,
        25, 58, 99,
        158, 267, 832
    };

    for (var val : values)
        System.out.printf("$%.2f --> $%.2f%n", val, getNicerNumber(val));
}

This will print something like:

0,1 --> 0,1
0,2 --> 0,25
0,7 --> 1
1 --> 1
2 --> 2,5
9 --> 10
25 --> 50
58 --> 100
99 --> 100
158 --> 250
267 --> 500
832 --> 1000
vgru
  • 49,838
  • 16
  • 120
  • 201
5

I prefer the following over Groo's approach, as it rounds 267 to 275 instead of 500. It basically rounds to the first digit, and then the nearest quarter fraction of that power of 10.

static double round_pretty(double val) {
    var fraction = 1;
    var log = Math.floor(Math.log10(val));

    // This keeps from adding digits after the decimal
    if(log > 1) {
        fraction = 4;
    }

    return Math.round(val * fraction * Math.pow(10, -log)) 
        / fraction / Math.pow(10, -log);
}

The output is as follows:

0.01 -> 0.01
0.025 -> 0.03    (Groo's does 0.025)
0.1 -> 0.1
0.2 -> 0.2       (Groo's does 0.25)
0.49 -> 0.5
0.5 -> 0.5       (Groo's does 1)
0.51 -> 0.5      (Groo's does 1)
0.7 -> 0.7       (Groo's does 1)
1 -> 1
2 -> 2           (Groo's does 2.5)
9 -> 9
23.07 -> 20
25 -> 30
49 -> 50
50 -> 50         (Groo's does 100 here)
58 -> 60
94 -> 90
95 -> 100
99 -> 100
100 -> 100
109 -> 100       (Groo's does 250 here)
158 -> 150
249 -> 250
267 -> 275
832 -> 825
1234567 -> 1250000
1499999 -> 1500000
1625000 -> 1750000
LeBleu
  • 1,101
  • 3
  • 17
  • 28
0

I came up with this quite crude solution, which returns the correct values for all of the cases I tested just now:

public static double foo(long value) {
    for (double i = 0.2; true; i *= 5) {
        if (i >= value) {
            return i / 5;
        }
    }
}

Although I must admit that a mathematical solution as posted by Groo would be more beautiful. ;)

Till Helge
  • 9,253
  • 2
  • 40
  • 56