-1

I am doing the following programming exercise: Drying Potatoes. The statement is:

All we eat is water and dry matter.

John bought potatoes: their weight is 100 kilograms. Potatoes contain water and dry matter.

The water content is 99 percent of the total weight. He thinks they are too wet and puts them in an oven - at low temperature - for them to lose some water.

At the output the water content is only 98%.

What is the total weight in kilograms (water content plus dry matter) coming out of the oven?

He finds 50 kilograms and he thinks he made a mistake: "So much weight lost for such a small change in water content!"

Can you help him?

Write function potatoes with

int parameter p0 - initial percent of water-
int parameter w0 - initial weight -
int parameter p1 - final percent of water -

potatoes should return the final weight coming out of the oven w1 truncated as an int. Example:

potatoes(99, 100, 98) --> 50

I have tried to figure out how could we calculate the final weight, by hand. Plus, I got help in the following thread: How could we calculate the final weight, knowing the initial and final water percentage, and initial weight‽??

I have written the following code:

class Potatoes {

    public static long potatoes(int p0, int w0, int p1) {
        System.out.println("\ninitial percent of water p0: "+p0);
        System.out.println("initial weight w0: "+w0);
        System.out.println("final percent of water p1: "+p1);
        double left = (1 + (p1 / (100.0 - p1)));
        System.out.println("left: "+left);
        double right = (w0 * (1 - (p0/100.0)));
        System.out.println("right: "+right);
        System.out.println("double result: "+left * right);
        System.out.println("int result: "+(int)(left * right));
        return (int)(left * right);
    }
}

And I have observed that there are some tests where the output is different than expected. The difficulty is that the statement requires us to truncate the final weight to int. But if we do that, there are some edge cases, where the left * right instruction returns a number with a lot of decimals like: .9999999; and it expects the next int (to round it up).

It is better explained with the test cases themselves:

import static org.junit.Assert.*;
import org.junit.Test;

public class PotatoesTest {

    private static void dotest(int p0, int w0, int p1, int expected) {
        assertEquals(expected, Potatoes.potatoes(p0, w0, p1));
    }

    @Test
    public void testResultDoesNotNeedRounding() {
        dotest(99, 100, 98, 50);
        dotest(82, 127, 80, 114);
        dotest(93, 129, 91, 100);
    }
    @Test
    public void testResultNeedsRoundingUp1(){
        dotest(92, 120, 88, 80);
    }
    @Test
    public void testResultNeedsRoundingUp2(){
        dotest(91, 132, 89, 108);
    }
}

When we execute the previous tests, the console gives us the following output:

initial percent of water p0: 99
initial weight w0: 100
final percent of water p1: 98
left: 50.0
right: 1.0000000000000009
double result: 50.00000000000004
int result: 50

initial percent of water p0: 82
initial weight w0: 127
final percent of water p1: 80
left: 5.0
right: 22.860000000000007
double result: 114.30000000000004
int result: 114

initial percent of water p0: 93
initial weight w0: 129
final percent of water p1: 91
left: 11.11111111111111
right: 9.029999999999994
double result: 100.33333333333326
int result: 100

initial percent of water p0: 92
initial weight w0: 120
final percent of water p1: 88
left: 8.333333333333332
right: 9.599999999999994
double result: 79.99999999999994
int result: 79
expected:<80> but was:<79>

initial percent of water p0: 91
initial weight w0: 132
final percent of water p1: 89
left: 9.090909090909092
right: 11.879999999999995
double result: 107.99999999999997
int result: 107
expected:<108> but was:<107>

So, as you see, the first three tests pass, because of they do not need to be rounded and can be truncated to int. However the two last fail, because of the exercise expects them to round it up.

Besides I have written the following to be able to pass the test cases, however I know there should be a better approach:

class Potatoes {

    public static long potatoes(int p0, int w0, int p1) {
        System.out.println("\ninitial percent of water p0: "+p0);
        System.out.println("initial weight w0: "+w0);
        System.out.println("final percent of water p1: "+p1);
        double left = (1 + (p1 / (100.0 - p1)));
        System.out.println("left: "+left);
        double right = (w0 * (1 - (p0/100.0)));
        System.out.println("right: "+right);
        System.out.println("double result: "+left * right);
        System.out.println("int result: "+(int)(left * right));
        return String.valueOf(left * right).contains(".99") ? (int)(Math.ceil(left * right)) : (int)(left * right);
    }
}

In addition, I have read:

How could we round a decimal up if it ends in 0.99‽??

EDIT: I have tested and Math.round does not solve this situation. Here we have two more test cases:

import static org.junit.Assert.*;
import org.junit.Test;

public class PotatoesTest {

    private static void dotest(int p0, int w0, int p1, int expected) {
        assertEquals(expected, Potatoes.potatoes(p0, w0, p1));
    }

    @Test
    public void mathRoundDoesNotWork1(){
      dotest(89,53,85,38);
    }
    @Test
    public void mathRoundDoesNotWork2(){
      dotest(82,134,77,104);
    }
}

Being the code:

class Potatoes {

    public static long potatoes(int p0, int w0, int p1) {
        System.out.println("\ninitial percent of water p0: "+p0);
        System.out.println("initial weight w0: "+w0);
        System.out.println("final percent of water p1: "+p1);
        double left = (1 + (p1 / (100.0 - p1)));
        System.out.println("left: "+left);
        double right = (w0 * (1 - (p0/100.0)));
        System.out.println("right: "+right);
        System.out.println("double result: "+left * right);
        System.out.println("int result: "+(int)(left * right));
        return Math.round(left*right);
    }
}

And we see in the output:

initial percent of water p0: 89
initial weight w0: 53
final percent of water p1: 85
left: 6.666666666666667
right: 5.829999999999999
double result: 38.86666666666666
int result: 38
expected:<38> but was:<39>

initial percent of water p0: 82
initial weight w0: 134
final percent of water p1: 77
left: 4.3478260869565215
right: 24.120000000000008
double result: 104.86956521739134
int result: 104
expected:<104> but was:<105>

The question is: how do we round up only if a double result contains a very close number to the next int, meaning it has the format: 100.999999... (number.{9 n decimals})?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Yone
  • 2,064
  • 5
  • 25
  • 56
  • 1
    I don't understand how [your first link](https://stackoverflow.com/questions/153724/how-to-round-a-number-to-n-decimal-places-in-java) doesn't answer this question...? You're trying to round to zero decimal places is all... Some answers there show using [`Math.round`](https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/Math.html#round(double)) (which returns a `long`). – T.J. Crowder Jan 04 '20 at 12:35
  • 3
    you said you have read those links - do they not answer your question and if not - why? Math.round should do what you need – Janar Jan 04 '20 at 12:35
  • tried Math.round(Number) ? – Maxdola Jan 04 '20 at 12:37
  • I've closed this question as a duplicate of [your first link](https://stackoverflow.com/questions/153724/how-to-round-a-number-to-n-decimal-places-in-java). If your question is really different, we can reopen it, but I'm not seeing it. – T.J. Crowder Jan 04 '20 at 12:41

1 Answers1

1

Read this for enlightenment: Computers aren't magical; there are only 2^64 different numbers a double can represent, which means: Not every number is in there (there are infinite numbers between 0 and 1, let alone between minus infinity and positive infinity; clearly 'an infinite infinity' is a lot more than 2^64!). Computers count in binary, humans count in decimal, and these two are not the same.

In the translation from the number in that batch of 2^64 that doubles can represent, to human form, it can look ugly.

The solution is to realize that you cannot print doubles to humans and have them be reasonable. Instead, you need a strategy when rendering them, for example, by including the precision you want to render at. Thus:

System.out.printf("%.2f\n", someDouble); // this is how you print doubles.
System.out.println(someDouble); // this is never a good idea.
String x = String.format("%.2f", someDouble); // If you need it as a string.

The text %.2f is String.format-ese for 'print a double; no more than 2 digits after the separator'.

EDIT: Fixed 'println' to 'printf' in first line of code snippet.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72