Система.Математика.Round-округление до нуля цифр и деление против округления до одной или нескольких цифр
После неправильного толкования с моей стороны, во время чтения ответа на вопрос Как получить числа до определенного десятичного знака...
Саммери указанного вопроса:
Q: Feii Momo хочет знать: как округлить сумму денег до ближайшего значения в пределах 0,05 шага.
A: решение, обеспечиваемое загадочностью , состоит в том, чтобы умножить значение на 20 и округлить его и, по крайней мере, разделить его к 20
Math.Round(value * 20.0m, 0) / 20.0m
... Я придумал более общий вопрос:
Существуют ли какие-либо практические преимущества / недостатки между этими двумя подходами:(I) var rValue = Math.Round(value * 10.0m , 0) / 10.0m; (II) var rValue = Math.Round(value, 1);
Что я сделал до сих пор:
Сначала я посмотрел на Docs - систему.Математика.Раунд, но я не смог найти никакого намека. Я также взгляну на источник ссылки the - десятичный раунд , чтобы увидеть, есть ли какие-либо другие исполняющие ветви, но так далеко он только придумывает:
public static Decimal Round(Decimal d, int decimals)
{
FCallRound (ref d, decimals);
return d;
}
И FCallRound
как:
private static extern void FCallRound(ref Decimal d, int decimals);
К сожалению, я не нашел какой-то код для FCallRound
.
После этого я хочу взять его более практически и хочу увидеть, есть ли какая-либо разница в производительности, между округлением до 0 цифр или до 1..n цифр и "скакали кони" .
Сначала я выполняю эти три вызова функций:
(1) var rValue = Math.Round(value, 0);
(2) var rValue = Math.Round(value, 1);
(3) var rValue = Math.Round(value, 12);
Это показывает мне, что для 1'000' 000 итераций все три выполняются тихо равный (~70 мс). И, кажется, нет никакой разницы в исполнении.
Но просто чтобы проверить, нет ли неожиданных сюрпризов, я сравнил эти строки:
(1) var rValue = Math.Round(value, 1);
(2) var rValue = Math.Round(value * 10.0m, 0);
(3) var rValue = Math.Round(value * 10.0m, 0) / 10.0m;
Как и ожидалось, каждое умножение увеличивает время (~70 мс).
Поэтому, как и ожидалось в c#, нет никаких преимуществ производительности в округлении и делении вместо округления до требуемого числа дробных цифр.
Так повторяя мое вопрос:
Существуют ли какие-либо практические преимущества / недостатки между этими двумя подходами:(I) var rValue = Math.Round(value * 10.0m , 0) / 10.0m; (II) var rValue = Math.Round(value, 1);
1 ответ:
Краткий (er) ответ на обновленный вопрос
Вы можете фактически увидеть код текущей реализации
FCallRound
вCoreCLR . Если вы идете throung ecalllist.h#L784 , Вы можете видеть, что он отображается наCOMDecimal::DoRound
что, в свою очередь, делегирует большую часть логикиVarDecRound
. Вы можете увидеть полный код по ссылке, Но решающая часть:iScale = pdecIn->u.u.scale - cDecimals; do { ulSticky |= ulRem; if (iScale > POWER10_MAX) ulPwr = ulTenToNine; else ulPwr = rgulPower10[iScale]; ulRem = Div96By32(rgulNum, ulPwr); iScale -= 9; } while (iScale > 0);
Где константы определяются как
#define POWER10_MAX 9 static const ULONG ulTenToNine = 1000000000U; static ULONG rgulPower10[POWER10_MAX+1] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
Итак, этот код находит, как во многих десятичных позициях текущее значение должно быть сдвинуто, а затем выполняется деление на пакеты до
10^9
(а10^9
- это максимальная мощность 10, которая укладывается в 32 бита). Это означает, что существует два потенциальных источника разницы в производительности:Так что да, вы можете создать искусственный пример, когда код с умножением будет работать медленнее. Например, вы можете использовать разницу #2, если начнете с
- вы округляете до более чем 9 значащих цифр, так что цикл будет выполняться больше раз, если вы умножите первый
Div96By32
может работать медленнее, если после умножения мантисса имеет больше ненулевых 32-бит слова.decimal value = 92233720368.547758m
, для которого мантисса является ≈
2^63/100
. ТогдаMath.Round(value, 4)
будет быстрее, чемMath.Round(value*10000, 0)
, Даже если вы не учитываете время для расчетаvalue*10000
(смотрите онлайн ).Все же я думаю, что в любом реальном использовании жизни вы не заметите ничего существенного. разница.
Оригинальный длинный ответ
Я думаю, что вы пропустили весь смысл упомянутого вопроса. Главная проблема в том, что феи Момо хотели 40.23 округляться до 40.25. Это точность, которая не равна некоторому целому числу десятичных цифр! Используя округление до указанной дробной цифры, вы получите либо 40.23 (>=2 цифры), либо 40.2(0) (1 цифра). Трюк с умножением и делением-это простой трюк, чтобы получить округление по субразрядной точности (но это работает только в том случае, если ваш "субразряд" имеет некоторую отрицательную степень 2, например 0,5 или 0.25/0.5/0.75 и т.д.). Более того, я не знаю ни одного другого простого способа, который не использовал бы этот трюк с умножением-округлением-делением.
И да, когда вы все равно умножаете-округляете-делите, не важно, делаете ли вы это
var rValue = Math.Round(value * 20.0m, 0) / 20.0m;
Или
Потому что и деление, и деление занимают одно и то же время независимо от их вторых аргументов. Обратите внимание, что в вашей второй пример, вы не избегаете ни одного из существенных шагов первого примера! Так что это не совсемvar rValue = Math.Round(value * 2.0m, 1) / 2.0m;
Почему должно быть проще округлять и делить вместо округления до определенной дробной цифры?Лучше ли один, чем другой-это почти чисто субъективная вещь. (Я могу вспомнить некоторые крайние случаи, когда второй не потерпит неудачу, в то время как первый будет, но они не актуальны, пока мы говорим о какой-либо реальной сумме денег, как указано в оригинале вопрос.)Итак, подведем итог:
Можно ли избежать умножения и деления и использовать только математику?Круглый? Скорее всего, нет Можете ли вы умножить и разделить на число, которое не делится на 10, и будет ли это иметь какое-либо значение? Да, ты можешь. Нет, почти наверняка никакой разницы не будет.