• Welcome to Valhalla Legends Archive.
 

My rounding function. Need constructive criticism please.

Started by Sorc.Polgara, November 21, 2005, 02:09 PM

Previous topic - Next topic

Sorc.Polgara

Ok, so my assignment is to:
Quote
Write and test a method round that has a real parameter amount and an integer parameter n and that returns the value of amount rounded to n places.  Fore example, round(10.536, 0), round(10.536, 1), and round(10.536, 2) should return the values 11.0, 10.5, 10.54, respectively.

I wrote my function, tested it, and it works.  However, the way I do it is probably messy and not the best way I'm guessing.  So, I'd like some constructive criticism so that I can improve my algorithm.

Here is the code:

public class Round{
public static double round(double amount, int n)
{
if(n == 0){
return (double)Math.round(amount);
}else{
double fracPart = (amount % 1);
double intPart = (amount - fracPart);

double decPlace = (amount / (1 / Math.pow(10, n + 1))) % 10;

if((int)decPlace >= 5)
{
fracPart += (1 / Math.pow(10, n));
}

int rPart = (int)(fracPart * (1 / Math.pow(10, -n)));

return intPart + ((double)rPart * (1 / Math.pow(10, n)));
}
}

public static void main(String[] args)
{
double num = 10.536;

for(int i = 0; i < 3; ++i)
{
System.out.println(num + " rounded to " + i + " decimal places:");
System.out.println(round(num, i) + "\n");
}
}
}


Here is the output:
Quote
10.536 rounded to 0 decimal places:
11.0

10.536 rounded to 1 decimal places:
10.5

10.536 rounded to 2 decimal places:
10.54


Thanks.

iago

I would do:

Math.floor(amount * ((double) 10n)) / (double) 10n

I believe that would get you the same answer, faster. 

BUGS: Mine rounds down.  To round up, you'd probably use Math.round() or something.  I'm not entirely positive what the function is, so I didn't use it :)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Ender

Quote from: Sorc.Polgara on November 21, 2005, 02:09 PM
Here is the output:
10.536 rounded to 0 decimal places:
11.0

It should be 11. You may have to convert a double to an int. It can be done easily using a wrapper class, either a Double or an Integer.

You have some extraneous parentheses. Like these lines for example:
Quote
double fracPart = (amount % 1);
double intPart = (amount - fracPart);


dxoigmn

Here is what I came up with:


public static double round(double amount, int n)
{
return ((int)(amount * Math.pow(10, n) + 0.5) / Math.pow(10, n));
}


Seems to work.

Sorc.Polgara

#4
Quote from: dxoigmn on November 21, 2005, 07:48 PM
Here is what I came up with:


public static double round(double amount, int n)
{
return ((int)(amount * Math.pow(10, n) + 0.5) / Math.pow(10, n));
}


Seems to work.

WOW, that is like freaking 10000000 times shorter.  THANKS.

-------------------------
Quote from: Ender on November 21, 2005, 07:04 PM
Quote from: Sorc.Polgara on November 21, 2005, 02:09 PM
Here is the output:
10.536 rounded to 0 decimal places:
11.0

It should be 11. You may have to convert a double to an int. It can be done easily using a wrapper class, either a Double or an Integer.

You have some extraneous parentheses. Like these lines for example:
Quote
double fracPart = (amount % 1);
double intPart = (amount - fracPart);

Quote
Write and test a method round that has a real parameter amount and an integer parameter n and that returns the value of amount rounded to n places.  For example, round(10.536, 0), round(10.536, 1), and round(10.536, 2) should return the values 11.0, 10.5, 10.54, respectively.
Yeah, I have a habit of putting extraneous parentheses... hehe.

Ummm, the method is suppose to return a double.  So returning 11.0 is suppose to be correct


EDIT:   Wait, I see where you are getting that it should be 11
Quote
... returns the value of amount rounded to n places.
Since it would be rounded to 0 decimal places, then it would really be an integer.

Well, since the example he gives in the assignment gives the result as 11.0, I'm going to stick with just returning values that have 0 decimal places as doubles.

dxoigmn

Quote from: Sorc.Polgara on November 21, 2005, 08:34 PM
WOW, that is like freaking 10000000 times shorter.  THANKS.

Do you understand why it works, though? Uses the same method that Math.round()'s internal implementation uses, kind of. Might be better to use Math.floor() instead of casting to an int.

Edit: Haha i just noticed it looks a lot like iago's. When I had initially wrote that function is was pretty hideous looking until I noticed it could be simplified.

Ender

QuoteUmmm, the method is suppose to return a double.  So returning 11.0 is suppose to be correct
Yeah, but mathematically it isn't correct, as it implies that 11.01 rounded to one decimal place is the same as 11.1 rounded to 0 decimal places. You wouldn't return an integer in that method of course, but you could call another function that converts a double into an int and return that int.
And this function should be private, since the end-user will never use it.

