【C】基数変換プログロムを自作してみた

# 基数変換プログラムとは
n進数で表された文字列を、別の指定されたn進数に変換して文字列として出力するプログラム。

例えば、"0123456789abcdef"の16進数で表された「a0」を2進数である"01"で表すと、「1111011」となる。

# 必要な機能
基数変換プログラムに必要な機能は、大きく分けると以下の2つである。
・n進数の文字を10進数に変換する（str to int）
・10進数の数字をn進数に変換する（int to str）


# プログラムの全体像
```c
#include <unistd.h>
#include <stdlib.h>
long		ft_base_check(char *base);
int		ft_len(char *str);
int		ft_len_num(long nbr);
int		ft_len_digits(long num, char *base);
char	*ft_from_deci(int nbr, char *base);
int		ft_strcmp(char *s1, char *s2);
int		ft_strlen_base(char *str, char *base);
long	ft_str_len(char *str, char *base);
void	ft_to_deci(char *str, char *base, long *ans, long tmp);
int		ft_atoi_base(char *str, char *base);
int		ft_to_decimal(char *nbr, char *base_from);


long	ft_base_check(char *base)
{
	long	i;
	long	j;
	long	count;

	count = 0;
	while (base[count])
	{
		if (base[count] == '+' || base[count] == '-'
			|| base[count] == ' ' || (base[count] >= 9 && base[count] <= 13))
			return (-1);
		count++;
	}
	i = 0;
	while (i < count - 1)
	{
		j = i + 1;
		while (j < count)
		{
			if (base[i] == base[j])
				return (-1);
			j++;
		}
		i++;
	}
	return (count);
}

int	ft_len(char *str)
{
	int	count;

	count = 0;
	while (str[count])
	{
		if (str[count] == '+' || str[count] == '-' )
			return (-1);
		count++;
	}
	return (count);
}

int	ft_len_num(long nbr)
{
    int count = 0;
    if (nbr == 0)
        return (1);
    while (nbr != 0)
    {
        nbr /= 10;
        count++;
    }
    return (count);
}

int ft_len_digits(long num, char *base)
{
	int	len;

	len = 0;
    while (num >= ft_len(base))
    {
        num /= ft_len(base);
        len++;
    }
	return (len);
}

char	*ft_from_deci(int nbr, char *base)
{
    long	num;
    int		len;
	int		index;
	char	*result;

	num = nbr;
    if (num < 0)
        num = -num;
	len = 1 + ft_len_digits(num, base);
    if (nbr < 0)
        len++;
    result = (char *)malloc(sizeof(char) * (len + 1));
    if (result == NULL)
        return NULL;
    index = len - 1;
    while (num >= ft_len(base))
    {
        result[index--] = base[num % ft_len(base)];
        num /= ft_len(base);
    }
    result[index] = base[num];
    if (nbr < 0)
        result[0] = '-';
    result[len] = '\0';
    return result;
}
int	ft_strcmp(char *s1, char *s2)
{
	while (*s1 != '\0' || *s2 != '\0')
	{
		if (*s1 != *s2)
		{
			return (*s1 - *s2);
		}
		s1++;
		s2++;
	}
	return (0);
}


int ft_strlen_base(char *str, char *base)
{
    int i = 0;
    int j = 0;
    int count = 0;

    while (str[i] != '\0' && count == i)
    {
        j = 0;
        while (base[j] != '\0')
        {
            if (base[j] == str[i])
            {
                count++;
                break;
            }
            j++;
        }
        i++;
    }
    return (count);
}
long	ft_str_len(char *str, char *base)
{
	long	count;
	long	j;

	count = 0;
	while (str[count])
	{
		j = 0;
		while (base[j])
		{
			if (str[count] == base[j])
			{
				count++;
				break ;
			}
			j++;
		}
		if (!base[j])
			break ;
	}
	return (count);
}

void	ft_to_deci(char *str, char *base, long *ans, long tmp)
{
	long	i;
	long	j;
	long	str_len;

	i = 0;
	while (str[i])
	{
		j = 0;
		str_len = ft_str_len(str, base);
		while (base[j])
		{
			if (str[i] == base[j])
			{
				tmp = j;
				while (--str_len > i)
					tmp *= ft_base_check(base);
				*ans += tmp;
				i++;
				break ;
			}
			j++;
		}
		if (!base[j])
			return ;
	}
}

int	ft_atoi_base(char *str, char *base)
{
	long		i;
	long		sign;
	long		ans;
	char		*tmp;

	i = 0;
	ans = 0;
	sign = 1;
	if (ft_base_check(base) == -1)
		return (0);
	while ((str[i] >= 9 && str[i] <= 13) || str[i] == ' ')
		i++;
	while (str[i] == '+' || str[i] == '-')
	{
		if (str[i] == '-')
			sign *= -1;
		i++;
	}
	tmp = str + i;
	ft_to_deci(tmp, base, &ans, 0);
	return ((int)ans * sign);
}


int    ft_to_decimal(char *nbr, char *base_from)
{
	int		i;
	int		j;
    int		sign;
	char	*str;

	sign = 1;
	i = 0;
	while ((nbr[i] >= 9 && nbr[i] <= 13) || nbr[i] == ' ')
		i++;
	while (nbr[i] == '+' || nbr[i] == '-')
	{
		if (nbr[i] == '-')
			sign *= -1;
		i++;
	}
	nbr += i;
	str = (char *)malloc(sizeof(char) * (ft_strlen_base(nbr, base_from) + 1));
	j = -1;
	while (++j < ft_strlen_base(nbr, base_from))
		str[j] =  nbr[j];
	return (sign * ft_atoi_base(str, base_from));
}

char	*ft_convert_base(char *nbr, char *base_from, char *base_to)
{
	char	*result;
	int		num;

	if (!nbr || ft_base_check(base_from) < 2 || ft_base_check(base_to) < 2)
		return (0);
	num = ft_to_decimal(nbr, base_from);
	result = ft_from_deci(num, base_to);
	return (result);
}
```

# それぞれの関数の説明
## strcmp
与えられた文字列を比較する。全く同じ時だけ0を返し、そのほかの場合は辞書順での差分を返す。
```c
int	ft_strcmp(char *s1, char *s2)
{
	while (*s1 != '\0' || *s2 != '\0')
	{
		if (*s1 != *s2)
		{
			return (*s1 - *s2);
		}
		s1++;
		s2++;
	}
	return (0);
}
```
## strlen_base
ベースシステムとして与えられた文字列に当てはまる文字列が何文字分あるかをカウント。（前から何文字分が数を表している文字列かを調べている）
```c
int ft_strlen_base(char *str, char *base)
{
    int i = 0;
    int j = 0;
    int count = 0;

    while (str[i] != '\0' && count == i)
    {
        j = 0;
        while (base[j] != '\0')
        {
            if (base[j] == str[i])
            {
                count++;
                break;
            }
            j++;
        }
        i++;
    }
    return (count);
}
```
## str_len
上のstrlen_base関数と全く同じ。作成した後に気づいた。笑
```c
long	ft_str_len(char *str, char *base)
{
	long	count;
	long	j;

	count = 0;
	while (str[count])
	{
		j = 0;
		while (base[j])
		{
			if (str[count] == base[j])
			{
				count++;
				break ;
			}
			j++;
		}
		if (!base[j])
			break ;
	}
	return (count);
}
```

## to_dici
この関数は指定のベースシステムで表された文字列を、10進数に変換する関数。
ft_base_check関数でベースシステムが正しく機能するか調べている。
```c
void	ft_to_deci(char *str, char *base, long *ans, long tmp)
{
	long	i;
	long	j;
	long	str_len;

	i = 0;
	while (str[i])
	{
		j = 0;
		str_len = ft_str_len(str, base);
		while (base[j])
		{
			if (str[i] == base[j])
			{
				tmp = j;
				while (--str_len > i)
					tmp *= ft_base_check(base);
				*ans += tmp;
				i++;
				break ;
			}
			j++;
		}
		if (!base[j])
			return ;
	}
}
```

