Подтвердить что ты не робот

Проблемы с двойной точностью в .NET.

У меня есть простая функция С#:

public static double Floor(double value, double step)
{
    return Math.Floor(value / step) * step;
}

Это вычисляет большее число, меньшее или равное "значению", которое кратно "шагу". Но ему не хватает точности, как видно из следующих тестов:

[TestMethod()]
public void FloorTest()
{
    int decimals = 6;
    double value = 5F;
    double step = 2F;
    double expected = 4F;
    double actual = Class.Floor(value, step);
    Assert.AreEqual(expected, actual);
    value = -11.5F;
    step = 1.1F;
    expected = -12.1F;
    actual = Class.Floor(value, step);
    Assert.AreEqual(Math.Round(expected, decimals),Math.Round(actual, decimals));
    Assert.AreEqual(expected, actual);
}

Первый и второй утверждения одобрены, но третий не работает, потому что результат равен только 6-му знаку после запятой. Почему это? Есть ли способ исправить это?

Обновить. Если я отлаживаю тест, я вижу, что значения равны до восьмого десятичного разряда вместо 6-го, возможно, потому, что Math.Round вводит некоторую неточность.

Примечание. В моем тестовом коде я написал суффикс "F" (явная константа float), где я имел в виду "D" (double), поэтому, если я изменю, у меня может быть больше точности.

4b9b3361

Ответ 1

Если вы опустите все постфикс F (т.е. -12.1 вместо -12.1F), вы получите равенство нескольким цифрам. Ваши константы (и особенно ожидаемые значения) теперь плавают из-за F. Если вы делаете это специально, пожалуйста, объясните.

Но для остальных я согласен с другими ответами на сравнение двойных или плавающих значений для равенства, это просто ненадежно.

Ответ 2

Арифметика с плавающей точкой на компьютерах не является точной наукой:).

Если вам нужна точная точность для предопределенного числа десятичных знаков, используйте Decimal вместо double или допускайте небольшой интервал.

Ответ 3

Я действительно хотел, чтобы они не реализовали оператор == для float и double. Это почти всегда неправильно делать, когда когда-либо спрашивают, равна ли double или float любому другому значению.

Ответ 4

http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems

Например, не представимость 0,1 и 0,01 (в двоичном выражении) означает, что результат попытки квадрата 0,1 не является ни 0.01, ни представимым числом, наиболее близким к нему.

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

Ответ 5

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

  • Если вы хотите делать математику на очень больших И очень малых числах, не используйте float или double.
  • Если вам нужна бесконечная точность, не используйте float или double.
  • Если вы агрегируете очень большое количество значений, не используйте float или double (ошибки будут сложны).
  • Если вам нужна скорость и размер, используйте float или double.

См. этот ответ (также я) для детального анализа того, как точность влияет на результат ваших математических операций.

Ответ 7

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

Если вам не нужна точность за определенный момент, то, возможно, десятичный код будет работать лучше для вас. Он имеет более высокую точность, чем двойной.

Ответ 8

Для подобной проблемы я в конечном итоге использую следующую реализацию, которая, кажется, имеет успех в большинстве моих тестовых случаев (до пятизначной точности):

public static double roundValue(double rawValue, double valueTick)
{
    if (valueTick <= 0.0) return 0.0;

    Decimal val = new Decimal(rawValue);
    Decimal step = new Decimal(valueTick);
    Decimal modulo = Decimal.Round(Decimal.Divide(val,step));

    return Decimal.ToDouble(Decimal.Multiply(modulo, step));
}

Ответ 9

Иногда результат является более точным, чем вы ожидаете от строгого: FP IEEE 754. Это потому, что HW использует больше бит для вычисления. См. спецификация С# и эта статья

Java имеет ключевое слово strictfp, а С++ - это компиляторы. Я пропустил эту опцию в .NET