You could also do this completely excluding the Math class. The algorithm could be:
~input n, the decimal places to round to
~break double into integer part I and fraction part F
~take the fraction part, multiply it by 10^n,  break it up into integer part I2 and fraction part F2, if fraction part is greater than 0.5, then integer part ++
~return (double) (first integer part (I) + second integer part (I2) x 10^-n)                // note pemdas / order of operations

I believe this will work but I haven't tested it.

Another fun way to do it without the Math class would be to write a class that performs bitwise operations / logical gates on base 10 to isolate certain digits/decimal places

Sorc.Polgara

#7
Quote from: dxoigmn on November 21, 2005, 08:54 PM
Quote from: Sorc.Polgara on November 21, 2005, 08:34 PM
WOW, that is like freaking 10000000 times shorter.  THANKS.

Do you understand why it works, though? Uses the same method that Math.round()'s internal implementation uses, kind of. Might be better to use Math.floor() instead of casting to an int.

Edit: Haha i just noticed it looks a lot like iago's. When I had initially wrote that function is was pretty hideous looking until I noticed it could be simplified.

Yes, actually, what I did is wrote this huge comment block so that when my professor reads my code he won't mark off for not having documentation.

Here it is:

/* round():  Rounds a given real number to a given number places.
* and returns the rounded value as type double.
*/
public static double round(double amount, int n)
{
return ((int)(amount * Math.pow(10, n) + 0.5) / Math.pow(10, n));

/* A simplified version of the code above:
*     ( (int)( (amount * 10^n) + 0.5 ) ) / ( 10^n )
*
* A step-by-step explanation of the above algorithm.
* 1.  Moves the decimal point to the right n places by simply
*     multiplying the amount by 10 to the nth power.  Doing
*     this allows easy evalution of the decimal place that is
*     to be rounded.
* 2.  Adds 0.5 (one half) to the tenths place of the resulting
*     value in step 1, which is the decimal place that must be
*     evaluated in order to round.  Adding 0.5 rounds the number
*     and will increment accordingly if the value of the digit
*     in the tenths place is greater than or equal to 0.5.
* 3.  The value that is computed in steps 1 and 2 is converted
*     to integer type by typcasting.
* 4.  The resulting integer from steps 1, 2, and 3 is divided by
*     10 to the nth power.
*     <NOTE: The method Math.pow(double a, double b) computes
*     the value of a to the b power and returns it as type
*     double.  Thus, an integer is being divided by a double.
*     Therefore the resulting quotient is then of double type.>
* 5.  Returns the amount rounded to n decimal places as double
*     type.
*/
}

dxoigmn

Haha, that is one huge comment for 1 line of code. But I guess it is needed.

Sorc.Polgara

Quote from: Ender on November 21, 2005, 08:56 PM
QuoteUmmm, the method is suppose to return a double.  So returning 11.0 is suppose to be correct
Yeah, but mathematically it isn't correct, as it implies that 11.01 rounded to one decimal place is the same as 11.1 rounded to 0 decimal places.

Your right.  I think I'm going to fix it and then correct the professor because his example is mathematically incorrect.  If he takes off for it I'm going to be pissed, I should even go to the other CS Professors and explain it if he does!

I don't think my professor even has a Ph.D. in Computer Science, only Mathematics.  He should know better!

Sorc.Polgara

Quote from: iago on November 21, 2005, 05:13 PM
I would do:

Math.floor(amount * ((double) 10n)) / (double) 10n

I believe that would get you the same answer, faster. 

BUGS: Mine rounds down.  To round up, you'd probably use Math.round() or something.  I'm not entirely positive what the function is, so I didn't use it :)


I was curious and decided to try and fix iago's code, which doesn't quite work as he mentioned.  Thanks to dxoigmn's code I was able to easily fix it.

Working code:

/* round():  Rounds a given real number to a given number places.
* and returns the rounded value as type double.
*/
public static double round(double amount, int n)
{
return Math.floor(amount * Math.pow(10, n) + 0.5) / Math.pow(10, n);
}


NOTE:  Adding 0.5 fixed the rounding problem quite nicely.

Sorc.Polgara

#11
Quote from: Ender on November 21, 2005, 08:56 PM
QuoteUmmm, the method is suppose to return a double.  So returning 11.0 is suppose to be correct
Yeah, but mathematically it isn't correct, as it implies that 11.01 rounded to one decimal place is the same as 11.1 rounded to 0 decimal places. You wouldn't return an integer in that method of course, but you could call another function that converts a double into an int and return that int.
And this function should be private, since the end-user will never use it.

