Является ли "десятичная" арифметика .NET независимой от платформы/архитектуры?
Недавно я спросил о System.Double
и сказал, что вычисления могут отличаться в зависимости от платформы/архитектуры. К сожалению, я не могу найти какую-либо информацию, чтобы сообщить мне, относится ли это к System.Decimal
.
Я гарантированно получаю точно такой же результат для любого конкретного вычисления decimal
независимо от платформы/архитектуры?
Ответы
Ответ 1
Я гарантированно получаю точно такой же результат для любого конкретного десятичного вычисления независимо от платформы/архитектуры?
В спецификации С# 4 ясно, что полученное вами значение будет вычисляться одинаково на любой платформе.
Как отмечает LukeH, версия ECMA спецификации С# 2 предоставляет свободу действий для соответствия реализациям, чтобы обеспечить более высокую точность, поэтому реализация С# 2.0 на другой платформе может обеспечить ответ с более высокой точностью.
Для целей этого ответа я просто обсужу указанное поведение С# 4.0.
Спецификация С# 4.0 говорит:
Результатом операции над значениями типа decimal является то, что было бы результатом вычисления точного результата (сохранение шкалы, как определено для каждого оператора), а затем округление в соответствии с представлением. Результаты округляются до ближайшего представляемого значения, и, когда результат одинаково близок к двум представляемым значениям, к значению, которое имеет четное число в наименее значащей позиции разряда [...]. Нулевой результат всегда имеет знак 0 и шкалу от 0.
Поскольку вычисление точного значения операции должно быть одинаковым на любой платформе, а алгоритм округления хорошо определен, результирующее значение должно быть одинаковым независимо от платформы.
Однако обратите внимание на скобки и последнее предложение об нулях. Возможно, неясно, зачем нужна эта информация.
Одна из странностей десятичной системы состоит в том, что почти каждая величина имеет более одного возможного представления. Рассмотрим точное значение 123.456. Десятичная комбинация состоит из 96-битного целого числа, знака с 1 битом и восьмибитового показателя, который представляет число от -28 до 28. Это означает, что точное значение 123.456 может быть представлено десятичными знаками 123456 x 10 - 3 или 1234560 x 10 -4 или 12345600 x 10 -5. Значение шкалы.
Спецификация С# также определяет, как вычисляется информация о масштабе. Литерал 123.456m будет закодирован как 123456 x 10 -3 а 123.4560m будет закодирован как 1234560 x 10 -4.
Наблюдайте за эффектами этой функции в действии:
decimal d1 = 111.111000m;
decimal d2 = 111.111m;
decimal d3 = d1 + d1;
decimal d4 = d2 + d2;
decimal d5 = d1 + d2;
Console.WriteLine(d1);
Console.WriteLine(d2);
Console.WriteLine(d3);
Console.WriteLine(d4);
Console.WriteLine(d5);
Console.WriteLine(d3 == d4);
Console.WriteLine(d4 == d5);
Console.WriteLine(d5 == d3);
Это создает
111.111000
111.111
222.222000
222.222
222.222000
True
True
True
Обратите внимание, что информация о значительных нулевых значениях сохраняется в операциях с десятичными знаками, и что decimal.ToString знает об этом и показывает сохраненные нули, если это возможно. Также обратите внимание на то, как известно десятичное равенство для сравнения на основе точных значений, даже если эти значения имеют разные двоичные и строковые представления.
Спектр, который, как я думаю, на самом деле не говорит, что decimal.ToString() должен правильно распечатывать значения с конечными нулями на основе их масштабов, но было бы глупо с реализацией не делать этого; Я бы подумал, что это ошибка.
Я также отмечаю, что формат внутренней памяти десятичного числа в реализации CLR составляет 128 бит, подразделяемый на: 16 неиспользуемых битов, 8 бит шкалы, еще 7 неиспользуемых битов, 1 знаковый бит и 96 бит мантиссы. Точная компоновка этих бит в памяти не определяется спецификацией, и если другая реализация хочет записать дополнительную информацию в эти 23 неиспользуемых бита для своих собственных целей, она может это сделать. В реализации CLR неиспользуемые биты всегда должны быть равны нулю.
Ответ 2
Тип decimal
представлен в количестве, равном base-10, используя структуру (содержащую целые числа, я полагаю), в отличие от double
и других типов с плавающей запятой, которые представляют собой нецелые значения в base- 2. Таким образом, decimal
представляют собой точные представления значений base-10 в рамках стандартизованной точности для любой архитектуры. Это справедливо для любой архитектуры, использующей правильную реализацию спецификации .NET.
Итак, чтобы ответить на ваш вопрос, так как поведение decimal
стандартизировано таким образом в спецификации, значения decimal
должны быть одинаковыми для любой архитектуры, соответствующей этой спецификации. Если они не соответствуют этой спецификации, то они на самом деле не .NET.
"Десятичный" тип .NET против "Float" и "Double" C/С++ Type
Ответ 3
Несмотря на то, что формат типов с плавающей точкой четко определен, вычисления с плавающей запятой могут действительно иметь разные результаты в зависимости от архитектуры, как указано в разделе 4.1.6 of спецификация С#:
Операции с плавающей запятой могут быть выполняются с большей точностью, чем тип результата операции. Для Например, некоторые аппаратные архитектуры поддерживать "расширенный" или "длинный двойной", тип с плавающей точкой с большим диапазоном и точность, чем двойной тип, и неявно выполнять все операции с плавающей запятой, используя это более высокий тип точности. Только в чрезмерная стоимость исполнения может аппаратные архитектуры выполнять операции с плавающей запятой меньшей точности, а не требуют исполнения для отказа как производительность, так и точность, С# позволяет использовать более высокий тип точности используется для всех плавающих точек операции.
В то время как тип decimal
подлежит аппроксимации для того, чтобы значение представлялось в его конечном диапазоне, диапазон, по определению, определяется как подходящий для финансовых и денежных расчетов. Поэтому он имеет более высокую точность (и меньший диапазон), чем float
или double
. Он также более четко определен, чем другие типы с плавающей точкой, так что он выглядит не зависящим от платформы (см. Раздел 4.1.7 - я подозреваю эта независимость платформы больше, потому что не существует стандартной аппаратной поддержки типов с размером и точностью decimal
, а не из-за самого типа, поэтому это может измениться с будущими спецификациями и аппаратными архитектурами).
Если вам нужно знать, является ли конкретная реализация типа decimal
правильной, вы должны провести некоторые модульные тесты, используя спецификацию, которая будет проверять правильность.
Ответ 4
Чтение спецификации предполагает, что decimal
- like float
и double
- может быть допущено некоторое отклонение в его реализации, если оно соответствует определенным минимальным стандартам.
Вот несколько выдержек из ECMA С# spec (раздел 11.1.7). Все выделение жирным шрифтом принадлежит мне.
Тип decimal
может представлять значения , включая, в диапазон 1 x 10 -28 через 1 x 10 28 с не менее 28 значащих цифр.
Конечный набор значений типа decimal
имеет вид (-1) s x c x 10 -e где знак s 0 или 1, коэффициент c задается 0 <= c < Cmax, и масштаб e таков, что Emin <= e <= Emax, где Cmax не менее 1 x 10 28 Emin < = 0 и Emax > = 28. Тип decimal
не обязательноподдерживать подписанные нули, бесконечности или NaN.
Для decimal
с абсолютным значением, меньшим 1.0m
, значение является точным до не менее 28 th десятичной место. Для decimal
с абсолютным значением больше или равное 1.0m
, значение является точным до не менее 28 цифр.
Обратите внимание, что формулировка Microsoft С# spec (раздел 4.1.7) существенно отличается от формулировки спецификации ECMA. Похоже, что более строгое поведение decimal
больше блокируется.