代码之家  ›  专栏  ›  技术社区  ›  Jonathan Leffler

您将如何在每日WTF中实现柱状功能?

  •  3
  • Jonathan Leffler  · 技术社区  · 16 年前

    The Daily 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 回复  |  直到 11 年前
        1
  •  13
  •   Adam Rosenfield    16 年前

    如果你是一个经验丰富的C程序员,你会意识到这段代码实际上并没有那么糟糕。它相对简单(对于C),而且速度非常快。它有三个问题:

    1. 它在LONG_MIN(-2147483648)的边缘情况下失败,因为对这个数字求反会产生两个补码
      • 它假设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
  •  2
  •   Michael Burr    16 年前

    这不是特别简单,但如果你在一个你不能使用的系统上工作,我不会称之为WTF snprintf() 在…上

    printf()

    注:有些图书馆 printf()

        3
  •  1
  •   Robert Gamble    16 年前

    这可能和我写它的方式非常接近。我能马上看出这个解决方案的唯一错误是,在LONG_MIN为-(LONG_MAX+1)的机器上,LONG_MIN不起作用,这是当今大多数机器的情况。我可以用 localeconv 为了得到千位分隔符而不是使用逗号,我可能会更仔细地计算缓冲区大小,但算法和实现对我来说似乎非常简单,对于C来说并不是什么WTF(对于C++有更好的解决方案)。

        4
  •  1
  •   strager    16 年前

    口齿不清:

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

    我很惊讶我能这么容易做到。我的口齿不清书的第一章还没读完。xD(或者我应该说,~:D)

        5
  •  1
  •   Adrian Panasiuk    15 年前
    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;
    }
    

        6
  •  0
  •   strager    16 年前

    #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;
    }
    

    通常我会返回一个alloc'd缓冲区,这需要用户释放。这个添加是微不足道的。

        7
  •  0
  •   Kent Fredric    16 年前

    我觉得很无聊,于是用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";