Почему этот цикл никогда не заканчивается? [дубликат]

19
задан Community 23 May 2017 в 11:50
поделиться

9 ответов

Вычисления с плавающей запятой не совсем точны. Вы получите ошибку представления, потому что 0.2 не имеет точного представления в виде двоичного числа с плавающей запятой, поэтому значение не становится точно равным нулю. Попробуйте добавить оператор отладки, чтобы увидеть проблему:

double d = 2.0;
while (d != 0.0)
{
    Console.WriteLine(d);
    d = d - 0.2;
}
2
1,8
1,6
1,4
1,2
1
0,8
0,6
0,4
0,2
2,77555756156289E-16   // Not exactly zero!!
-0,2
-0,4

Один из способов решения - использовать десятичный тип .

32
ответ дан 30 November 2019 в 01:57
поделиться

это из-за точности с плавающей запятой. используйте while (d> 0,0) или, если необходимо,

while (Math.abs(d-0.0) > some_small_value){

}
1
ответ дан 30 November 2019 в 01:57
поделиться

f неинициализирован;)

Если вы имеете в виду:

double f = 2.0;

Это может быть эффектом неточного артиметика для переменных типа double.

1
ответ дан 30 November 2019 в 01:57
поделиться

Вам лучше использовать

while(f  > 0.0) 

* edit: см. Комментарий Паскаля ниже. Но если вам нужно выполнить цикл целое детерминированное число раз, лучше используйте целочисленный тип данных.

3
ответ дан 30 November 2019 в 01:57
поделиться

Это не останавливается, потому что 0.2 i не точно представлено в двух дополнениях поэтому ваш цикл никогда не выполняет тест 0.0 == 0.0

0
ответ дан 30 November 2019 в 01:57
поделиться

Как говорили другие, это просто фундаментальная проблема, с которой вы сталкиваетесь при выполнении арифметических операций с плавающей запятой для любой базы. Просто так получилось, что base-2 является наиболее распространенным в компьютерах (потому что допускает эффективную аппаратную реализацию).

Лучшее исправление, если возможно, - переключиться на использование некоторого частного представления числа для цикла, сделав на основе этого значение с плавающей запятой. Хорошо, это звучит преувеличенно! Для вашего конкретного случая я бы написал это так:

int dTimes10 = 20;
double d;
while(dTimes10 != 0) {
   dTimes10 -= 2;
   d = dTimes10 / 10.0;
}

Здесь мы действительно работаем с дробями [20/10, 18/10, 16/10, ..., 2/10, 0/10], где итерация выполняется с целыми числами (т.е. легко получить правильные) в числителе с фиксированным знаменателем перед преобразованием в числа с плавающей запятой.Если вы можете переписать свои настоящие итерации так, чтобы они работали таким образом, вы добьетесь большого успеха (и в любом случае они не намного дороже, чем то, что вы делали раньше, что является отличным компромиссом для получить правильность).

Если вы не можете этого сделать, вам нужно использовать для сравнения равенство внутри эпсилона. Примерно это замена d! = Target на abs (d - target) <ε , где выбор ε (эпсилон) иногда может быть неудобным. По сути, правильное значение ε зависит от множества факторов, но, вероятно, лучше всего выбрать 0,001 для примера итерации с учетом масштаба значения шага (т. Е. Это половина процента от величины шага, поэтому все, что в пределах этого будет ошибкой, а не информативной).

0
ответ дан 30 November 2019 в 01:57
поделиться

(С одной стороны, вы не используете одну и ту же переменную, но я предполагаю, что это опечатка :)

0,2 на самом деле не 0,2. Это ближайшее двойное значение к 0,2. Когда вы вычтете это 10 раз из 2,0, вы не получите точно 0,0.

В C # вы можете изменить использование десятичного типа вместо , который будет работать:

// Works
decimal d = 2.0m;
while (d != 0.0m) {
   d = d - 0.2m;
}

Это работает, потому что десятичный тип точно представляет десятичные значения, такие как 0,2 (в определенных пределах ; это 128-битный тип). Каждое задействованное значение точно представимо, поэтому оно работает. То, что не сработает, будет следующее:

decimal d = 2.0m;
while (d != 0.0m) {
   d = d - 1m/3m;
}

Здесь «третий» не совсем представимо, поэтому мы получаем ту же проблему, что и раньше.

В общем, проводить точное сравнение на равенство между числами с плавающей запятой - плохая идея - обычно вы сравниваете их с определенным допуском.

У меня есть статьи о двоичной с плавающей запятой и с плавающей десятичной запятой из контекста C # /. NET, которые объясняют вещи более подробно.

27
ответ дан 30 November 2019 в 01:57
поделиться

Я помню, как купил Sinclair ZX-81, изучил прекрасное руководство по базовому программированию и почти вернулся в магазин, когда наткнулся на свою первую ошибку округления с плавающей запятой.

Никогда бы не подумал, что 27,99998 лет спустя у людей все еще будут эти проблемы.

11
ответ дан 30 November 2019 в 01:57
поделиться

Проблема заключается в арифметике с плавающей запятой. Если для числа нет точного двоичного представления, то вы можете сохранить только ближайшее к нему число (точно так же, как вы не можете сохранить число 1/3 в десятичном виде - вы можете сохранить только что-то вроде 0,33333333 для некоторой длины троек.) Это означает, что арифметические операции с числами с плавающей запятой довольно часто не совсем точны. Попробуйте что-то вроде следующего (Java):

public class Looping {

    public static void main(String[] args) {

        double d = 2.0;
        while(d != 0.0 && d >= 0.0) {
            System.out.println(d);
            d = d - 0.2;
        }

    }

}

Ваш результат должен быть примерно таким:

2.0
1.8
1.6
1.4000000000000001
1.2000000000000002
1.0000000000000002
0.8000000000000003
0.6000000000000003
0.4000000000000003
0.2000000000000003
2.7755575615628914E-16

И теперь вы должны увидеть, почему условие d == 0 никогда не выполняется. (последнее число - это число, которое очень близко к 0, но не совсем.

В качестве другого примера странности с плавающей запятой, попробуйте следующее:

public class Squaring{

    public static void main(String[] args) {

        double d = 0.1;
        System.out.println(d*d);

    }

}

Потому что не существует двоичного представления точно 0,1 , возведение в квадрат не дает ожидаемого результата ( 0,01 ), но на самом деле что-то вроде 0,010000000000000002 !

2
ответ дан 30 November 2019 в 01:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: