【问题标题】:How would you implement the pilloried function in the Daily WTF?您将如何在 Daily WTF 中实现 poloied 功能?
【发布时间】:2010-09-24 13:00:09
【问题描述】:

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

你会怎么写?

【问题讨论】:

  • 我注意到 The Daily WTF 网站上的 cmets 似乎大多指出它不是真正的 WTF(至少我浏览的前几屏 cmets)。
  • @Mike B:嗯,我同意这个评估。我没有看过那里的 cmets。
  • 我们已经有了更广泛的实现:stackoverflow.com/q/4351371/103167

标签: c coding-style


【解决方案1】:

如果您是一位经验丰富的 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。

【讨论】:

  • 当我阅读您的第一段时,我决定实际尝试阅读代码。我很惊讶我很容易理解它是如何工作的。我希望这是一件好事。 =]
  • 我对这段代码的唯一抱怨是,如果要复制的字符串比接收它的缓冲区长,strncpy() 不保证空终止。返回值确实表明了这一点,但确保输出为空终止通常是个好主意。
  • 缺少:static_assert( INT_MIN == -2,147,483,648 );。在代码中混合魔术字符串和符号常量只是一个等待发生的意外。
【解决方案2】:

嗯...我想我不应该承认这一点,但我的嵌入式系统的 int 到字符串例程的工作方式几乎完全相同(但没有放入逗号)。

这不是特别简单,但如果您正在开发一个无法使用 snprintf() 的系统,我不会将其称为 WTF。

写上面的那个人大概注意到printf()系列的例程不能做逗号分组,所以他想出了自己的。

脚注:有些库的printf() 样式格式确实支持分组,但它们不是标准的。而且我知道发布的代码不支持使用“。”分组的其他语言环境。但这几乎不是 WTF,可能只是一个错误。

【讨论】:

    【解决方案3】:

    这可能与我实际编写它的方式非常接近。我可以立即看到该解决方案的唯一问题是它不适用于 LONG_MIN 在 LONG_MIN 为 -(LONG_MAX + 1) 的机器上,这是当今大多数机器。我可能会使用localeconv 来获取千位分隔符而不是假设逗号,并且我可能会更仔细地计算缓冲区大小,但算法和实现对我来说似乎很简单,对于 C 来说并不是真正的 WTF(有更好的 C++ 解决方案)。

    【讨论】:

      【解决方案4】:

      Lisp:

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

      我很惊讶我能如此轻松地做到这一点。我什至没有超过我的 Lisp 书的第一章。 xD(或者我应该说,~:D)

      【讨论】:

        【解决方案5】:
        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 倍。

        【讨论】:

          【解决方案6】:

          在纯 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;
          }
          

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

          【讨论】:

          • 为什么不使用“IM_AMERICAN”,而不是使用 localeconv()->thousands_sep?这将是适合您的语言环境的值。
          【解决方案7】:

          我感到无聊,于是在 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";
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-11-09
            • 1970-01-01
            • 2017-03-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多