You could also do this completely excluding the Math class. The algorithm could be:
~input n, the decimal places to round to
~break double into integer part I and fraction part F
~take the fraction part, multiply it by 10^n,  break it up into integer part I2 and fraction part F2, if fraction part is greater than 0.5, then integer part ++
~return (double) (first integer part (I) + second integer part (I2) x 10^-n)                // note pemdas / order of operations

I believe this will work but I haven't tested it.

I'm procrastinating studying so tried your solution out, it works as you thought.  However I use the Math.pow() function. 

Here is the code:

// Rounds a real number to n decimal places and returns the
// rounded value as a double
public static double round(double amount, int n)
{
if(n == 0){
return (double)Math.round(amount);
}else{
// Break amount into integer part1 (I1) and a fraction part1 (F1)
double F1 = amount % 1;
int I1 = (int)(amount - F1);

// Multiply the fraction part1 (F1) by 10^n, store it to amount2
double amount2 = F1 * Math.pow(10, n);

// Break amount2 into integer part2 (I2) and fraction part2 (F2)
double F2 = amount2 % 1;
int I2 = (int)(amount2 - F2);

// If the fraction part2 (F2) is greater than 0.5, then increment
// integer part2 (I2) by 1
if(F2 >= 0.5)
++I2;

// Multiply integer part2 (I2) by 10^-n and add it to integer
// part1, then typecast it as type double and return the value
return (double)(I1 + I2 * Math.pow(10, -n));
}
}


I added this if statement that will simply use the Math.round() function if n equals 0... instead of going through all those steps, just use the Math.round() for when n needs to be rounded to 0 decimal places.

It seems to work!  This is a good example of to use to *try* and *explain* in pseudo-like terms to my fellow students how to approach the algorithm.  I will undoubtly be recieving IM's the night before this assignment is due.

iago

Quote from: Sorc.Polgara on November 21, 2005, 11:35 PM
Quote from: iago on November 21, 2005, 05:13 PM
I would do:

Math.floor(amount * ((double) 10n)) / (double) 10n

I believe that would get you the same answer, faster. 

BUGS: Mine rounds down.  To round up, you'd probably use Math.round() or something.  I'm not entirely positive what the function is, so I didn't use it :)


I was curious and decided to try and fix iago's code, which doesn't quite work as he mentioned.  Thanks to dxoigmn's code I was able to easily fix it.

Working code:

/* round():  Rounds a given real number to a given number places.
* and returns the rounded value as type double.
*/
public static double round(double amount, int n)
{
return Math.floor(amount * Math.pow(10, n) + 0.5) / Math.pow(10, n);
}


NOTE:  Adding 0.5 fixed the rounding problem quite nicely.

Instead of adding 0.5, using Math.round instead of Math.floor should fix everything. :)
This'll make an interesting test for broken AV:
QuoteX5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*


Ender

Quote from: Ender on November 21, 2005, 08:56 PM
Another fun way to do it without the Math class would be to write a class that performs bitwise operations / logical gates on base 10 to isolate certain digits/decimal places

Hm, I got bored, then realized this was useless, but I post it anyways because I spent a while making the algorithm work... perhaps this could be useful for *something* :(

NOTE: The AND gate does the following:
    T   F
T  T   F
F  F   F

In other words, if both things are true, then it comes out true. For this crude useless base 10 AND, if both things are the same condition (excluding 0, as 0 would mean undefined or w/e) then it comes out as that condition.


/**
* Useless Base 10 AND operator
*/

/**
* @author Andy
*
*/
public final class DecimalOps {

public DecimalOps(int comparable, int comparison) {
System.out.println("Answer: " + recursAND(comparable, comparison));
System.out.println("This should return 204");
}

public int recursAND(double comparable, double comparison) {
Double doubleWrapper = new Double(recursAND(comparable, comparison, 0.0));
return doubleWrapper.intValue();
}

private double recursAND(double comparable, double comparison, double power) {
if ( (comparable < Math.pow(10, power)) && (comparison < Math.pow(10, power)) )
return 0.0;
else {
double digit = ( (comparable * Math.pow(10, -power -1)) % 1 ) * 10;
        double digit2 = ( (comparison * Math.pow(10, -power -1)) % 1 ) * 10;
       
        digit = digit - (digit % 1);
        digit2 = digit2 - (digit2 % 1);
       
        if (digit == digit2)
        return recursAND(comparable, comparison, power + 1) + digit * Math.pow(10, power);
        else
        return recursAND(comparable, comparison, power + 1);   
}
       
}

/**
* @param args
*/
public static void main(String[] args) {
int comparable = 1234;
int comparison = 3254;
System.out.println(comparable + " compared to " + comparison);
new DecimalOps(comparison, comparable);
}

}


Input: Compare 1234 to 3254
Output: 204
This should return 204

Yoni

The opposite of the floor function is called the ceiling function. The function iago forgot the name of is probably something like Math.ceil.