## atoi_base
この関数は指定のベースシステムで表された文字列を、10進数に変換する関数。
文字列に含まれる最初の数字を探して、to_deci関数に10進数への変換はお任せする。
```c
int	ft_atoi_base(char *str, char *base)
{
	long		i;
	long		sign;
	long		ans;
	char		*tmp;

	i = 0;
	ans = 0;
	sign = 1;
	if (ft_base_check(base) == -1)
		return (0);
	while ((str[i] >= 9 && str[i] <= 13) || str[i] == ' ')
		i++;
	while (str[i] == '+' || str[i] == '-')
	{
		if (str[i] == '-')
			sign *= -1;
		i++;
	}
	tmp = str + i;
	ft_to_deci(tmp, base, &ans, 0);
	return ((int)ans * sign);
}
```

## to_decimal
```c
int    ft_to_decimal(char *nbr, char *base_from)
{
	int		i;
	int		j;
    int		sign;
	char	*str;

	sign = 1;
	i = 0;
	while ((nbr[i] >= 9 && nbr[i] <= 13) || nbr[i] == ' ')
		i++;
	while (nbr[i] == '+' || nbr[i] == '-')
	{
		if (nbr[i] == '-')
			sign *= -1;
		i++;
	}
	nbr += i;
	str = (char *)malloc(sizeof(char) * (ft_strlen_base(nbr, base_from) + 1));
	j = -1;
	while (++j < ft_strlen_base(nbr, base_from))
		str[j] =  nbr[j];
	return (sign * ft_atoi_base(str, base_from));
}
```

## convert_base
全ての基幹。

```c
char	*ft_convert_base(char *nbr, char *base_from, char *base_to)
{
	char	*result;
	int		num;

	if (!nbr || ft_base_check(base_from) < 2 || ft_base_check(base_to) < 2)
		return (0);
	num = ft_to_decimal(nbr, base_from);
	result = ft_from_deci(num, base_to);
	return (result);
}
```

## ft_base_check

ベースシステムが正しく機能するか判断する。
具体的に以下の場合を省く。

- 1文字以下の時
- "+"や"-"が含まれている時
- 重複文字がある場合
```c
long	ft_base_check(char *base)
{
	long	i;
	long	j;
	long	count;

	count = 0;
	while (base[count])
	{
		if (base[count] == '+' || base[count] == '-'
			|| base[count] == ' ' || (base[count] >= 9 && base[count] <= 13))
			return (-1);
		count++;
	}
	i = 0;
	while (i < count - 1)
	{
		j = i + 1;
		while (j < count)
		{
			if (base[i] == base[j])
				return (-1);
			j++;
		}
		i++;
	}
	return (count);
}
```

## len
シンプルに文字列の長さを返す。
```c
int	ft_len(char *str)
{
	int	count;

	count = 0;
	while (str[count])
	{
		if (str[count] == '+' || str[count] == '-' )
			return (-1);
		count++;
	}
	return (count);
}
```

## len_num
数字が何桁か返す。
```c
int	ft_len_num(long nbr)
{
    int count = 0;
    if (nbr == 0)
        return (1);
    while (nbr != 0)
    {
        nbr /= 10;
        count++;
    }
    return (count);
}
```

## len_digits
10進数の数字を指定のベースシステムで表記したら何桁になるかを調べる関数。
```c
int ft_len_digits(long num, char *base)
{
	int	len;

	len = 0;
    while (num >= ft_len(base))
    {
        num /= ft_len(base);
        len++;
    }
	return (len);
}
```
## from_deci
10進数から出力先のベースシステムへ変換する関数。
```c
char	*ft_from_deci(int nbr, char *base)
{
    long	num;
    int		len;
	int		index;
	char	*result;

	num = nbr;
    if (num < 0)
        num = -num;
	len = 1 + ft_len_digits(num, base);
    if (nbr < 0)
        len++;
    result = (char *)malloc(sizeof(char) * (len + 1));
    if (result == NULL)
        return NULL;
    index = len - 1;
    while (num >= ft_len(base))
    {
        result[index--] = base[num % ft_len(base)];
        num /= ft_len(base);
    }
    result[index] = base[num];
    if (nbr < 0)
        result[0] = '-';
    result[len] = '\0';
    return result;
}
```

# まとめ
今回は基数変換プログラムをmalloc関数とwrite関数のみで実装してみた。競技プログラミングなどでライブラリとして作成しておきたくなったら、もう少し簡潔にまとめるつもりです。