В .NET Core я изменил параметр onDelete на ReferencialAction.NoAction
constraints: table =>
{
table.PrimaryKey("PK_Schedule", x => x.Id);
table.ForeignKey(
name: "FK_Schedule_Teams_HomeId",
column: x => x.HomeId,
principalTable: "Teams",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
table.ForeignKey(
name: "FK_Schedule_Teams_VisitorId",
column: x => x.VisitorId,
principalTable: "Teams",
principalColumn: "Id",
onDelete: ReferentialAction.NoAction);
});
Математическая математика с плавающей запятой такова. В большинстве языков программирования он основан на стандарте IEEE 754 . JavaScript использует 64-битное представление с плавающей запятой, которое совпадает с Java double
. Суть проблемы состоит в том, что числа представлены в этом формате как целое число раз в два раза; рациональные числа (такие как 0.1
, который является 1/10
), знаменатель которого не является степенью двух, не могут быть точно представлены.
Для 0.1
в стандартном формате binary64
представление может записывается в точности как
0.1000000000000000055511151231257827021181583404541015625
в десятичной форме или 0x1.999999999999ap-4
в нотации C99 hexfloat . Напротив, рациональное число 0.1
, которое является 1/10
, может быть записано точно как
0.1
в десятичной форме или 0x1.99999999999999...p-4
в аналоге обозначения гексафлоата C99, где ...
представляет собой бесконечную последовательность 9. Константы 0.2
и 0.3
в вашей программе также будут приближенными к их истинные ценности. Бывает, что ближайший double
до 0.2
больше, чем рациональное число 0.2
, но ближайший double
до 0.3
меньше, чем рациональное число 0.3
. Сумма 0.1
и 0.2
заканчивается выше, чем рациональное число 0.3
и, следовательно, не согласуется с константой в вашем коде.
Достаточно полное рассмотрение арифметических вопросов с плавающей запятой Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой . Для более простого объяснения см. floating-point-gui.de .
Тип математики с плавающей запятой, которая может быть реализована на цифровом компьютере, обязательно использует приближение реальных чисел и операций над ними. (Стандартная версия стандартная работает до более чем пятидесяти страниц документации и имеет комитет для рассмотрения ее ошибок и дальнейшего уточнения.)
Это приближение представляет собой смесь приближений разного типа, каждый из которых можно либо игнорировать, либо тщательно учитывать из-за его конкретного способа отклонения от точности. Это также включает в себя ряд явных исключительных случаев как на уровне аппаратного обеспечения, так и на уровне программного обеспечения, которое большинство людей прогуливает прямо мимо, делая вид, что не замечает.
Если вам нужна бесконечная точность (например, вместо числа π одного из его более коротких резервных копий), вы должны написать или использовать символическую математическую программу.
Но если вы в порядке с идеей о том, что иногда математика с плавающей запятой нечеткая по значению и логике и ошибки могут быстро накапливаться, и вы можете написать свои требования и тесты для этого, тогда ваш код может часто проходить с помощью того, что находится в вашем FPU.
Учитывая, что никто не упомянул об этом ...
Некоторые языки высокого уровня, такие как Python и Java, поставляются с инструментами для преодоления ограничений двоичной с плавающей запятой. Например:
decimal
Python [и g0] и класс BigDecimal
Java [], которые представляют числа внутри с десятичной нотацией (в отличие от двоичная запись). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с бинарной арифметикой с плавающей запятой. Десятичные числа очень хороши при работе с деньгами: десять центов плюс двадцать центов всегда ровно тридцать центов: >>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Модуль decimal
Python основан на стандарте IEEE стандарта 802.118 . 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 вместо Math использовать ошибки плавания
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) пиццы? На самом деле подумайте об этом и попробуйте разобраться. Вы даже можете попытаться использовать настоящую пиццу, если у вас есть мифическая пресса для резки пиццы под рукой. : -)
Большинство опытных программистов, конечно же, знают реальный ответ, который заключается в том, что нет возможности собрать кусок точной десятой или пятой пиццы используя эти срезы, независимо от того, насколько мелко вы их нарезаете. Вы можете сделать довольно хорошее приближение, и если вы добавите аппроксимацию 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.
в частности, 0,1 + 0,2 действительно 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, тогда как число ближе к 0,3 фактически 0,299999999999999988897769753748434595763683319091796875.
<ч>П.С. Некоторые языки программирования также предоставляют резаки для пиццы, которые могут разделять фрагменты на точные десятки . Хотя такие резаки для пиццы необычны, если у вас есть доступ к одному, вы должны использовать его, когда важно получить ровно одну десятую или одну пятую части среза.
ч>Резюме
Арифметика с плавающей точкой точный, к сожалению, он не очень хорошо сочетается с нашим обычным представлением числа base-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.
Как это произошло?
Когда мы пишем в десятичной форме, каждая дробь является рациональным числом форма
& nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; & nbsp; x / (2n + 5n).
В двоичном выражении мы получаем только член 2n , то есть:
& nbsp ; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; & NBSP; x / 2n
Итак, в десятичной форме мы не можем представлять 1/3. Поскольку база 10 включает в себя 2 как простой коэффициент, каждое число, которое мы можем записать как двоичную дробь , также может быть записано в виде базовой дроби. Однако вряд ли что-либо, что мы пишем как 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/3. Но в двоичном коде мы не можем делать 1/10 или 1/3.
Так что, хотя каждая двоичная дробь может быть записана в десятичной системе, обратное неверно. И фактически большинство десятичных дробей повторяются в двоичном формате.
Работа с ним
Разработчикам обычно дают указание & Lt; epsilon , лучшим советом может быть округление до целочисленных значений (в библиотеке C: round () и roundf (), т. е. оставаться в формате FP), а затем сравнивать. Округление до определенной длины десятичной дроби решает большинство проблем с выходом.
Кроме того, при реальных проблемах с хрустом (проблемы, которые FP был изобретен на ранних, ужасно дорогих компьютерах) физические константы вселенной и все другие измерения известны только относительно небольшому числу значимых цифр, поэтому все пространство проблем было «неточным» в любом случае. FP «точность» не является проблемой в этом виде приложений.
Вся проблема действительно возникает, когда люди пытаются использовать FP для подсчета бобов. Это работает для этого, но только если вы придерживаетесь интегральных значений, какой вид поражает смысл его использования. Вот почему у нас есть все эти библиотеки программного обеспечения с десятичной дроби.
Мне нравится ответ на пиццу от Chris , потому что он описывает фактическую проблему, а не только обычная ручная работа о «неточности». Если бы FP были просто «неточными», мы могли бы исправить , что и сделали бы это несколько десятилетий назад. Причина, по которой у нас нет, - это то, что формат FP компактен и быстр, и это лучший способ хрустить множество чисел. Кроме того, это наследие космической эры и гонки вооружений и ранние попытки решить большие проблемы с очень медленными компьютерами с использованием небольших систем памяти. (Иногда отдельные магнитные сердечники для 1-битного хранилища, но это другая история. )
Заключение
Если вы просто считаете бобы в банке, программные решения, которые используют представления десятичной строки, в первую очередь работают отлично. Но вы не можете делать квантовую хромодинамику или аэродинамику таким образом.
nextafter()
с целым приращением или декрементом в двоичном представлении поплавка IEEE. Кроме того, вы можете сравнить float как целые числа и получить правильный ответ, за исключением случаев, когда они оба отрицательные (из-за знака-величины по сравнению с дополнением 2).
– Peter Cordes
9 December 2016 в 04:35
В дополнение к другим правильным ответам вы можете рассмотреть возможность масштабирования ваших значений, чтобы избежать проблем с арифметикой с плавающей запятой.
Например:
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 Дуглас Крокфорд: 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;
}
precision относится к числу цифр, которые вы хотите сохранить после десятичной точки во время добавления.
Некоторые статистические данные, связанные с этим известным вопросом с двойной точностью.
При добавлении всех значений ( 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), мы имеем вероятность 34% точности. Вот несколько примеров:
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,29999999999999999989897769753748434595763683319091796875 и
0,30000000000000004 преобразуется в 0,3000000000000000444089209850062616169452667236328125.
Добавление первых двух чисел вручную или в десятичный калькулятор, такой как Full Precision Calculator , показывает точную сумму фактических входов: 0.3000000000000000166533453693773481063544750213623046875.
Если округлить до эквивалента 0,3, ошибка округления будет 0.0000000000000000277555756156289135105907917022705078125. Округление до эквивалента 0,30000000000000004 также дает ошибку округления 0,0000000000000000277555756156289135105907917022705078125.
Возвращаясь к конвертеру с плавающей запятой, необработанный шестнадцатеричный показатель для 0.30000000000000004 равен 3fd3333333333334, который заканчивается четной цифрой и, следовательно, является правильным результатом.
Поскольку этот поток немного разветвился в общем обсуждении текущих реализаций с плавающей запятой, я бы добавил, что есть проекты по исправлению их проблем.
Взгляните на https: // posithub.org/, который демонстрирует тип номера, называемый posit (и его предшественник unum), который обещает предложить лучшую точность с меньшим количеством бит. Если мое понимание верное, оно также фиксирует проблемы в вопросе. Весьма интересный проект, человек, стоящий за ним, является математиком Dr. Джон Густафсон . Все это с открытым исходным кодом, с множеством реализаций в 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,677, что также будет технически неточным.
Подсчитайте в тройном, а третья не проблема, может быть, какая-то гонка с 15 пальцами на каждой руке спросит, почему ваша десятичная математика была сломана ...
Мой ответ довольно длинный, поэтому я разделил его на три раздела. Поскольку вопрос касается математики с плавающей запятой, я делаю акцент на том, что на самом деле делает машина. Я также сделал это для двойной точности (64 бит), но этот аргумент применим в равной степени к любой арифметике с плавающей запятой.
Преамбула
IEEE 754 номер двойной бинарной с плавающей запятой (binary64) представляет собой номер формы
value = (-1) ^ s * (1.m51m50 ... m2m1m0 ) 2 * 2e-1023
в 64 бит:
- Первый бит является битом знака :
1
, если число отрицательно,0
в противном случае.- Следующие 11 бит являются показателем , который является offset на 1023. Другими словами, после чтение битов экспоненты из числа двойной точности 1023 должно быть вычтено для получения мощности двух.
- Остальные 52 бита представляют собой значение (или мантисса). В мантиссе «подразумеваемый»
1.
всегда пропускается, поскольку самый старший бит любого двоичного значения равен1
.1 - IEEE 754 допускает концепцию с нулевым значением -
+0
и-0
обрабатываются по-разному:1 / (+0)
- положительная бесконечность;1 / (-0)
- отрицательная бесконечность. Для нулевых значений биты мантиссы и экспоненты равны нулю. Примечание: нулевые значения (+0 и -0) явно не классифицируются как denormal2.2 - Это не относится к денормальным номерам , которые имеют показатель смещения нуля (и подразумевается
0.
). Диапазон денормальных чисел двойной точности dmin ≤ | x | ≤ dmax, где dmin (наименьшее представимое ненулевое число) составляет 2-1023 - 51 (≈ 4,94 * 10-324) и dmax (наибольшее денормальное число, для которого мантисса полностью состоит из1
s) составляет 2-1023 + 1 - 2-1023 - 51 (≈ 2.225 * 10-308).
Превращение числа двойной точности в двоичный
Существует множество онлайн-конвертеров для преобразования двойной точности (например, в binaryconvert.com ), но здесь приведен пример кода C # для получения представления IEEE 754 для числа двойной точности (я разделяю три части с двоеточиями (
:
):public static string BinaryRepresentation(double value) { long valueInLongType = BitConverter.DoubleToInt64Bits(value); string bits = Convert.ToString(valueInLongType, 2); string leadingZeros = new string('0', 64 - bits.Length); string binaryRepresentation = leadingZeros + bits; string sign = binaryRepresentation[0].ToString(); string exponent = binaryRepresentation.Substring(1, 11); string mantissa = binaryRepresentation.Substring(12); return string.Format("{0}:{1}:{2}", sign, exponent, mantissa); }
Достижение точки: исходный вопрос
(Перейти к нижней части для версии TL; DR)
Катон Джонстон (вопросник) спросил, почему 0.1 + 0.2! = 0.3.
Написано в двоичном (с двоеточиями, разделяющими три части), представления IEEE 754 значений:
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010 0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
Обратите внимание, что мантисса состоит из повторяющихся цифр
0011
. ключ к тому, почему есть какие-либо ошибки в расчетах - 0,1, 0,2 и 0,3 не могут быть представлены в двоичной форме точно в конечном числе двоичных битов не более 1/9, 1/3 или 1/7 могут быть представлены точно в десятичных разрядах .Преобразование экспонентов в десятичные, удаление смещения и повторное добавление подразумеваемых
1
(в квадратных скобках), 0,1 и 0,2 :0.1 = 2^-4 * [1].1001100110011001100110011001100110011001100110011010 0.2 = 2^-3 * [1].1001100110011001100110011001100110011001100110011010
Чтобы добавить два числа, показатель должен быть одинаковым, т. е.
0.1 = 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0) 0.2 = 2^-3 * 1.1001100110011001100110011001100110011001100110011010 sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
Поскольку сумма не имеет вид 2n * 1. { bbb} мы увеличиваем показатель на единицу и сдвигаем десятичную ( двоичную ) точку, чтобы получить:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
В мантиссе сейчас 53 бит (53-й квадрат скобки в строке выше). Режим округления по умолчанию для IEEE 754 равен ' Round to Nearest ' - то есть, если число x падает между двумя значениями a и b выбрано значение, в котором наименьший значащий бит равен нулю.
a = 2^-2 * 1.0011001100110011001100110011001100110011001100110011 x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1) b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
Обратите внимание, что a и b отличаются только последним битом;
...0011
+1
=...0100
. В этом случае значение с младшим значащим разрядом равно b , поэтому сумма равна:sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
TL; DR
Запись
0.1 + 0.2
в двоичном представлении IEEE 754 (с двоеточиями, разделяющими три части) и сравнивая его с0.3
, это (я положил отдельные биты в квадратные скобки):0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100] 0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
Преобразован обратно к десятичной, эти значения:
0.1 + 0.2 => 0.300000000000000044408920985006... 0.3 => 0.299999999999999988897769753748...
Разница в точности равна 2-54, что составляет ~ 5.5511151231258 × 10-17 - незначительно (для многих приложений) по сравнению с исходными значениями.
Сравнение последних нескольких бит числа с плавающей запятой по своей сути опасно, как и любой, кто читает знаменитый «
, что каждый компьютерный ученый должен знать о арифметике с плавающей точкой » (который охватывает все основные части этого ответа).Большинство калькуляторов используют дополнительные охранные цифры , чтобы обойти эту проблему, так как
0.1 + 0.2
даст0.3
: последние несколько биты округлены.
Ошибка округления с плавающей точкой. Из Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой :
Сжатие бесконечного числа действительных чисел в конечное число бит требует приблизительного представления. Несмотря на то, что в большинстве программ имеется бесконечно много целых чисел, результат целочисленных вычислений может быть сохранен в 32 битах. В отличие от этого, учитывая любое фиксированное количество бит, большинство вычислений с реальными числами будут давать количества, которые не могут быть точно представлены с использованием этого количества бит. Поэтому результат вычисления с плавающей запятой часто должен быть округлен, чтобы соответствовать его конечному представлению. Эта ошибка округления является характерной особенностью вычисления с плавающей запятой.