Вопрос

У меня есть простая функция 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);
}

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

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

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

Это было полезно?

Решение

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

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

Другие советы

На самом деле я как бы жалею, что они не внедрили оператор == для floats и doubles .Почти всегда неправильно спрашивать, равно ли значение double или float какому-либо другому значению.

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

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

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

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

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

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

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

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

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

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

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

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

Для решения аналогичной проблемы я в конечном итоге использую следующую реализацию, которая, похоже, успешно выполняет большую часть моего тестового примера (с точностью до 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));
}

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top