Проблемы двойной точности в .NET - PullRequest
17 голосов
/ 19 февраля 2009

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

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);
}

Первое и второе утверждения в порядке, но третье не дает результатов, потому что результат равен только до шестого десятичного знака. Это почему? Есть ли способ исправить это?

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

Примечание В своем тестовом коде я написал суффикс «F» (явная константа с плавающей точкой), где я имел в виду «D» (двойной), поэтому, если я изменю его, я могу иметь большую точность.

Ответы [ 9 ]

11 голосов
/ 03 июля 2009

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

8 голосов
/ 19 февраля 2009

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

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

6 голосов
/ 19 февраля 2009

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

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

5 голосов
/ 20 февраля 2009

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

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

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

5 голосов
/ 19 февраля 2009

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

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

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

3 голосов
/ 19 февраля 2009

Проверьте ответы на этот вопрос: Безопасно ли проверять значения с плавающей запятой на равенство 0?

Действительно, просто проверьте "в пределах допуска ..."

1 голос
/ 19 февраля 2009

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

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

0 голосов
/ 12 января 2010

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

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

0 голосов
/ 22 апреля 2009

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

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));
}
...