atoiやatofの逆で、数値を文字列に変換する関数を考えます。
目次
printf系関数は重く、遅い
C言語には、数値をだいたい狙った通りのフォーマットで文字列に変換してくれる、sprintf関数というものがあります。しかしこのsprintf関数、多機能であるがゆえにとても多くのプログラムメモリーを消費し、かつ変換速度も遅いです。このため組み込みソフトでは使いづらい面もあります。
そこで数値を文字列に変換する関数をまとめた、「stdlib.h」ならぬ「mylib.h」を考えてみました。
目標
- 符号あり整数を文字列に変換します。
- 符号なし整数を文字列に変換します。
- 浮動小数点数を簡単な小数点付き文字列に変換します。
- 小数点の記号にピリオドだけでなく、コンマも選択できます。
- 文字列の最後には’\0’が付きます。
- 変換条件を満たしていない場合は、エラーを返すと同時に先頭を’\0’にします。
サンプルコード
それぞれの関数の変換例は関数ヘッダーにコメントとして記載しているので、それを参考にどうぞ。その他では下記のような機能があります。
[mylib.h]
#ifndef MYLIB_H
#define MYLIB_H
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdbool.h>
#include <stdint.h>
//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------
// 戻り値
#define OUT_OF_RANGE (1) //!< 引数が範囲外
#define BUFFER_OVERFLOW (2) //!< バッファー不足
//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
void Set_DecimapSeparator(uint8_t separator);
uint8_t UIntToAscii(uint32_t value, char ascii[], uint8_t len, char padding);
uint8_t IntToAscii(int32_t value, char ascii[], uint8_t len, char padding);
uint8_t IntFloatToAscii(int32_t value, char ascii[], uint8_t len, uint8_t dec_place, char padding);
uint8_t DoubleToAscii(double value, char ascii[], uint8_t len, uint8_t dec_place, char padding);
#ifdef __cplusplus
}
#endif
#endif
[mylib.c]
/**
* @file mylib.c
* @brief マイライブラリー処理
*/
//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include "mylib.h"
//------------------------------------------------------------------------------
// macro
//------------------------------------------------------------------------------
#define POW_MAX (7)
//------------------------------------------------------------------------------
// private variable
//------------------------------------------------------------------------------
// 小数点の記号
static uint8_t m_decimap_separator = '.';
// べき乗
static const uint32_t m_pow10[POW_MAX] = {1, 10, 100, 1000, 10000, 100000, 1000000};
//------------------------------------------------------------------------------
// private function
//------------------------------------------------------------------------------
static uint8_t uitoa(uint32_t value, uint8_t *adr, char ascii[], uint8_t len);
static uint8_t Set_Padding(uint8_t adr, bool f_minus, char ascii[], uint8_t padding);
/** ----------------------------------------------------------------------------
* @brief 小数点の記号を設定
*
* @param separator : 小数点の記号 [0=. / 1=,]
*/
void Set_DecimapSeparator(uint8_t separator)
{
if (separator == 0) {
m_decimap_separator = '.';
} else {
m_decimap_separator = ',';
}
}
/** ----------------------------------------------------------------------------
* @brief 符号なし整数を文字列に変換
* @details (1234, ascii, 6, ' ') => ascii = " 1234"
* @details (1234, ascii, 6, '0') => ascii = "001234"
*
* @param value : 符号なし整数
* @param [out] ascii : 文字列
* @param len : 文字列長
* @param padding : パディング
* @retval 0 : OK
* @retval BUFFER_OVERFLOW : ascii
*/
uint8_t UIntToAscii(uint32_t value, char ascii[], uint8_t len, char padding)
{
uint8_t ret;
uint8_t adr;
ret = uitoa(value, &adr, ascii, len);
if (ret != 0) {
ascii[0] = '\0';
return ret;
}
ret = Set_Padding(adr, false, ascii, padding);
if (ret != 0) {
ascii[0] = '\0';
return ret;
}
ascii[len] = '\0';
return 0;
}
/** ----------------------------------------------------------------------------
* @brief 符号あり整数を文字列に変換
* @details ( 1234, ascii, 6, ' ') => ascii = " 1234"
* @details (-1234, ascii, 6, ' ') => ascii = " -1234"
* @details (-1234, ascii, 6, '0') => ascii = "-01234"
*
* @param value : 符号あり整数 [-INT32_MAX~INT32_MAX]
* @param [out] ascii : 文字列
* @param len : 文字列長
* @param padding : パディング
* @retval 0 : OK
* @retval OUT_OF_RANGE : ascii < -INT32_MAX
* @retval BUFFER_OVERFLOW : ascii
*/
uint8_t IntToAscii(int32_t value, char ascii[], uint8_t len, char padding)
{
bool f_minus = false;
uint8_t ret;
uint8_t adr;
uint32_t uvalue;
if (value < 0) {
if (value < -INT32_MAX) {
ascii[0] = '\0';
return OUT_OF_RANGE;
}
f_minus = true;
value *= -1;
}
uvalue = value;
ret = uitoa(uvalue, &adr, ascii, len);
if (ret != 0) {
ascii[0] = '\0';
return ret;
}
ret = Set_Padding(adr, f_minus, ascii, padding);
if (ret != 0) {
ascii[0] = '\0';
return ret;
}
ascii[len] = '\0';
return 0;
}
/** ----------------------------------------------------------------------------
* @brief 小数を文字列に変換
* @details ( 1234, ascii, 6, 2, ' ') => ascii = " 12.34"
* @details (-1234, ascii, 6, 2, ' ') => ascii = "-12.34"
* @details (-1234, ascii, 10, 5, ' ') => ascii = " -0.01234"
* @details (-1234, ascii, 10, 5, '0') => ascii = "-000.01234"
*
* @param value : 符号あり整数 [-INT32_MAX~INT32_MAX]
* @param [out] ascii : 文字列
* @param len : 文字列長
* @param dec_place : 小数点以下の桁数
* @param padding : パディング
* @retval 0 : OK
* @retval OUT_OF_RANGE : ascii < -INT32_MAX
* @retval BUFFER_OVERFLOW : ascii
*/
uint8_t IntFloatToAscii(int32_t value, char ascii[], uint8_t len, uint8_t dec_place, char padding)
{
bool f_minus = false;
uint8_t ret;
uint8_t adr = len;
uint32_t uvalue;
uint32_t tmp;
if ((dec_place > 0) && (len < dec_place + 2)) {
ascii[0] = '\0';
return BUFFER_OVERFLOW;
}
if (value < 0) {
if (value < -INT32_MAX) {
ascii[0] = '\0';
return OUT_OF_RANGE;
}
f_minus = true;
value *= -1;
}
uvalue = value;
// 小数部
if (dec_place > 0) {
while (dec_place > 0) {
--dec_place;
tmp = uvalue;
uvalue /= 10;
ascii[adr - 1] = (char)((uint8_t)(tmp - uvalue * 10) | 0x30);
--adr;
if (uvalue == 0) {
break;
}
}
while (dec_place > 0) {
--dec_place;
ascii[adr - 1] = '0';
--adr;
}
ascii[adr - 1] = m_decimap_separator;
--adr;
}
// 整数部
ret = uitoa(uvalue, &adr, ascii, adr);
if (ret != 0) {
ascii[0] = '\0';
return ret;
}
ret = Set_Padding(adr, f_minus, ascii, padding);
if (ret != 0) {
ascii[0] = '\0';
return ret;
}
ascii[len] = '\0';
return 0;
}
/** ----------------------------------------------------------------------------
* @brief 小数を文字列に変換
* @details ( 12.34, ascii, 6, 2, ' ') => ascii = " 12.34"
* @details (-12.34, ascii, 6, 2, ' ') => ascii = "-12.34"
* @details (-0.01234, ascii, 10, 5, ' ') => ascii = " -0.01234"
* @details (-0.01234, ascii, 10, 5, '0') => ascii = "-000.01234"
*
* @param value : 符号あり整数
* @param [out] ascii : 文字列
* @param len : 文字列長
* @param dec_place : 小数点以下の桁数
* @param padding : パディング
* @retval 0 : OK
* @retval OUT_OF_RANGE : dec_place > POW_MAX - 1
* @retval BUFFER_OVERFLOW : ascii
*/
uint8_t DoubleToAscii(double value, char ascii[], uint8_t len, uint8_t dec_place, char padding)
{
uint8_t ret;
if (dec_place > (POW_MAX - 1)) {
ascii[0] = '\0';
return OUT_OF_RANGE;
}
value = value * m_pow10[dec_place];
if (value >= 0) {
value += 0.5;
} else {
value -= 0.5;
}
ret = IntFloatToAscii((int32_t)value, ascii, len, dec_place, padding);
return ret;
}
/** ----------------------------------------------------------------------------
* @brief 符号なし整数を文字列に変換
*
* @param value : 符号なし整数
* @param [out] adr : 変換後先頭アドレス
* @param [out] ascii : 文字列
* @param len : 文字列長
* @retval 0 : OK
* @retval BUFFER_OVERFLOW : ascii
*/
static uint8_t uitoa(uint32_t value, uint8_t *adr, char ascii[], uint8_t len)
{
uint8_t i;
uint32_t tmp;
for (i = len; i > 0; --i) {
tmp = value;
value /= 10;
ascii[i - 1] = (char)((uint8_t)(tmp - value * 10) | 0x30);
if (value == 0) {
--i;
break;
}
}
if (value != 0) {
return BUFFER_OVERFLOW;
}
*adr = i;
return 0;
}
/** ----------------------------------------------------------------------------
* @brief パディングを設定
*
* @param adr : パディングの挿入開始アドレス
* @param f_minus : 負の値?
* @param [out] ascii : 文字列
* @param padding : パディング
* @retval 0 : OK
* @retval BUFFER_OVERFLOW : ascii
*/
static uint8_t Set_Padding(uint8_t adr, bool f_minus, char ascii[], uint8_t padding)
{
if (f_minus == true) {
if (adr == 0) {
return BUFFER_OVERFLOW;
}
if (padding != '0') {
f_minus = false;
ascii[adr - 1] = '-';
--adr;
}
}
while (adr > 0) {
ascii[adr - 1] = padding;
--adr;
}
if (f_minus == true) {
ascii[0] = '-';
}
return 0;
}
サンプルコードの使用例
使用例代わりに、CppUTestによるテストコードを掲載しておきます。
#include "CppUTest/TestHarness.h"
#include "mylib.h"
// clang-format off
TEST_GROUP(mylib){
TEST_SETUP(){
}
TEST_TEARDOWN(){
}
};
// clang-format on
TEST(mylib, Test_UIntToAscii)
{
char ascii[15];
uint8_t ret;
// 成功パターン
ret = UIntToAscii(1234, ascii, 4, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "1234", 4);
ret = UIntToAscii(1234, ascii, 6, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 1234", 6);
ret = UIntToAscii(1234, ascii, 6, '0');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "001234", 6);
// 失敗パターン
ret = UIntToAscii(1234, ascii, 3, ' ');
UNSIGNED_LONGS_EQUAL(ret, BUFFER_OVERFLOW);
}
TEST(mylib, Test_IntToAscii)
{
char ascii[15];
uint8_t ret;
// 成功パターン
ret = IntToAscii(1234, ascii, 4, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "1234", 4);
ret = IntToAscii(1234, ascii, 6, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 1234", 6);
ret = IntToAscii(1234, ascii, 6, '0');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "001234", 6);
ret = IntToAscii(-1234, ascii, 5, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "-1234", 5);
ret = IntToAscii(-1234, ascii, 6, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " -1234", 6);
ret = IntToAscii(-1234, ascii, 6, '0');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "-01234", 6);
// 失敗パターン
ret = IntToAscii(1234, ascii, 3, ' ');
UNSIGNED_LONGS_EQUAL(ret, BUFFER_OVERFLOW);
ret = IntToAscii(-2147483648, ascii, 11, ' ');
UNSIGNED_LONGS_EQUAL(ret, OUT_OF_RANGE);
}
TEST(mylib, Test_IntFloatToAscii)
{
char ascii[15];
uint8_t ret;
// 成功パターン
ret = IntFloatToAscii(12345, ascii, 7, 1, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 1234.5", 7);
ret = IntFloatToAscii(12345, ascii, 7, 2, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 123.45", 7);
ret = IntFloatToAscii(12345, ascii, 7, 5, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "0.12345", 7);
Set_DecimapSeparator(1); // 小数点の記号をコンマに設定
ret = IntFloatToAscii(-12345, ascii, 8, 4, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " -1,2345", 8);
Set_DecimapSeparator(0); // 小数点の記号をピリオドに設定
ret = IntFloatToAscii(-12345, ascii, 8, 4, '0');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "-01.2345", 8);
// 失敗パターン
ret = IntFloatToAscii(12345, ascii, 5, 1, ' ');
UNSIGNED_LONGS_EQUAL(ret, BUFFER_OVERFLOW);
ret = IntFloatToAscii(-2147483648, ascii, 12, 3, ' ');
UNSIGNED_LONGS_EQUAL(ret, OUT_OF_RANGE);
}
TEST(mylib, Test_DoubleToAscii)
{
char ascii[15];
uint8_t ret;
// 成功パターン
ret = DoubleToAscii(1234.5, ascii, 7, 1, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 1234.5", 7);
Set_DecimapSeparator(1); // 小数点の記号をコンマに設定
ret = DoubleToAscii(123.45, ascii, 7, 2, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 123,45", 7);
ret = DoubleToAscii(123.45, ascii, 6, 1, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " 123,5", 6);
Set_DecimapSeparator(0); // 小数点の記号をピリオドに設定
ret = DoubleToAscii(0.12345, ascii, 7, 5, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "0.12345", 7);
ret = DoubleToAscii(0.12345, ascii, 8, 6, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "0.123450", 8);
ret = DoubleToAscii(-1.2345, ascii, 8, 4, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " -1.2345", 8);
ret = DoubleToAscii(-1.2345, ascii, 8, 4, '0');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, "-01.2345", 8);
ret = DoubleToAscii(-1.2345, ascii, 7, 3, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " -1.235", 7);
ret = DoubleToAscii(-1.2345, ascii, 6, 2, ' ');
UNSIGNED_LONGS_EQUAL(ret, 0);
STRNCMP_EQUAL(ascii, " -1.23", 6);
// 失敗パターン
ret = DoubleToAscii(1234.5, ascii, 5, 1, ' ');
UNSIGNED_LONGS_EQUAL(ret, BUFFER_OVERFLOW);
ret = DoubleToAscii(1.23456789, ascii, 10, 8, ' ');
UNSIGNED_LONGS_EQUAL(ret, OUT_OF_RANGE);
ret = DoubleToAscii(-2147483.648, ascii, 12, 3, ' ');
UNSIGNED_LONGS_EQUAL(ret, OUT_OF_RANGE);
}