Как бы вы внедрили функцию pilloried в Daily WTF? - PullRequest
3 голосов
/ 29 ноября 2008

Ежедневный WTF для столбцов 2008-11-28 следующий код:

static char *nice_num(long n)
{
    int neg = 0, d = 3;
    char *buffer = prtbuf;
    int bufsize = 20;

    if (n < 0)
    {
        neg = 1;
        n = -n;
    }
    buffer += bufsize;
    *--buffer = '\0';

    do
    {
        *--buffer = '0' + (n % 10);
        n /= 10;
        if (--d == 0)
        {
            d = 3;
            *--buffer = ',';
        }
    }
    while (n);

    if (*buffer == ',') ++buffer;
    if (neg) *--buffer = '-';
    return buffer;
}

Как бы вы это написали?

Ответы [ 7 ]

13 голосов
/ 29 ноября 2008

Если вы опытный программист на C, вы поймете, что этот код на самом деле не так уж и плох. Это относительно просто (для C), и это невероятно быстро. У него три проблемы:

  1. Ошибка в случае с краем LONG_MIN (-2 147 483 648), поскольку отрицание этого числа приводит к дополнению до двух
    • Предполагается, что 32-разрядные целые числа - для 64-разрядных длинных 20-байтовый буфер недостаточно велик
    • Это не поточно-ориентированный - он использует глобальный статический буфер, поэтому несколько потоков, вызывающих его одновременно, приведут к состоянию гонки

Задача № 1 легко решается с помощью специального случая. Чтобы обратиться к # 2, я бы разделил код на две функции: одну для 32-битных целых и одну для 64-битных целых. # 3 немного сложнее - нам нужно изменить интерфейс, чтобы сделать его полностью потокобезопасным.

Вот мое решение, основанное на этом коде, но измененное для решения этих проблем:

static int nice_num(char *buffer, size_t len, int32_t n)
{
  int neg = 0, d = 3;
  char buf[16];
  size_t bufsize = sizeof(buf);
  char *pbuf = buf + bufsize;

  if(n < 0)
  {
    if(n == INT32_MIN)
    {
      strncpy(buffer, "-2,147,483,648", len);
      return len <= 14;
    }

    neg = 1;
    n = -n;
  }

  *--pbuf = '\0';

  do
  {
    *--pbuf = '0' + (n % 10);
    n /= 10;
    if(--d == 0)
    {
      d = 3;
      *--pbuf = ',';
    }
  }
  while(n > 0);

  if(*pbuf == ',') ++pbuf;
  if(neg) *--pbuf = '-';

  strncpy(buffer, pbuf, len);
  return len <= strlen(pbuf);
}

Объяснение: он создает локальный буфер в стеке, а затем заполняет его тем же методом, что и исходный код. Затем он копирует его в параметр, переданный в функцию, следя за тем, чтобы не переполнять буфер. Он также имеет особый случай для INT32_MIN. Возвращаемое значение равно 0, если исходный буфер был достаточно большим, или 1, если буфер был слишком мал, и результирующая строка была усечена.

2 голосов
/ 29 ноября 2008

Хм ... Полагаю, я не должен этого признавать, но моя процедура int to string для встроенной системы работает почти так же (но без запятых).

Это не особенно просто, но я бы не назвал это WTF, если вы работаете в системе, в которой вы не можете использовать snprintf().

Парень, который написал выше, вероятно, заметил, что семейство подпрограмм printf() не может группировать запятые, поэтому он придумал свою собственную.

Сноска: в некоторых библиотеках форматирование в стиле printf() поддерживает группировку, но они не являются стандартными. И я знаю, что опубликованный код не поддерживает другие локали, которые группируются с использованием «.». Но это вряд ли WTF, возможно, просто ошибка.

1 голос
/ 30 августа 2009
size_t
signed_as_text_grouped_on_powers_of_1000(char *s, ssize_t max, int n)
{
    if (max <= 0)
        return 0;

    size_t r=0;
    bool more_groups = n/1000 != 0;
    if (more_groups)
    {
       r = signed_as_text_grouped_on_powers_of_1000(s, max, n/1000);
       r += snprintf(s+r, max-r, ",");
       n = abs(n%1000);
       r += snprintf(s+r, max-r, "%03d",n);
    } else
       r += snprintf(s+r, max-r, "% 3d", n);

    return r;
}

