Рассмотрим следующий код:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Почему происходят эти неточности?
Тип математики с плавающей запятой, который может быть реализован на цифровом компьютере, обязательно использует приближение действительных чисел и операций над ними. (Стандартная версия работает до более чем пятидесяти страниц документации и имеет комитет для рассмотрения своих ошибок и дальнейшего уточнения.)
Это приближение представляет собой смесь приближений разных видов, каждый из которых можно игнорировать или тщательно учитывается в силу его специфического способа отклонения от точности. Это также включает в себя ряд явных исключительных случаев как на аппаратном, так и на программном уровне, которые большинство людей ходят в прошлом, но притворяясь, что они не замечают.
Если вам нужна бесконечная точность (например, вместо числа π одного из его более коротких резервных копий), вы должны написать или использовать символическую математическую программу.
Но если вы в порядке с идеей, что иногда математика с плавающей запятой нечеткая по значению и логике и ошибки могут быстро накапливаться, и вы можете написать свои требования и тесты для этого, тогда ваш код может часто проходить с помощью того, что находится в вашем FPU.
Учитывая, что никто не упомянул об этом ...
Некоторые языки высокого уровня, такие как Python и Java, поставляются с инструментами для преодоления ограничений двоичной с плавающей запятой. Например:
модуль decimal Python и класс BigDecimal Java, которые представляют числа внутри с десятичной нотацией (в отличие от двоичной нотации). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с бинарной арифметикой с плавающей запятой. Десятичные числа очень хороши при работе с деньгами: десять центов плюс двадцать центов всегда ровно тридцать центов:>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Модуль Python decimal основан на стандарте IEEE 854-1987. Python fractions и класс Apache Common BigFraction. Оба представляют собой рациональные числа как пары (numerator, denominator), и они могут давать более точные результаты, чем десятичная арифметика с плавающей запятой. Ни одно из этих решений не является совершенным (особенно если мы смотрим на действия, или если нам требуется очень высокая точность), но все же они решают большое количество проблем с двоичной арифметикой с плавающей запятой.
Другой вопрос был назван дубликатом этого:
В C ++ почему результат cout << x отличается от значения, которое показывает отладчик для x?
x в вопросе является переменной float.
В качестве примера можно привести
float x = 9.9F;
Отладчик показывает 9.89999962, вывод cout 9.9.
Ответ оказывается, что точность cout по умолчанию для float равна 6, поэтому округляет до шести десятичных цифр.
См. здесь для справки
Math.sum (javascript) .... вид замены оператора
.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
sign: {
value: function (x) {
return x ? x < 0 ? -1 : 1 : 0;
}
},
precision: {
value: function (value, precision, type) {
var v = parseFloat(value),
p = Math.max(precision, 0) || 0,
t = type || 'round';
return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
}
},
scientific_to_num: { // this is from https://gist.github.com/jiggzson
value: function (num) {
//if the number is in scientific notation remove it
if (/e/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if (sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
} else {
var dec = coeff_array[1];
if (dec)
l = l - dec.length;
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
return num;
}
}
get_precision: {
value: function (number) {
var arr = Math.scientific_to_num((number + "")).split(".");
return arr[1] ? arr[1].length : 0;
}
},
diff:{
value: function(A,B){
var prec = this.max(this.get_precision(A),this.get_precision(B));
return +this.precision(A-B,prec);
}
},
sum: {
value: function () {
var prec = 0, sum = 0;
for (var i = 0; i < arguments.length; i++) {
prec = this.max(prec, this.get_precision(arguments[i]));
sum += +arguments[i]; // force float to convert strings to number
}
return Math.precision(sum, prec);
}
}
});
Идея состоит в том, чтобы вместо Math использовать операторы, чтобы избежать ошибок с плавающей запятой [ ! d3]
Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false
также отмечают, что Math.diff и Math.sum автоматически определяют точность использования
Math.sum принимает любое количество аргументов
Было опубликовано много хороших ответов, но я хотел бы добавить еще один.
Не все числа могут быть представлены с помощью float / double. Например, будет представлено число «0,2» как «0.200000003» в единой точности в стандарте по плавающей точке IEEE754.
Модель для хранения действительных чисел под капотом представляет собой число с плавающей запятой как
тип 0.2 легко, FLT_RADIX и DBL_RADIX равно 2; не 10 для компьютера с FPU, который использует «стандарт IEEE для двоичной арифметики с плавающей запятой» (ISO / IEEE Std 754-1985) ».
Так что это очень сложно точно представить такие числа. Даже если вы укажете эту переменную явно без какого-либо промежуточного вычисления.
Большинство ответов здесь затрагивают этот вопрос в очень сухих технических терминах. Я хотел бы затронуть это в терминах, которые могут понять обычные люди.
Представьте, что вы пытаетесь нарезать пиццу. У вас есть роботизированный нож для пиццы, который может разрезать кусочки пиццы . Большинство ответов здесь затрагивают этот вопрос в очень сухих технических терминах. Я хотел бы остановиться на этом в терминах, которые могут понять обычные люди. пополам.
Этот резак для пиццы имеет очень тонкие движения, и если вы начнете с целой пиццы, затем уменьшите вдвое и продолжайте делать поменьше наименьший срез каждый раз, вы можете сделать половину 53 раз, прежде чем срез слишком мал для даже его высокоточных способностей. В этот момент вы уже не можете вдвое уменьшить этот тонкий срез, но должны либо включать, либо исключать его, как есть.
Теперь, как бы вы отделили все кусочки таким образом, чтобы это было бы до одного (0,1) или одну пятую (0,2) пиццы? На самом деле подумайте об этом и попробуйте разобраться. Вы даже можете попытаться использовать настоящую пиццу, если у вас есть мифическая пресса для резки пиццы под рукой. : -)
Большинство опытных программистов, конечно же, знают реальный ответ, который заключается в том, что нет возможности объединить 53 раза десятый или пятую часть пиццы, используя эти кусочки, независимо от того, насколько мелко вы их нарезаете. Вы можете сделать довольно хорошее приближение, и если вы добавите аппроксимацию 0,1 с аппроксимацией 0,2, вы получите довольно хорошее приближение 0,3, но это все равно только приближение.
Для двойного -оценки (это точность, которая позволяет вам вдвое сократить вашу пиццу 53 раза), цифры сразу меньше и больше 0,1 - 0.09999999999999999167332731531132594682276248931884765625 и 0,1000000000000000055511151231257827021181583404541015625. Последнее немного ближе к 0,1, чем первое, поэтому числовой синтаксический анализатор, учитывая ввод 0,1, благоприятствует последнему.
(Разница между этими двумя числами - это «самый маленький срез», который мы должны решить либо включить, что вводит восходящее смещение, либо исключить, что приводит к смещению вниз. Техническим термином для этого наименьшего среза является ulp.)
В случае 0.2 цифры все же, просто увеличен в 2 раза. Мы также рекомендуем значение, которое немного больше 0,2.
Обратите внимание, что в обоих случаях аппроксимации для 0,1 и 0,2 имеют небольшое смещение вверх. Если мы добавим достаточно этих предубеждений, они будут толкать число дальше и дальше от того, что мы хотим, а на самом деле, в случае 0,1 + 0,2, смещение достаточно велико, чтобы получившееся число больше не было самым близким числом до 0,3.
[D15] в частности, 0,1 + 0,2 действительно 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, тогда как число ближе к 0,3 на самом деле 0,299999999999999988897769753748434595763683319091796875.[D16] [D17] П.С. Некоторые языки программирования также предоставляют резаки для пиццы, которые могут ulp . Хотя такие резаки для пиццы необычны, если у вас есть доступ к одному, вы должны использовать его, когда важно получить ровно одну десятую или одну пятую части среза.
(Первоначально опубликовано в Quora.)
Арифметика с плавающей точкой является точной, к сожалению, она не совпадает с нашей обычной базой- 10, так что получается, что мы часто даем ему ввод, который немного от того, что мы написали.
Даже простые числа, такие как 0.01, 0.02, 0.03, 0.04 ... 0.24, не представляются точно так же, как бинарные фракции, даже если у вас были тысячи бит точности в мантиссе, даже если у вас были миллионы. Если вы отсчитываете с шагом 0,01, пока вы не достигнете 0,25, вы получите первую фракцию (в этой последовательности), представленную в base10 и base2. Но если вы попытались использовать FP, ваш 0,01 был бы слегка отключен, поэтому единственный способ добавить 25 из них до хорошего точного 0.25 потребовал бы длинной цепи причинности, включающей защитные биты и округление. Трудно предсказать, чтобы мы подняли руки и сказали, что -
Мы постоянно даем аппаратное обеспечение FP, что кажется простым в базе 10, но является повторяющейся фракцией в базе 2. [ ! d10] Резюме
Как это произошло?
Когда мы пишем в десятичной форме, каждая дробь является рациональным числом формы
& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; Как это произошло?
В двоичном коде мы получаем только 2n-член, то есть:
& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; ; & NBSP; & NBSP; & NBSP; & NBSP; x / 2n
Итак, в десятичной форме мы не можем представить 1 / 10 . Поскольку база 10 включает в себя 2 в качестве основного фактора, каждое число, которое мы можем записать как двоичную дробь 2n , можно записать в виде базовой дроби. Однако вряд ли что-либо, что мы пишем как base10, представляется в двоичном виде. В диапазоне от 0,01, 0,02, 0,03 ... 0,99 только три числа могут быть представлены в нашем формате FP: 0,25, 0,50 и 0,75, поскольку они составляют 1/4, 1/2 и 3/4, все числа с простым множителем, использующим только 2n-член.
В базе 10 мы не можем представить 1 / 2 . Но в двоичном коде мы не можем сделать 1/10 x / 2n 1/3.
РезюмеИтак, пока каждая двоичная дробь может быть записана в десятичной форме, обратное неверно. И действительно, большинство десятичных дробей повторяются в двоичном формате.
Разработчикам обычно поручают делать три сравнения , лучше советовать округлять до целых значений (в библиотеке C: round ( ) и roundf (), т. е. оставаться в формате FP), а затем сравнивать. Округление до определенной длины десятичной дробей решает большинство проблем с выходом.
Кроме того, при реальных проблемах с хрустом (проблемы, которые FP был изобретен для ранних, ужасно дорогих компьютеров) физические константы вселенной и все другие измерения известны только относительно небольшому числу значимых цифр, поэтому все пространство проблем было «неточным» в любом случае. FP «точность» не является проблемой в этом виде приложений.
Вся проблема действительно возникает, когда люди пытаются использовать FP для подсчета бобов. Это работает для этого, но только если вы придерживаетесь интегральных значений, какой вид поражает смысл его использования. Вот почему у нас есть все эти библиотеки программного обеспечения с десятичной дроби.
Резюме
Мне нравится ответ «Пицца» Криса, потому что он описывает фактическую проблему, а не только обычную ручную работу о «неточности». Если бы FP были просто «неточными», мы могли бы исправить это и сделали бы это несколько десятилетий назад. Причина, по которой у нас нет, - это то, что формат FP компактен и быстр, и это лучший способ хрустить множество чисел. Кроме того, это наследие космической эры и гонки вооружений и ранние попытки решить большие проблемы с очень медленными компьютерами с использованием небольших систем памяти. (Иногда отдельные магнитные сердечники для 1-битного хранения, но это уже другая история.)
В дополнение к другим правильным ответам вы можете рассмотреть возможность масштабирования ваших значений, чтобы избежать проблем с арифметикой с плавающей запятой.
Например:
var result = 1.0 + 2.0; // result === 3.0 returns true
... вместо:
var result = 0.1 + 0.2; // result === 0.3 returns false
Выражение 0.1 + 0.2 === 0.3 возвращает false в JavaScript, но, к счастью, целочисленная арифметика в плавающей точке является точной, поэтому ошибки масштабирования можно избежать путем масштабирования.
В качестве практического примера, чтобы избежать проблем с плавающей запятой, где точность имеет первостепенное значение, рекомендуется обрабатывать деньги как целое число, представляющее число центов: 2550 центов вместо 25.50 долларов.
1 Douglas Crockford: JavaScript: Хорошие детали: Приложение A - Ужасные части (стр. 105).
Ошибки округления с плавающей запятой. 0,1 не могут быть представлены точно в базе-2, как в базе-10, из-за недостающего простого коэффициента 5. Так же, как 1/3 принимает бесконечное число цифр для представления в десятичной форме, но составляет «0,1» в базе-3, 0.1 принимает бесконечное число цифр в базе-2, где оно не находится в базе-10. И у компьютеров нет бесконечного объема памяти.
Когда вы конвертируете .1 или 1/10 в base 2 (двоичный), вы получаете повторяющийся шаблон после десятичной точки, точно так же, как пытаться представить 1/3 в базе 10. Значение не является точным, и поэтому вы можете 't делать точную математику с ним, используя обычные методы с плавающей запятой.
Мое обходное решение:
function add(a, b, precision) {
var x = Math.pow(10, precision || 2);
return (Math.round(a * x) + Math.round(b * x)) / x;
}
точность относится к числу цифр, которые вы хотите сохранить после десятичной точки во время добавления.
Некоторые статистические данные, связанные с этим известным вопросом с двойной точностью.
При добавлении всех значений (a + b) с шагом 0,1 (от 0,1 до 100) мы имеем вероятность 15% точности. Обратите внимание, что ошибка может привести к несколько большим или меньшим значениям. Вот несколько примеров:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
При вычитании всех значений ( a + b , где a> b), используя шаг 0,1 (от 100 до 0,1), имеем ~ 15% вероятность ошибки точности . Вот несколько примеров:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
* 15% и 34% действительно огромны, поэтому всегда используйте BigDecimal, когда точность имеет большое значение. С 2 десятичными цифрами (шаг 0,01) ситуация немного ухудшается (18% и 36%).
Числа с плавающей запятой, хранящиеся в компьютере, состоят из двух частей: целого и экспоненты, в которых база берется и умножается на целую часть.
Если компьютер работал на базе 10, 0.1 будет 1 x 10⁻¹, 0.2 будет 2 x 10⁻¹, а 0.3 будет 3 x 10⁻¹. Целочисленная математика проста и точна, поэтому добавление 0.1 + 0.2, очевидно, приведет к 0.3.
Компьютеры обычно не работают в базе 10, они работают в базе 2. Вы все равно можете получить точные результаты для некоторые значения, например 0.5, равны 1 x 2⁻¹, а 0.25 - 1 x 2⁻², а их добавление приводит к 3 x 2⁻² или 0.75. Точно.
Проблема связана с числами, которые могут быть представлены точно в базе 10, но не в базе 2. Эти числа должны быть округлены до их ближайшего эквивалента. Предполагая, что для 64-битного формата с плавающей точкой IEEE используется очень общий формат, ближайшим номером к 0.1 является 3602879701896397 x 2⁻⁵⁵, а ближайшим номером к 0.2 является 7205759403792794 x 2⁻⁵⁵; добавление их результатов в 10808639105689191 x 2⁻⁵⁵ или точное десятичное значение 0.3000000000000000444089209850062616169452667236328125. Числа с плавающей запятой, как правило, округлены для отображения.
Чтобы предложить лучшее решение, я могу сказать, что обнаружил следующий метод:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
Позвольте мне объяснить, почему это лучшее решение. Как упоминалось выше в других ответах, рекомендуется использовать готовые для использования функции Javascript toFixed () для решения проблемы. Но, скорее всего, вы столкнетесь с некоторыми проблемами.
Представьте, что вы собираетесь добавить два числа с плавающей запятой, такие как 0.2 и 0.7, вот оно: 0.2 + 0.7 = 0.8999999999999999.
Ваш ожидаемый результат 0.9 означает, что в этом случае вам нужен результат с точностью до 1 цифры. Поэтому вы должны были использовать (0.2 + 0.7).tofixed(1), но вы не можете просто указать определенный параметр toFixed (), поскольку он зависит от заданного числа, например
`0.22 + 0.7 = 0.9199999999999999`
В этом примере вам нужна точность в 2 цифры поэтому это должно быть toFixed(2), так что должно быть параметром для каждого заданного числа с плавающей запятой?
Вы могли бы сказать, что пусть это будет 10 в каждой ситуации:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
Черт! Что вы собираетесь делать с этими нежелательными нулями после 9? Пришло время преобразовать его в float, чтобы сделать это по своему желанию:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Теперь, когда вы нашли решение, лучше предложить его как функцию:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Вы можете использовать его следующим образом:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
Просто для удовольствия я играл с представлением поплавков, следуя определениям из стандарта C99, и я написал код ниже.
Код печатает двоичное представление поплавков в 3 отдельных группах
SIGN EXPONENT FRACTION
, и после этого он печатает сумму, которая при суммировании с достаточной точностью покажет значение, которое действительно существует в аппаратном обеспечении.
Поэтому, когда вы пишете float x = 999... компилятор преобразует это число в битовое представление, напечатанное функцией xx, так что сумма, напечатанная функцией yy, будет равна заданному числу.
В действительности эта сумма является только приближение. Для числа 999,999,999 компилятор будет вставлять в бит представление float число 1,000,000,000
После кода я присоединяю консольный сеанс, в котором я вычисляю сумму терминов для обеих констант (минус PI и 999999999) что действительно существует в аппаратном обеспечении, вставленном там компилятором.
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
Вот сеанс консоли, в котором я вычисляю реальное значение float, которое существует в аппаратном обеспечении. Я использовал bc для печати суммы терминов, выводимых основной программой. Можно вставить эту сумму в python repl или что-то подобное.
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
Вот и все. Фактически значение 999999999
999999999.999999446351872
Вы также можете проверить bc, что -3.14 также возмущен. Не забудьте установить коэффициент scale в bc.
Отображаемая сумма - это то, что внутри аппаратного обеспечения. Значение, которое вы получаете, вычисляя его, зависит от установленного вами масштаба. Я установил коэффициент scale равным 15. Математически, с бесконечной точностью, кажется, что это 1 000 000 000.
Sine Python 3.5 вы можете использовать функцию math.isclose(), если условия
import math
if math.isclose(0.1 + 0.2, 0.3, abs_tol=0.01):
pass
Другой способ взглянуть на это: Используются 64 бита для представления чисел. Как следствие, не может быть представлено более 2 ** 64 = 18 446 744 073 709 551 616 различных чисел.
Тем не менее, Math говорит, что существует уже бесконечное число десятичных знаков между 0 и 1. IEE 754 определяет кодировку для эффективного использования этих 64 бит для гораздо большего количества пробелов плюс NaN и +/- Infinity, поэтому есть пробелы между точно представленными числами, заполненными числами, только приближены.
К сожалению, 0,3 сидит в промежутке.
Многие многочисленные дубликаты этого вопроса спрашивают о влиянии округления с плавающей запятой на конкретные числа. На практике легче понять, как это работает, глядя на точные результаты вычислений, а не просто на чтение. Некоторые языки предоставляют способы сделать это - например, преобразование float или double в BigDecimal в Java.
Поскольку это вопрос, связанный с языком, ему нужны языковые агностические инструменты, такие как как конвертер с десятичной точностью для плавающей запятой.
Применяя его к номерам в вопросе, рассматриваемым как двойные:
0,1 преобразуется в 0,1000000000000000055511151231257827021181583404541015625,
0,2 конвертирует до 0.200000000000000011102230246251565404236316680908203125,
0,3 конвертирует в 0.29999999999999999988897769753748434595763683319091796875 и
0.30000000000000004 преобразует в 0.3000000000000000444089209850062616169452667236328125.
Добавление первых двух чисел вручную или в десятичном калькуляторе, таком как Десятичное значение для конвертера с плавающей запятой , показывает точную сумму фактических входов: 0.3000000000000000166533453693773481063544750213623046875.
Если округлить до эквивалента 0,3, ошибка округления составит 0,0000000000000000277555756156289135105907917022705078125. Округление до эквивалента 0,30000000000000004 также дает ошибку округления 0,0000000000000000277555756156289135105907917022705078125.
Возвращаясь к конвертеру с плавающей запятой, необработанный шестнадцатеричный показатель для 0.30000000000000004 равен 3fd3333333333334, который заканчивается четной цифрой и, следовательно, является правильным результатом.
Поскольку этот поток немного разветвился в общем обсуждении текущих реализаций с плавающей точкой, я бы добавил, что есть проекты по исправлению их проблем.
Взгляните на https://posithub.org / например, который демонстрирует тип номера, называемый posit (и его предшественник unum), который обещает предложить лучшую точность с меньшим количеством бит. Если мое понимание верное, оно также фиксирует проблемы в вопросе. Весьма интересный проект, человек, стоящий за ним, является математиком его доктором Джоном Густафсоном. Все это с открытым исходным кодом, с множеством фактических реализаций в C / C ++, Python, Julia и C # (https://hastlayer.com/arithmetics).
Эти странные цифры появляются из-за того, что компьютеры используют двоичную (базовую 2) систему счисления, в то время как мы используем десятичную (базовую 10).
Есть большинство дробных чисел, которые невозможно точно представить в двоичном или десятичном или в обоих. Результат - округленное (но точное) число результатов.
Вы пытались решить проблему с клейкой лентой?
Попробуйте определить, когда возникают ошибки, и исправить их с помощью коротких инструкций if, это не очень, но для некоторых проблем это единственное решение, и это один из них .
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
У меня была такая же проблема в проекте научной симуляции в c #, и я могу сказать вам, что если вы проигнорируете эффект бабочки, он превратится в большого толстого дракона и укусит вас в a **
Могу я просто добавить; люди всегда предполагают, что это компьютерная проблема, но если вы считаете своими руками (база 10), вы не можете получить (1/3+1/3=2/3)=true, если у вас нет бесконечности, чтобы добавить 0.333 ... в 0.333 ... так, как и с (1/10+2/10)!==3/10 в базе 2, вы усекаете ее до 0,333 + 0,333 = 0,666 и, вероятно, округлите ее до 0,667, что также будет технически неточным.
Подсчитайте в тройном, а третья не проблема, может быть, какая-то раса с 15 пальцами на каждой руке спросит, почему ваша десятичная математика была сломана ...