Система.Математика.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 2

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^910^9 - это максимальная мощность 10, которая укладывается в 32 бита). Это означает, что существует два потенциальных источника разницы в производительности:

  1. вы округляете до более чем 9 значащих цифр, так что цикл будет выполняться больше раз, если вы умножите первый
  2. Div96By32 может работать медленнее, если после умножения мантисса имеет больше ненулевых 32-бит слова.
Так что да, вы можете создать искусственный пример, когда код с умножением будет работать медленнее. Например, вы можете использовать разницу #2, если начнете с
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, и будет ли это иметь какое-либо значение? Да, ты можешь. Нет, почти наверняка никакой разницы не будет.