К сожалению, это примерно в 10 раз медленнее, чем оригинал.

1 голос
/ 29 ноября 2008

Лисп:

(defun pretty-number (x) (format t "~:D" x))

Я удивлен, как легко я мог это сделать. Я даже не прошел первую главу в моей книге о Лиспе. xD (Или я должен сказать, ~: D)

1 голос
/ 29 ноября 2008

Это, вероятно, довольно близко к тому, как я бы написал это на самом деле. Единственное, что я могу сразу увидеть, что это неправильно с решением, это то, что он не работает для LONG_MIN на машинах, где LONG_MIN - (LONG_MAX + 1), что в настоящее время является большинством машин. Я мог бы использовать localeconv, чтобы получить разделитель тысяч вместо запятой, и я мог бы более тщательно рассчитать размер буфера, но алгоритм и реализация кажутся мне довольно простыми, на самом деле не так уж много WTF для C (есть гораздо лучшие решения для C ++).

0 голосов
/ 29 ноября 2008

Мне стало скучно и я сделал эту наивную реализацию в Perl. Работает.


sub pretify {
    my $num = $_[0];
    my $numstring = sprintf( "%f", $num );

    # Split into whole/decimal
    my ( $whole, $decimal ) = ( $numstring =~ /(^\d*)(.\d+)?/ );
    my @chunks;
    my $output = '';

    # Pad whole into multiples of 3
    $whole = q{ } x ( 3 - ( length $whole ) % 3 ) . $whole;

    # Create an array of all 3 parts.
    @chunks = $whole =~ /(.{3})/g;

    # Reassemble with commas
    $output = join ',', @chunks;
    if ($decimal) {
        $output .= $decimal;
    }

    # Strip Padding ( and spurious commas )
    $output =~ s/^[ ,]+//;

    # Strip excess tailing zeros
    $output =~ s/0+$//;

    # Ending with . is ugly
    $output =~ s/\.$//;
    return $output;
}

print "\n", pretify 100000000000000000000000000.0000;
print "\n", pretify 10_202_030.45;
print "\n", pretify 10_101;
print "\n", pretify 0;
print "\n", pretify 0.1;
print "\n", pretify 0.0001;
print "\n";
0 голосов
/ 29 ноября 2008

В чистом C:

#include <stdio.h>
#include <limits.h>

static char *prettyNumber(long num, int base, char separator)
{
#define bufferSize      (sizeof(long) * CHAR_BIT)
        static char buffer[bufferSize + 1];
        unsigned int pos = 0;

        /* We're walking backwards because numbers are right to left. */
        char *p = buffer + bufferSize;
        *p = '\0';

        int negative = num < 0;

        do
        {
                char digit = num % base;
                digit += '0';

                *(--p) = digit;
                ++pos;

                num /= base;

                /* This the last of a digit group? */
                if(pos % 3 == 0)
                {
/* TODO Make this a user setting. */
#ifndef IM_AMERICAN
#       define IM_AMERICAN_BOOL 0
#else
#       define IM_AMERICAN_BOOL 1
#endif
                        /* Handle special thousands case. */
                        if(!IM_AMERICAN_BOOL && pos == 3 && num < base)
                        {
                                /* DO NOTHING */
                        }
                        else
                        {
                                *(--p) = separator;
                        }
                }
        } while(num);

        if(negative)
                *(--p) = '-';

        return p;
#undef bufferSize
}

int main(int argc, char **argv)
{
        while(argc > 1)
        {
                long num = 0;

                if(sscanf(argv[1], "%ld", &num) != 1)
                        continue;

                printf("%ld = %s\n", num, prettyNumber(num, 10, ' '));

                --argc;
                ++argv;
        };

        return 0;
}

Обычно я бы возвращал выделенный буфер, который должен был бы быть свободным пользователем. Это дополнение тривиально.

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