C基数从十进制到三进制的转换

时间:2018-05-04 11:32:09

标签: c string algorithm math

我将整数值转换为三元数(这是一个字符串)。我怀疑应该有更好的方法来减少计算量。

我的算法直接实现了长除法,用于转换十进制和任何基数之间的数字。

char *get_ternary_str(int value) {
  char buffer[3] = { "000" };
  int dividend = value;

  int i = 0;
  do {
    int remainder = dividend % 3;
    dividend = dividend / 3;
    buffer[i] = ((char)remainder + '0');
    i++;
  } while(dividend >= 1 && i < 3);

  char temp = buffer[2];
  buffer[2] = buffer[0];
  buffer[0] = temp;

  return buffer;
}

即使在这个例子中三元数是3位数,我的目标是使它长n位。关于如何对n位三进制数字进行此操作的建议也是受欢迎的。

4 个答案:

答案 0 :(得分:3)

  • 您应该使用malloc动态分配缓冲区。
  • 您可以使用memset使用零来初始化char数组。
  • 最后,您可以在数学上计算char数组长度。 Log to log [value] base 3将为您提供字符串的字符大小。请记住添加额外的char以使用null。

由于您要求提供建议,我不提供代码。但是,如果您需要进一步的帮助,可以在评论中注明。

答案 1 :(得分:3)

代码有问题。

返回指向本地变量的指针

返回指向本地(非静态)变量的指针是未定义行为(UB)。在功能完成后,不要依赖该存储器有效。

char *get_ternary_str(int value) {
  char buffer[3] = ...
  ...
  return buffer;  // UB
}

因负数而失败

-1 % 3 - &gt; -1,因此remainder + '0'不会生成数字字符。

int remainder = dividend % 3;
...
buffer[i] = ((char)remainder + '0');

空间不足

buffer[3]对于"000"字符串而言太小,因为 null字符需要空格。 buffer[3]足以容纳3位数字符的数组,但当然需要字符串

// char buffer[3] = { "000" };
char buffer[3+1] = { "000" };

从未分配空字符

当然结果应该是字符串,它需要空字符 '\0'。代码需要修改以确保。

  

我的目标是让它长到n位数   关于如何对n位三进制数字进行此操作的建议也是受欢迎的。

需要char缓冲区来处理形成的字符串。一个简单的解决方案是传入一个足够大的缓冲区指针,以应对更糟糕的情况。我建议也传入缓冲区的大小。

以下是处理任何基础2-36的解决方案。

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

// Maximum buffer size (worst case: INT_MIN and base 2)
#define ITOA_BASE_N (1 + sizeof(int)*CHAR_BIT + 1)

char *itoa_base(char *dest, size_t sz, int i, unsigned base) {
  char buf[ITOA_BASE_N];
  char *s = &buf[sizeof buf - 1];  // set to last character.
  *s = '\0';
  unsigned u = (unsigned) i;  // Use unsigned to cope with INT_MIN ...
  if (i < 0) {
    u = -u;                   // ... as -INT_MIN is UB
  }
  do {
    *(--s) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[u % base];
    u /= base;
  } while (u);
  if (i < 0) {
    *(--s) = '-';
  }
  size_t size_used = &buf[sizeof buf] - s;
  if (size_used > sz) {
    return NULL;  // or some other error handling.
  }
  return memcpy(dest, s, size_used);
}

测试使用复合文字进行临时存储,但如果需要,可以传入固定缓冲区。

#define TO_BASE3(x) itoa_base((char [ITOA_BASE_N]){0}, ITOA_BASE_N, (x), 3)

void test3(int x) {
  char *s = TO_BASE3(x);
  // do stuff with `s`, it is valid for until the end of this block
  printf("base10:%11d base3:'%s'\n", x, s);
}

int main(void) {
  test3(0);
  test3(1);
  test3(42);
  test3(-81);
  test3(INT_MAX);
  test3(INT_MIN);
}

输出

base10:          0 base3:'0'
base10:          1 base3:'1'
base10:         42 base3:'1120'
base10:        -81 base3:'-10000'
base10: 2147483647 base3:'12112122212110202101'
base10:-2147483648 base3:'-12112122212110202102'

答案 2 :(得分:1)

我在special_user函数中分配动态内存并传递指向该函数的指针。这样我就可以管理内存。对于每个分配,您需要重新分配。我为字符串终止分配了超过字符串长度的1个元素。你的算法几乎不变。我更改了缓冲区的索引以避免重新排序缓冲区元素。

main

根据Y.Doktur的想法,这里的动态长度是相同的代码。我使用指向指针的指针来传递和接收指针。

#include<stdio.h>
#include<stdlib.h>

void get_ternary_str(char* buffer, int value, unsigned int n) {
  int dividend = value;

  int i = 0;
  do {
    int remainder = dividend % 3;
    dividend = dividend / 3;
    buffer[n - 1 - i] = ((char)remainder + '0');
    i++;
  } while(i < n);

  buffer[i] = 0;
}

int main() {
    int n = 5;
    char* buffer = (char*) malloc(n * sizeof(char));

    int value = 31;
    get_ternary_str(buffer, value, n - 1);

    printf("%s", buffer);
    free(buffer);
    return 0;
}

答案 3 :(得分:1)

如果您需要考虑,可以使用一个简单的小型查找表来交换一些内存空间用于计算时间。

考虑一下这个C ++(你的问题的早期版本也要求用这种语言提出想法)片段作为我提议的算法的一个例子。

#include <iostream>
#include <array>
#include <vector>
#include <string>
#include <iomanip>
#include <algorithm>
#include <limits>

std::string ternary_from(int value)
{ 
    // The idea is to elaborate three figures (in base 3) at a time
    constexpr int dim = 3 * 3 * 3;

    // Note the values, are "reversed"
    static const std::array<std::string, dim> inner_parts {
        "000", "100", "200", "010", "110", "210", "020", "120", "220", 
        "001", "101", "201", "011", "111", "211", "021", "121", "221", 
        "002", "102", "202", "012", "112", "212", "022", "122", "222" 
    };

    static const std::array<std::string, dim> parts {
        "", "1", "2", "01", "11", "21", "02", "12", "22", 
        "001", "101", "201", "011", "111", "211", "021", "121", "221", 
        "002", "102", "202", "012", "112", "212", "022", "122", "222" 
    };

    if ( value == 0 )
        return std::string{"0"};

    std::string result;

    // Thanks @Chux for recalling that -INT_MIN is UB
    unsigned tmp = value;
    if ( value < 0 )
        tmp = -tmp;

    // note that 'dim' = 27, so you are performing a third of the calculations
    while (tmp >= dim)
    {
        unsigned remainder = tmp % dim;
        // concatenating a string at the end is easier...
        result += inner_parts[remainder];
        tmp = tmp / dim;
    }
    result += parts[tmp];

    if (value < 0)
        result += '-';

    // now the string needs to be reversed. e.g. 3 -> 10(3), not 01 
    std::reverse(result.begin(), result.end());
    return result;
}

int main()
{
    std::vector<int> tests {
        42, -8, 0, 81, 27, -28, std::numeric_limits<int>::max(), std::numeric_limits<int>::min()
    };

    std::cout << "   decimal           ternary\n";
    for (int i : tests)
    {
        std::cout << std::setw(12) << i << std::setw(23) << ternary_from(i) << '\n';
    }
}

输出

   decimal           ternary
          42                   1120
          -8                    -22
           0                      0
          81                  10000
          27                   1000
         -28                  -1001
  2147483647   12112122212110202101
 -2147483648  -12112122212110202102