Как мне исправить мой вывод для неточности с плавающей точкой? - PullRequest
4 голосов
/ 08 декабря 2008

Я делаю некоторые операции с плавающей точкой и получаю следующие числа:

-0.5
-0.4
-0.3000000000000000004
-0.2000000000000000004
-0.1000000000000000003
1.10E-16
0.1
0.2
0.30000000000000000004
0.4
0.5

Алгоритм следующий:

var inc:Number = nextMultiple(min, stepSize);
trace(String(inc));

private function nextMultiple(x:Number, y:Number) {
    return Math.ceil(x/y)*y;
}

Я понимаю, что число с плавающей точкой не всегда может быть точно представлено в байте. например, 1/3. Я также знаю, что мой размер шага равен 0,1 . Если у меня есть размер шага, как я могу получить правильный вывод?

Странно то, что я впервые столкнулся с такой проблемой. Может быть, я недостаточно играю с плавающей точкой.

Ответы [ 10 ]

8 голосов
/ 08 декабря 2008

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

Решение, не зависящее от языка, заключается в том, чтобы выяснить, какова реализация printf на вашем языке.

printf ("float: %.1f\n", number);
3 голосов
/ 08 декабря 2008

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

1 голос
/ 08 декабря 2008

Либо используйте целые числа вместо типа с плавающей запятой, либо используйте тип с плавающей запятой, где «точка» является десятичной точкой (например, System.Decimal в .NET).

1 голос
/ 08 декабря 2008

Если вы используете язык с функцией округления, вы можете использовать его.

Редактировать

В ответ на комментарии по поводу округления, вот пример в c #:

float value = 1.0F;

for (int i = 0; i < 20; i++)
{
    value -= 0.1F;
    Console.WriteLine(Math.Round(value, 1).ToString() + " : " + value.ToString());
}

Результаты:

0,9: 0,9

0,8: 0,8

0,7: 0,6999999

0,6: 0,5999999

(и т.д.)

Округление решает проблему точности. Я не утверждаю, что это лучше, чем делать целочисленную математику, а затем делить на 10, просто это работает.

0 голосов
/ 31 декабря 2008

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

0 голосов
/ 31 декабря 2008

Это немного нелогично, но я протестировал его, и он работает (пример в AS3):

var inc:Number = nextMultiple(min, stepSize);
trace(String(inc));

private function nextMultiple(x:Number, y:Number) {
    return Math.ceil(x/y)*(y*10)/10;
}

Таким образом, единственное, что я добавил, это умножение y на 10, а затем деление на 10. Не универсальное решение, но оно работает с вашим stepSize.

[edit:] Логика здесь заключается в том, что вы умножаете на достаточно большое число, чтобы последние десятичные цифры «выпали из шкалы», а затем снова делите, чтобы получить округленное число. Тем не менее, приведенный выше пример, в котором используется Math.round () , более читабелен и лучше в том смысле, что код явно говорит, что произойдет с числами, переданными в.

0 голосов
/ 21 декабря 2008

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

//this will round to 3 decimal places
var NUM_SCALE = 1000;

function scaleUpNumber(n) {
    return (Math.floor(n * NUM_SCALE));
}
function scaleDnNumber(n) {
    return (n / NUM_SCALE);
}


var answ = scaleUpNumber(2.1) - scaleUpNumber(3.001);
alert(scaleDnNumber(answ)); // displays: -0.901

Измените NUM_SCALE, чтобы увеличить / уменьшить количество десятичных знаков

| / | ах

0 голосов
/ 11 декабря 2008

Если у вас нет printf, или если шаги не просто степенями 10 (например, если вы хотите округлить до ближайшего 0,2), то это звучит так, как будто вы хотите квантователь:

q (x, u) = u * этаж (x / u + 0,5);

«u» - размер шага (в вашем случае 0,1), floor () находит наибольшее целое число, не превышающее его входного значения, а «+ 0,5» - округление до ближайшего целого числа.

Таким образом, вы делите на размер шага, округляете до ближайшего целого числа и умножаете на размер шага.

edit: о, не берите в голову, вы все равно делаете это, и шаг, на котором он умножается на u, приводит к ошибке округления.

0 голосов
/ 09 декабря 2008

Я сделал следующее,

var digitsNbr:Number = Math.abs(Math.ceil(((Math.log(stepSize) / Math.log(10))) + 1));      
tickTxt.text = String(inc.toPrecision(digitsNbr));

Это не эффективно, но у меня не так много шагов.

====== Я должен просто получить nbr шагов как целое и умножить на шаг ...

0 голосов
/ 08 декабря 2008

С вашей конкретной проблемой, считайте от -5 до 5 и делите на 10, прежде чем использовать значение для чего-либо.

...