Как бороться с точностью чисел с плавающей точкой в ​​JavaScript?

Вы не можете этого сделать - это изменение подписи, определенной на базе. Чтобы сделать то, что вы пытаетесь сделать, вам нужно использовать дженерики:

Public Class OrderBase(Of T As IOrderItem)
    Public ReadOnly Property OrderItems As IList(Of T)
End Class

Мой Visual Basic ржавый, поэтому, надеюсь, это точно ...

520
задан Derek Pollard 18 July 2019 в 21:35
поделиться

20 ответов

Из руководства Floating-Point Guide :

Что я могу сделать, чтобы избежать этой проблемы?

Это зависит от типа расчеты, которые вы делаете.

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

Обратите внимание, что первый пункт применяется только в том случае, если вам действительно требуется конкретное точное десятичное поведение. Большинству людей это не нужно, их просто раздражает, что их программы некорректно работают с числами вроде 1/10, не понимая, что они бы даже не моргнули при той же ошибке, если бы она произошла с 1/3.

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

442
ответ дан 22 November 2019 в 22:18
поделиться

Как насчет toLocaleString метод?

> 0.1+0.2
< 0.30000000000000004

> (0.1+0.2).toLocaleString()
< 0.3

, Если Вы хотите, чтобы он продолжил свое вычислимое.

Number.parseFloat((0.1+0.2).toLocaleString())

Number.prototype.toLocaleString ()

, Почему бы не посмотреть документ ?

> 1000.021+0.02
< 1000.0409999999999


> (1000.021+0.02).toLocaleString()
< "1,000.041"

/// why not use this?
> (1000.021+0.02).toLocaleString("zh",{useGrouping: false,maximumFractionDigits: 10})
< "1000.041"
-1
ответ дан zheng 17 September 2019 в 06:19
поделиться
  • 1
    They' ре, не ОЧЕНЬ полезное, но иногда they' ре, полезное, где иначе метод был бы наиболее логичным решением (воображают метод, содержащий, например, цикл и Вы хочет возвратиться из метода в цикле). Существуют один или два примера в источнике JDK, если Вы смотрите тщательно. – Neil Coffey 15 April 2010 в 00:24

Изящный, Предсказуемый, и Допускающий повторное использование

Позволяют нам иметь дело с проблемой изящным способом допускающий повторное использование путь. Следующие семь строк позволят Вам получить доступ к точности с плавающей точкой, которой Вы требуете на любом числе просто путем добавления .decimal до конца числа, формулы, или созданный в Math функция.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

За Ваше здоровье!

-1
ответ дан Bernesto 17 September 2019 в 06:19
поделиться

decimal.js, big.js или bignumber.js может использоваться для предотвращения проблем управления с плавающей точкой в JavaScript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: минималист; простой в использовании; точность указана в десятичных разрядах; точность относилась к подразделению только.

bignumber.js: основания 2-64; параметры конфигурации; NaN; Бесконечность; точность указана в десятичных разрядах; точность относилась к подразделению только; основные префиксы.

decimal.js: основания 2-64; параметры конфигурации; NaN; Бесконечность; полномочия нецелого числа, exp, ln, журнал; точность указана в значащих цифрах; точность всегда применяется; случайные числа.

ссылка на подробные сравнения

1
ответ дан 22 November 2019 в 22:18
поделиться

Используйте

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);
1
ответ дан 22 November 2019 в 22:18
поделиться

Взгляните на Арифметика с фиксированной точкой . Это, вероятно, решит вашу проблему, если диапазон чисел, с которыми вы хотите работать, невелик (например, валюта). Я бы округлил его до нескольких десятичных значений, что является самым простым решением.

3
ответ дан 22 November 2019 в 22:18
поделиться

не изящно, но выполняет свою работу (удаляет конечные нули)

var num = 0.1*0.2;
alert(parseFloat(num.toFixed(10))); // shows 0.02
1
ответ дан 22 November 2019 в 22:18
поделиться

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

