Double каждый раз приносит разные значения - PullRequest
0 голосов
/ 21 мая 2018

Это мой алгоритм генерации, он генерирует случайные двойные элементы для массива, сумма которых должна быть 1

    public static double [] GenerateWithSumOfElementsIsOne(int elements)
    {
        double sum = 1;
        double [] arr = new double [elements];

        for (int i = 0; i < elements - 1; i++)
        {
            arr[i] = RandomHelper.GetRandomNumber(0, sum);
            sum -= arr[i];
        }

        arr[elements - 1] = sum;

        return arr;
    }

И вспомогательный метод

    public static double GetRandomNumber(double minimum, double maximum)
    {
        Random random = new Random();
        return random.NextDouble() * (maximum - minimum) + minimum;
    }

Мои тестовые примеры:

    [Test]
    [TestCase(7)]
    [TestCase(5)]
    [TestCase(4)]
    [TestCase(8)]
    [TestCase(10)]
    [TestCase(50)]
    public void GenerateWithSumOfElementsIsOne(int num)
    {
        Assert.AreEqual(1, RandomArray.GenerateWithSumOfElementsIsOne(num).Sum());   
    }

И дело в том - когда я тестирую, он каждый раз возвращает разные значения, подобные этим: Expected: 1 But was: 0.99999999999999967d<br> Expected: 1 But was: 0.99999999999999989d

Но в следующем тесте он проходит иногда все из них, иногда нет.

Я знаю, что проблемы с округлением, и прошу помощи, дорогие эксперты:)

Ответы [ 2 ]

0 голосов
/ 21 мая 2018

Ошибки округления часто встречаются в случае типов с плавающей запятой (например, Single и Double), например, давайте вычислим easy sum :

  // 0.1 + 0.1 + ... + 0.1 = ? (100 times). Is it 0.1 * 100 == 10? No!
  Console.WriteLine((Enumerable.Range(1, 100).Sum(i => 0.1)).ToString("R"));

Результат:

  9.99999999999998

Поэтому при сравнении значений floatinfg со значениями == или != добавить допуск :

// We have at least 8 correct digits
// i.e. the asbolute value of the (round up) error is less than tolerance
Assert.IsTrue(Math.Abs(RandomArray.GenerateWithSumOfElementsIsOne(num).Sum() - 1.0) < 1e-8); 
0 голосов
/ 21 мая 2018

https://en.wikipedia.org/wiki/Floating-point_arithmetic

В вычислениях арифметика с плавающей точкой является арифметикой, использующей формальное представление действительных чисел в качестве приближения, чтобы поддерживать компромисс между дальностью и точностью.По этой причине вычисления с плавающей точкой часто встречаются в системах, которые включают в себя очень маленькие и очень большие действительные числа, которые требуют быстрого времени обработки.Число, как правило, представляется приблизительно фиксированному числу значащих цифр (значимостей) и масштабируется с использованием показателя степени в некоторой фиксированной базе;основание для масштабирования обычно составляет два, десять или шестнадцать.

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

var ratio = a / b;
var diff = Math.Abs(ratio - 1);
return diff <= epsilon;
...