Добавление 1/3 в java результатов в 1.0, в то время как это не должно

Примечание: вопрос до сих пор не ответил полностью! Эти вопросы не касаются вопроса об усечении частей с плавающей запятой!!!

В Java у меня есть этот простой код:

double sum = 0.0;
for(int i = 1; i <= n; i++){
    sum += 1.0/n
}
System.out.println("Sum should be: 1");
System.out.println("The result is: " + sum);

Где n может быть любым целым числом. Для чисел вроде 7,9 ожидаемое значение для суммы должно иметь разницу в последних цифрах суммы, а результат равен 0.999999999998 или что-то, но результат, когда я использую 3, равен 1.0.

Если вы добавите 1/3 3 раза, вы ожидаете число, близкое к 1, но я получаю ровно 1.0.

Почему?

Ответ 1

Я не уверен, поможет ли это прояснить ситуацию, потому что я не уверен, что вы считаете проблемой.

Вот тестовая программа, которая использует BigDecimal, как было предложено ранее, для отображения значений промежуточных ответов. На последнем шаге, добавив третью копию 1.0/3 к сумме двух копий, точный ответ находится на полпути между 1.0 и следующим двойным ниже. В этой ситуации правило округления до четности выбирает 1.0.

Учитывая это, я думаю, что должен округлить до 1.0, что противоречит заголовку вопроса.

Программа тестирования:

import java.math.BigDecimal;

public class Test {
  public static void main(String[] args) {
    final double oneThirdD = 1.0/3;
    final BigDecimal oneThirdBD = new BigDecimal(oneThirdD);
    final double twoThirdsD = oneThirdD + oneThirdD;
    final BigDecimal twoThirdsBD = new BigDecimal(twoThirdsD);
    final BigDecimal exact = twoThirdsBD.add(oneThirdBD);
    final double nextLowerD = Math.nextAfter(1.0, 0);
    final BigDecimal nextLowerBD = new BigDecimal(nextLowerD);
    System.out.println("1.0/3: "+oneThirdBD);
    System.out.println("1.0/3+1.0/3: "+twoThirdsBD);
    System.out.println("Exact sum: "+exact);
    System.out.println("Rounding error rounding up to 1.0: "+BigDecimal.ONE.subtract(exact));
    System.out.println("Largest double that is less than 1.0: "+nextLowerBD);
    System.out.println("Rounding error rounding down to next lower double: "+exact.subtract(nextLowerBD));
  }
}

Вывод:

1.0/3: 0.333333333333333314829616256247390992939472198486328125
1.0/3+1.0/3: 0.66666666666666662965923251249478198587894439697265625
Exact sum: 0.999999999999999944488848768742172978818416595458984375
Rounding error rounding up to 1.0: 5.5511151231257827021181583404541015625E-17
Largest double that is less than 1.0: 0.99999999999999988897769753748434595763683319091796875
Rounding error rounding down to next lower double: 5.5511151231257827021181583404541015625E-17

Ответ 2

Это связано с тем, что деление производится целым числом.

1/n всегда дает 0 при n > 1.

Следовательно, вы всегда получаете сумму = 0 + 1/1 + 0 + 0...

Попробуйте с 1.0 / n

Ответ 3

Если вы добавите 1/3 3 раза, вы ожидаете число, близкое к 1, но я получите ровно 1.0.

На самом деле нормальный человек, не зараженный опытом программирования, ожидает, что n * 1/n будет равным 1, но мы здесь не нормальные.

Я не могу точно воспроизвести вашу проблему, я получаю

groovy:000> def foo(n) {
groovy:001>   sum = 0.0
groovy:002>   for (int i = 0; i < n; i++) {
groovy:003>     sum += 1.0 / n
groovy:004>   }
groovy:005>   sum
groovy:006> }
===> true
groovy:000> foo(3)
===> 0.9999999999

Здесь может быть 2 вопроса, по крайней мере, вы захотите узнать о них.

Один из них заключается в том, что двойники не являются точными, они не могут точно представлять некоторые значения, и вам просто нужно ожидать, что материал немного отключится. Ваша цель не 100% -ная точность, чтобы она сохраняла ошибку в допустимых пределах. (У Питера Лори есть интересная статья о удвоениях, которую вы можете проверить.) Если это не подходит для вас, вам нужно будет избегайте двойников. Для многих применений BigDecimal достаточно хорош. Если вы хотите получить библиотеку, в которой проблемы разделения в вашем вопросе дают точные ответы, вы можете проверить ответы на этот вопрос.

Другая проблема заключается в том, что System.out.println не сообщает вам точное значение double, оно немного подталкивает. Если вы добавите строку, например:

System.out.println(new java.math.BigDecimal(sum));

тогда вы получите точное представление о том, что содержит двойной.

Ответ 4

int, деленный на int, всегда производит другой int. Теперь int не имеет места для хранения дробной части номера, поэтому он отбрасывается. Имейте в виду, что он отбрасывается не округленным.

Поэтому 1/3 = 0,33333333, а дробная часть отбрасывается, что означает, что она становится равной 0.

Если вы укажете номер как двойной (включив десятичную точку, например, 1. или 1.0), тогда результат будет двойным (потому что java автоматически преобразует int в double), и дробная часть будет сохранена.

В обновленном вопросе вы устанавливаете я в 1.0, но я по-прежнему является int. Таким образом, что 1.0 усекается до 1 и для дальнейших вычислений это все равно int. Вам нужно изменить тип i, чтобы удвоить, иначе в противном случае не будет никакой разницы в коде.

В качестве альтернативы вы можете использовать sum + = 1.0/n

Это приведет к преобразованию n в double перед выполнением вычисления