3
ответ дан 22 November 2019 в 22:18
поделиться

Вы не можете точно представить большинство десятичных дробей с помощью двоичных типов с плавающей запятой (это то, что ECMAScript использует для представления значений с плавающей запятой). Таким образом, нет элегантного решения, если вы не используете арифметические типы произвольной точности или десятичный тип с плавающей запятой. Например, приложение «Калькулятор», поставляемое с Windows, теперь использует арифметику произвольной точности для решения этой проблемы .

3
ответ дан 22 November 2019 в 22:18
поделиться

Вам просто нужно решить, сколько десятичных цифр вы действительно хотите - не можете съесть пирог и съесть его: -)

Числовые ошибки накапливаются с каждой последующей операцией и если вы не срежете его рано, он просто вырастет. Числовые библиотеки, которые представляют результаты, которые выглядят чистыми, просто отсекают последние 2 цифры на каждом шаге, числовые сопроцессоры также имеют "нормальную" и "полную" длину по той же причине. Cuf-off дешево для процессора, но очень дорого для вас в сценарии (умножение, деление и использование pov (...)). Хорошая математическая библиотека предоставит пол (x, n), чтобы обрезать за вас.

По крайней мере, вы должны сделать глобальную переменную / константу с помощью pov (10, n) - это означает, что вы определились с необходимой точностью :-) Затем выполните:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

Вы также можете продолжать выполнять математические вычисления и только сокращать -off в конце - предполагается, что вы только отображаете, а не выполняете if-s с результатами. Если вы можете это сделать, то .toFixed (...) может быть более эффективным.

Если вы выполняете сравнение if-s / и не хотите сокращать его, вам также понадобится небольшая константа, обычно называемая eps, которая на один десятичный знак больше, чем максимальная ожидаемая ошибка. Скажите, что ваше отсечение - это два последних десятичных знака - тогда ваш eps имеет 1 на 3-м месте от последнего (3-е наименее значимое), и вы можете использовать его, чтобы сравнить, находится ли результат в пределах ожидаемого диапазона eps (0,02 -eps <0,1 * 0,2 <0,02 + eps).

11
ответ дан 22 November 2019 в 22:18
поделиться

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

Попробуйте javascript-sprintf , вы бы назвали его так:

var yourString = sprintf("%.2f", yourNumber);

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

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

26
ответ дан 22 November 2019 в 22:18
поделиться

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

Конечно, это не очень поможет с иррациональными числами. Но вы можете захотеть оптимизировать свои вычисления таким образом, чтобы они вызывали меньше всего проблем (например, выявляя ситуации типа sqrt(3)^2).

1
ответ дан 22 November 2019 в 22:18
поделиться
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

--- или ---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

--- также ---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- как в ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2
15
ответ дан 22 November 2019 в 22:18
поделиться

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

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}
14
ответ дан 22 November 2019 в 22:18
поделиться

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

0,1 в двоичном формате с плавающей запятой - это как 1/3 в десятичном (т.е. 0,3333333333333 ... навсегда), просто нет точного способа с этим справиться.

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

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

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

6
ответ дан 22 November 2019 в 22:18
поделиться

Чтобы избежать этого, вы должны работать с целочисленными значениями, а не с плавающими точками. Так, если вы хотите иметь точность в 2 позиции, работайте со значениями * 100, для 3 позиций используйте 1000. При отображении вы используете форматтер для вставки разделителя.

Многие системы не работают с десятичными дробями таким образом. Именно по этой причине многие системы работают с центами (как с целым числом), а не с долларами/евро (как с плавающей точкой).

6
ответ дан 22 November 2019 в 22:18
поделиться

Вы только выполняете умножение? В таком случае вы можете использовать в своих интересах изящный секрет десятичной арифметики. То есть NumberOfDecimals (X) + NumberOfDecimals (Y) = ExpectedNumberOfDecimals . То есть, если у нас есть 0,123 * 0,12 , то мы знаем, что будет 5 десятичных знаков, потому что 0,123 имеет 3 десятичных разряда, а 0,12 - два. Таким образом, если JavaScript дал нам число вроде 0,014760000002 , мы можем безопасно округлить до 5-го десятичного знака, не опасаясь потери точности.

