Можете ли вы обнаружить математическую ошибку в моем методе помощника тегов? - PullRequest
0 голосов
/ 03 октября 2019

Я сделал помощник по тегам, который отображает рейтинг от 0 до 5 звезд, включая пол-звезды.

Этот тег отображает пять звезд:

<star-rating rating="10"></star-rating>

значение для rating может быть любым целым числом от 0 до 10, которое делится пополам в вспомогательном методе, чтобы учесть половину звезд:

public class StarRatingTagHelper : TagHelper
{
    public double? Rating { get; set; }

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagMode = TagMode.StartTagAndEndTag;
        output.SuppressOutput();

        var sb = new StringBuilder();

        if (Rating != null)
        {
            double stars = (double)Rating/2;
            sb.AppendFormat($@"<span title='Rating {stars}/5'>");
            for (int s = 0; s < stars; s ++)
            {
                sb.AppendFormat("<span class='fas fa-star'></span>");
            }
            double dec = stars - Math.Truncate(stars);
            if (dec == 0.5)
            {
                sb.AppendFormat("<span class='fas fa-star-half'></span>");
            }
            sb.AppendFormat("</span>");
        }
        else
        {
            sb.AppendFormat("No rating");
        }
        output.PreContent.SetHtmlContent(sb.ToString());
    }
}

Но в моем методе есть математическая ошибка, которую я могуне найти. Метод производит следующие выходные данные (() = звезда, ( = половина звезды):

0.5: ()(         (one too many)
1.0: ()          (correct)
1.5: ()()(       (one too many)
2.0: ()()        (correct)
2.5: ()()()(     (one too many)
3.0: ()()()      (correct)
3.5: ()()()()(   (one too many)
4.0: ()()()()    (correct)
4.5: ()()()()()( (one too many)
5.0: ()()()()()  (correct)

Здесь есть очевидная закономерность. В чем причина?

Обновление

Я получил его для работы с уродливым обходным путем:

double dec = stars - Math.Truncate(stars);
if (dec == 0.5)
{
    for (int s = 1; s < stars; s++)
    {
        sb.AppendFormat("<span class='fas fa-star text-warning text-outline'></span>");
    }
    sb.AppendFormat("<span class='fas fa-star-half'></span>");
}
else
{
    for (int s = 0; s < stars; s++)
    {
        sb.AppendFormat("<span class='fas fa-star'></span>");
    }
}

Если будетполовина звезды, он запускает один цикл for, если нет, он запускает другой, нарушая принцип DRY ...

Ответы [ 2 ]

4 голосов
/ 03 октября 2019

В вашем цикле for вы поднимаетесь до s < stars, это будет означать, что 0.5 создаст полную звезду, потому что она больше 0. Вы можете Floor stars опустить десятичные дроби. например, Math.Floor(1.5) = 1, который является возможным исправлением, см. ниже.

for (int s = 0; s < Math.Floor(stars); s++)
{
    sb.AppendFormat("<span class='fas fa-star'></span>");
}

РЕДАКТИРОВАТЬ:

Math.Truncate() также будет работать.

2 голосов
/ 03 октября 2019

Во-первых, я бы рекомендовал использовать здесь тип decimal вместо double, так как вы можете получить ошибки округления при выполнении математических операций (а затем при сравнении результатов) с doubles. В вашем случае вам повезло, потому что вы используете .5, который, похоже, не представляет проблему, но, например, рассмотрите следующую строку вашего кода:

double dec = stars - Math.Truncate(stars);

Если stars было 2.4, тогда dec было бы 0.39999999999999991
Если бы stars было 2.6, тогда dec было бы 0.60000000000000009

Ни один из случаев не дал бы ожидаемый результат, если быЗатем вы сделали сравнение: if (dec == .4) или if (dec == .6). Так что, в общем, хорошей привычкой является использование decimal, если вы собираетесь выполнять математику с числами и сравнивать результаты.


Для решения проблемы в вашем вопросе естьпара приемов, которые вы можете использовать:

  1. Примените decimal к int, чтобы получить целую часть числа
    • (int) 2.5M == 2
  2. Используйте оператор модуля (который возвращает остаток от деления одного числа на другое) с помощью 1, чтобы получить десятичную часть
    • 2.5M % 1 == 0.5

Например:

// Loop from .5 to 10 in increments of .5 (the 'M' signifies that the number is a decimal)
for (decimal i = .5M; i <= 5; i += .5M)
{
    Console.Write($"{i:0.0}: ");

    // First write out our "whole" stars
    for (int j = 0; j < (int) i; j++)
    {
        Console.Write("()");
    }

    // Then, if needed, write out the "half" star
    if (i % 1 >= .5M) Console.Write("(");

    Console.WriteLine();
}

GetKeyFromUser("\n\nDone! Press any key to exit...");

Выход

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...