42
ответ дан 22 November 2019 в 22:18
поделиться

Постарайтесь не иметь дело с плавающими точками во время операции с помощью Целых чисел

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

, Например, если Вы работаете с 2 десятичными числами, Вы умножаете все свои факторы на 100 прежде, чем сделать операцию и затем делите результат на 100.

Вот пример, Result1 является обычным результатом, Result2 использует решение:

var Factor1="1110.7";
var Factor2="2220.2";
var Result1=Number(Factor1)+Number(Factor2);
var Result2=((Number(Factor1)*100)+(Number(Factor2)*100))/100;
var Result3=(Number(parseFloat(Number(Factor1))+parseFloat(Number(Factor2))).toPrecision(2));
document.write("Result1: "+Result1+"<br>Result2: "+Result2+"<br>Result3: "+Result3);

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

0
ответ дан 22 November 2019 в 22:18
поделиться

Решенный это первым созданием обоих целых чисел чисел, выполнением выражения и впоследствии делением результата для возвращения десятичных разрядов:

function evalMathematicalExpression(a, b, op) {
    const smallest = String(a < b ? a : b);
    const factor = smallest.length - smallest.indexOf('.');

    for (let i = 0; i < factor; i++) {
        b *= 10;
        a *= 10;
    }

    a = Math.round(a);
    b = Math.round(b);
    const m = 10 ** factor;
    switch (op) {
        case '+':
            return (a + b) / m;
        case '-':
            return (a - b) / m;
        case '*':
            return (a * b) / (m ** 2);
        case '/':
            return a / b;
    }

    throw `Unknown operator ${op}`;
}

Результаты для нескольких операций (исключенные числа являются результатами eval):

0.1 + 0.002   = 0.102 (0.10200000000000001)
53 + 1000     = 1053 (1053)
0.1 - 0.3     = -0.2 (-0.19999999999999998)
53 - -1000    = 1053 (1053)
0.3 * 0.0003  = 0.00009 (0.00008999999999999999)
100 * 25      = 2500 (2500)
0.9 / 0.03    = 30 (30.000000000000004)
100 / 50      = 2 (2)
0
ответ дан 22 November 2019 в 22:18
поделиться

Я выглядел одинаково, фиксируют, и я решил что, если Вы включаете целое число как 1 и оцениваете это console.log(0.1 * 0.2 + 1);. Который приводит к 1.02. Это может привыкнуть к раунду исходная x переменная к корректной сумме.

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

Видят в коде относительно того, что эта функция делает в прокомментированных разделах.

var myX= 0.2 * 0.1;
var myX= 42.5-42.65;
var myX= 123+333+3.33+33333.3+333+333;

console.log(myX);
// Outputs (example 1): 0.020000000000000004
// Outputs (example 2): -0.14999999999999858
// Outputs (example 3): 34458.630000000005
// Wrong

function fixRoundingError(x) {
// 1. Rounds to the nearest 10
//    Also adds 1 to round of the value in some other cases, original x variable will be used later on to get the corrected result.
var xRound = eval(x.toFixed(10)) + 1;
// 2. Using regular expression, remove all digits up until the decimal place of the corrected equation is evaluated..
var xDec = xRound.toString().replace(/\d+\.+/gm,'');
// 3. Gets the length of the decimal places.
var xDecLen = xDec.length;
// 4. Uses the original x variable along with the decimal length to fix the rounding issue.
var x = eval(x).toFixed(xDecLen);
// 5. Evaluate the new x variable to remove any unwanted trailing 0's should there be any.
return eval(x);
}

console.log(fixRoundingError(myX));
// Outputs (example 1): 0.02
// Outputs (example 2): -0.15
// Outputs (example 3): 34458.63
// Correct

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

0
ответ дан 22 November 2019 в 22:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: