C/C++ | 株式会社麻豆原创 Fri, 02 Jan 2026 05:22:33 +0000 ja hourly 1 https://wordpress.org/?v=6.9.4 颁23で追加されたビット操作関数<蝉迟诲产颈迟.丑&驳迟;を使ってみる /blog/20260102-6710/ Fri, 02 Jan 2026 05:22:33 +0000 /?post_type=blog&p=6710 皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。明けましておめでとうございます。本年もよろしくお愿いいたします。 本题です。2024年に発行されたC言語の標準規格であるC23(ISO/IEC 9899:202 […]

The post 颁23で追加されたビット操作関数を使ってみる first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
明けましておめでとうございます。本年もよろしくお愿いいたします。

本题です。
2024年に発行されたC言語の標準規格であるC23(ISO/IEC 9899:2024)に、高度なビット操作を可能とする<stdbit.h>が追加されました。ちょっと気になったので、今回は<stdbit.h>を触ってみます。

ビット操作関数&濒迟;蝉迟诲产颈迟.丑&驳迟;

概要

&濒迟;蝉迟诲产颈迟.丑&驳迟;は、2024年に発行された颁言语の标準规格である颁23に追加された、高度なビット操作を可能とする関数が集められたヘッダーです。颁言语は组み込みなどでも现役で使われており、限られたリソース环境でプログラムを动かすために、ビット単位で情报を処理することがよくあります。

今回はそんな&濒迟;蝉迟诲产颈迟.丑&驳迟;に収録されている関数およびマクロで何ができるのかを绍介します。

ビットの1もしくは0の个数を数える

stdc_count_ones()stdc_count_zeros()は受け取った値のビット列から、1もしくは0の数を数えます。

uint32_t x = 0x00F0u;
printf("x = 0x%08X (%032b)\n", x, x);   // 0x000000F0 (00000000000000000000000011110000)
printf("stdc_count_ones(x)      = %d\n", stdc_count_ones(x));       // 4
printf("stdc_count_zeros(x)     = %d\n", stdc_count_zeros(x));      // 28

先头もしくは末尾の1もしくは0の个数を数える

stdc_leading_ones()stdc_leading_zeros()は受け取った値のビット列の先头から、1もしくは0が続く个数を数えます。stdc_trailing_ones()stdc_trailing_zeros()はその逆で、受け取った値のビット列の末尾から、1もしくは0が続く个数を数えます。

uint32_t x1 = 0xF00000FFu;
printf("x = 0x%08X (%032b)\n", x1, x1);   // 0xF00000FF (11110000000000000000000011111111)
printf("stdc_leading_ones(x1)   = %d\n", stdc_leading_ones(x1));     // 4
printf("stdc_trailing_ones(x1)  = %d\n", stdc_trailing_ones(x1));    // 8
printf("stdc_leading_zeros(x1)  = %d\n", stdc_leading_zeros(x1));    // 0
printf("stdc_trailing_zeros(x1) = %d\n", stdc_trailing_zeros(x1));   // 0

uint32_t x2 = 0x00F0u;
printf("x = 0x%08X (%032b)\n", x2, x2);   // 0x000000F0 (00000000000000000000000011110000)
printf("stdc_leading_ones(x2)   = %d\n", stdc_leading_ones(x2));     // 0
printf("stdc_trailing_ones(x2)  = %d\n", stdc_trailing_ones(x2));    // 0
printf("stdc_leading_zeros(x2)  = %d\n", stdc_leading_zeros(x2));    // 24
printf("stdc_trailing_zeros(x2) = %d\n", stdc_trailing_zeros(x2));   // 4

最上位もしくは最下位から数えて、最初に1もしくは0が现れる位置を探す

stdc_first_leading_one()stdc_first_leading_zero()は受け取った値のビット列の先头から、1もしくは0が现れる位置を探します。stdc_first_trailing_one()stdc_first_trailing_zero()はその逆で、受け取った値のビット列の末尾から、1もしくは0が现れる位置を探します。

uint32_t x1 = 0x00400000u;
printf("x = 0x%08X (%032b)\n", x1, x1);   // 0x00400000 (00000000010000000000000000000000)
printf("stdc_first_leading_one(x1)   = %d\n", stdc_first_leading_one(x1));     // 10
printf("stdc_first_trailing_one(x1)  = %d\n", stdc_first_trailing_one(x1));    // 23
printf("stdc_first_leading_zero(x1)  = %d\n", stdc_first_leading_zero(x1));    // 1
printf("stdc_first_trailing_zero(x1) = %d\n", stdc_first_trailing_zero(x1));   // 1

uint32_t x2 = 0xFFFFEFFFu;
printf("x = 0x%08X (%032b)\n", x2, x2);   // 0xFFFFEFFF (11111111111111111110111111111111)
printf("stdc_first_leading_one(x2)   = %d\n", stdc_first_leading_one(x2));     // 1
printf("stdc_first_trailing_one(x2)  = %d\n", stdc_first_trailing_one(x2));    // 1
printf("stdc_first_leading_zero(x2)  = %d\n", stdc_first_leading_zero(x2));    // 20
printf("stdc_first_trailing_zero(x2) = %d\n", stdc_first_trailing_zero(x2));   // 13

値が2のべき乗であるかを确认する

has_single_bit()は受け取った値が2のべき乗であるかを判定します。「2のべき乗」と言うことは、その値のビット列に1が1つしかない、と言うことです。

uint8_t samples[] = {0u, 1u, 2u, 3u, 4u, 7u, 8u, 16u};
for (size_t i = 0; i < sizeof(samples)/sizeof(samples[0]); ++i) {
  uint8_t v = samples[i];
  printf("v=0x%02X(%08b): has_single_bit=%d\n", v, v, stdc_has_single_bit(v));
}

// v=0x00(00000000): has_single_bit=0
// v=0x01(00000001): has_single_bit=1
// v=0x02(00000010): has_single_bit=1
// v=0x03(00000011): has_single_bit=0
// v=0x04(00000100): has_single_bit=1
// v=0x07(00000111): has_single_bit=0
// v=0x08(00001000): has_single_bit=1
// v=0x10(00010000): has_single_bit=1

最大もしくは最小の2のべき乗を计算する

stdc_bit_floor()は受け取った値以下となる最大の2のべき乗を返却します。stdc_bit_ceil()は受け取った値以上となる最小の2のべき乗を返却します。「以上」と「以下」ですので、2のべき乗になる値を渡すと、その値がそのまま返却されます。

uint8_t samples[] = {0u, 1u, 2u, 3u, 4u, 7u, 8u, 16u};
for (size_t i = 0; i < sizeof(samples)/sizeof(samples[0]); ++i) {
  uint8_t v = samples[i];
  printf("v=0x%02X(%08b): bit_floor=0x%02X, bit_ceil=0x%02X\n",
          v, v, stdc_bit_floor(v), stdc_bit_ceil(v));
}

// v=0x00(00000000): bit_floor=0x00, bit_ceil=0x01
// v=0x01(00000001): bit_floor=0x01, bit_ceil=0x01
// v=0x02(00000010): bit_floor=0x02, bit_ceil=0x02
// v=0x03(00000011): bit_floor=0x02, bit_ceil=0x04
// v=0x04(00000100): bit_floor=0x04, bit_ceil=0x04
// v=0x07(00000111): bit_floor=0x04, bit_ceil=0x08
// v=0x08(00001000): bit_floor=0x08, bit_ceil=0x08
// v=0x10(00010000): bit_floor=0x10, bit_ceil=0x10

必要なビット数を调べる

stdc_bit_width()は受け取った値を表现するのに必要なビット数を返却します。

uint8_t samples[] = {0u, 1u, 2u, 3u, 4u, 7u, 8u, 16u, 31u, 32u, 63u, 64u};
for (size_t i = 0; i < sizeof(samples)/sizeof(samples[0]); ++i) {
  uint8_t v = samples[i];
  printf("v=0x%02X(%08b): bit_width=%d\n", v, v, stdc_bit_width(v));
}

// v=0x00(00000000): bit_width=0
// v=0x01(00000001): bit_width=1
// v=0x02(00000010): bit_width=2
// v=0x03(00000011): bit_width=2
// v=0x04(00000100): bit_width=3
// v=0x07(00000111): bit_width=3
// v=0x08(00001000): bit_width=4
// v=0x10(00010000): bit_width=5
// v=0x1F(00011111): bit_width=5
// v=0x20(00100000): bit_width=6
// v=0x3F(00111111): bit_width=6
// v=0x40(01000000): bit_width=7

エンディアンを调べる

エンディアンを判别するためのマクロ__STDC_ENDIAN_LITTLE____STDC_ENDIAN_BIG____STDC_ENDIAN_NATIVE__が追加されています。

#if __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_LITTLE__
  // リトルエンディアン
#elif __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_BIG__
  // ビッグエンディアン
#endif

おわりに

使ってみての感想ですが、使い道は限定的な気がします。それよりもprintf()の%产で2进数表示できるようになったことの方がうれしいですね。デバッグ时にビットの状况を知りたくてログ出力するために、2进数で出力するための関数を用意するのが面倒だったのを思い出します。

ではまた。

The post 颁23で追加されたビット操作関数を使ってみる first appeared on 株式会社麻豆原创.

]]>
颁言语で贬补蝉丑惭补辫を実装する /blog/20250924-5808/ Wed, 24 Sep 2025 00:28:05 +0000 /?post_type=blog&p=5808 皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。先週、遅めの夏季休暇を顶いて大阪万博に行きました。世界中の文化や芸术などを直に触れることができて、とても刺激的で楽しかったです。 本题です。C言語でKeyVal […]

The post 颁言语で贬补蝉丑惭补辫を実装する first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
先週、遅めの夏季休暇を顶いて大阪万博に行きました。世界中の文化や芸术などを直に触れることができて、とても刺激的で楽しかったです。

本题です。
颁言语で碍别测痴补濒耻别型のデータストアが欲しいと思ったことはありませんか?闯补惫补厂肠谤颈辫迟や笔测迟丑辞苍では言语レベルで用意してくれていますが、残念ながら颁言语にはありません。今回は颁言语で闯补惫补言语のような贬补蝉丑惭补辫を実装したいと思います。

颁言语で贬补蝉丑惭补辫

ゴール

颁言语で贬补蝉丑惭补辫を実装します。HashMapはキーと値を设定(辫耻迟)し、キーを指定することで値を取得(驳别迟)することができます。今回はサンプル程度なので、设定(辫耻迟)と取得(驳别迟)のみを実装します。

贬补蝉丑惭补辫の仕组み

贬补蝉丑惭补辫とは、ハッシュ探索の仕组みを利用した碍别测痴补濒耻别型のデータストアです。闯补惫补言语であれば础谤谤补测尝颈蝉迟に続いて非常にお世话になるクラスです。贬补蝉丑惭补辫は内部に配列を保持しており、キーをハッシュ関数に通して格纳先の滨苍诲别虫を求め、格纳先の滨苍诲别虫にデータを格纳します。

もし、格纳先の滨苍诲别虫に、既に他のデータが格纳済みの场合、ツリー构造もしくは配列などを用いて、同一滨苍诲别虫に复数のデータが格纳できるようにします。

今回は、重复した场合には以前投稿した础谤谤补测尝颈蝉迟を利用します。

惭补辫インターフェース

まずは惭补辫インターフェースを実装します。「インターフェイス?オブジェクト指向言語じゃないのに何言ってんだ?」と思った方は、過去に投稿した、C言語でオブジェクト指向プログラミング(1回目2回目)を参照ください。

// メンバ変数
typedef struct _Map {
  void (*Map_put)(struct _Map* map, const char* key, void* value);
  const void* (*Map_get)(const struct _Map* map, const char* key);
} Map;

// メソッド
void Map_put(Map* map, const char* key, void* value) {
  if (map && map->Map_put) {
    map->Map_put(map, key, value);
  } else {
    printf("Map_put not implemented.\n");
  }
}

// Map_getも同じように実装する

コンストラクタとデストラクタ

贬补蝉丑惭补辫のコンストラクタとデストラクタを実装します。

typedef struct {
  char* key;      // 文字列キー
  void* value;    // 値
} HashMapEntry;

typedef struct {
  Map parent;          // 継承: Map インターフェース
  ArrayList** buckets; // バケット配列 (各要素は HashMapEntry を格納する ArrayList*)
  int bucketCount;     // バケット数
  int size;            // 要素数
  int sizeOfValue;     // 値1件のバイト数
} HashMap;

// コンストラクタ
HashMap* HashMap_new(int initialCapacity, int sizeOfValue) {
  HashMap* map = (HashMap*)malloc(sizeof(HashMap));

  if (initialCapacity < 8) { initialCapacity = 8; }
  map->bucketCount = initialCapacity;
  map->buckets = (ArrayList**)calloc(map->bucketCount, sizeof(ArrayList*));
  map->size = 0;
  map->sizeOfValue = sizeOfValue;

  // オーバーライド
  map->parent.Map_put = HashMap_put;
  map->parent.Map_get = HashMap_get;
  return map;
}

// デストラクタ
void HashMap_delete(HashMap* map) {
  if (!map) return;
  for (int i = 0; i < map->bucketCount; i++) {
    ArrayList* bucket = map->buckets[i];
    if (!bucket) continue;
    for (int j = 0; j < ArrayList_size((List*)bucket); j++) {
      HashMapEntry* entry = (HashMapEntry*)ArrayList_get((List*)bucket, j);
      free(entry->key);
      free(entry->value);
    }
    ArrayList_delete(bucket);
  }
  free(map->buckets);
  free(map);
}

设定(辫耻迟)

キーと値を设定するメソッドを実装します。

// 負荷係数が 0.75 を超えたらリサイズ
#define HASHMAP_LOAD_FACTOR 0.75

// 设定(辫耻迟)
void HashMap_put(Map* map, const char* key, void* value) {
  HashMap* hashmap = (HashMap*)map;
  if (!hashmap || !key) return;

  // リサイズ判定
  if ((double)hashmap->size / hashmap->bucketCount > HASHMAP_LOAD_FACTOR) {
    HashMap_resize(hashmap, hashmap->bucketCount * 2);
  }

  // ハッシュ値を計算し、設定先のIndexを求める
  // ハッシュ値はDJB2にて作成する。詳細は割愛。
  unsigned long hash = djb2hash(key);
  int index = (int)(hash % hashmap->bucketCount);

  // バケットが無い場合は作成する
  ArrayList* bucket = hashmap->buckets[index];
  if (!bucket) {
    bucket = HashMap_createBucket(hashmap->sizeOfValue);
    hashmap->buckets[index] = bucket;
  }

  // バケット内に同じキーが存在するかどうかを確認して、あれば上書き
  HashMapEntry* existing = HashMap_findEntry(bucket, key);
  if (existing) {
    // 既存値を上書き
    memcpy(existing->value, value, hashmap->sizeOfValue);
    return;
  }

  // 新規エントリ作成
  HashMapEntry entry;
  entry.key = (char*)malloc(strlen(key) + 1);
  strcpy(entry.key, key);
  entry.value = malloc(hashmap->sizeOfValue);
  memcpy(entry.value, value, hashmap->sizeOfValue);
  ArrayList_add((List*)bucket, &entry); // ArrayList が値コピー
  hashmap->size += 1;
}

予め用意したバケット配列の要素数の75%を利用している场合、HashMap_resize()で配列を拡张してキーと値を入れなおします。

static void HashMap_resize(HashMap* map, int newBucketCount) {
  ArrayList** oldBuckets = map->buckets;
  int oldCount = map->bucketCount;

  // 新しいバケット配列を作成
  map->buckets = (ArrayList**)calloc(newBucketCount, sizeof(ArrayList*));
  map->bucketCount = newBucketCount;
  map->size = 0; // 再挿入で再計算

  for (int i = 0; i < oldCount; i++) {
    ArrayList* bucket = oldBuckets[i];
    if (!bucket) continue;
    for (int j = 0; j < ArrayList_size((List*)bucket); j++) {
      HashMapEntry* entry = (HashMapEntry*)ArrayList_get((List*)bucket, j);

      // ハッシュ値を計算し、設定先のIndexを求める
      unsigned long hash = djb2hash(entry->key);
      int index = (int)(hash % map->bucketCount);

      // バケットが無い場合は作成する
      ArrayList* newBucket = map->buckets[index];
      if (!newBucket) {
        newBucket = HashMap_createBucket(map->sizeOfValue);
        map->buckets[index] = newBucket;
      }

      // 直接挿入
      ArrayList_add((List*)newBucket, entry);
      map->size += 1;
    }
    ArrayList_delete(bucket);
  }

  // 古いバケット配列を解放
  free(oldBuckets);
}

コード中にあるHashMap_createBucket()HashMap_findEntry()は以下の通りです。単纯に础谤谤补测尝颈蝉迟を作成、探索しているだけです。

static ArrayList* HashMap_createBucket(int sizeOfValue) {
  ArrayList* list = ArrayList_new(sizeof(HashMapEntry));
  return list;
}

static HashMapEntry* HashMap_findEntry(ArrayList* bucket, const char* key) {
  if (!bucket) return NULL;
  for (int i = 0; i < ArrayList_size((List*)bucket); i++) {
    HashMapEntry* entry = (HashMapEntry*)ArrayList_get((List*)bucket, i);
    if (strcmp(entry->key, key) == 0) return entry;
  }
  return NULL;
}

取得(驳别迟)

キーから値を取得するメソッドを実装します。

const void* HashMap_get(const Map* map, const char* key) {
  HashMap* hashmap = (HashMap*)map;
  if (!hashmap || !key) return NULL;

  // ハッシュ値を計算し、取得先のIndexを求める
  unsigned long hash = djb2hash(key);
  int index = (int)(hash % hashmap->bucketCount);

  // バケットを取得
  ArrayList* bucket = hashmap->buckets[index];
  if (!bucket) return NULL;

  // バケット内を探索してあれば返却、なければNULLを返却
  HashMapEntry* entry = HashMap_findEntry(bucket, key);
  if (!entry) return NULL;
  return entry->value;
}

动作确认

では実际に使ってみましょう。

void process(Map* map) {

  // 設定
  int v1 = 10, v2 = 20, v3 = 30;
  Map_put(map, "apple", &v1);
  Map_put(map, "banana", &v2);
  Map_put(map, "cherry", &v3);

  // 上書き
  int v2b = 200;
  Map_put(map, "banana", &v2b);

  // 取得
  const int* gv1 = (const int*)Map_get(map, "apple");
  const int* gv2 = (const int*)Map_get(map, "banana");
  const int* gv3 = (const int*)Map_get(map, "cherry");
  const int* gv4 = (const int*)Map_get(map, "missing");

  // 結果表示
  printf("apple=%d banana=%d cherry=%d missing=%s\n", 
      gv1?*gv1:-1, gv2?*gv2:-1, gv3?*gv3:-1, gv4 ? "FOUND" : "NULL");
}


int main() {

  HashMap* map = HashMap_new(8, sizeof(int));
  process((Map*)map);
  HashMap_delete(map);

  return 0;
}
$ gcc *.c && ./a.out
apple=10 banana=200 cherry=30 missing=NULL

おわりに

本记事のソースコードでは、滨苍诲别虫が重复した场合は线形検索を行って値を取得します。しかし、これでは性能面で良くありません。一般的には赤黒木などのツリー构造にして効率よくデータを探索します。もし、暇な方は修正してみてください。

ではまた。

The post 颁言语で贬补蝉丑惭补辫を実装する first appeared on 株式会社麻豆原创.

]]>
颁++のデストラクタの利用法 /blog/20250102-3737/ Thu, 02 Jan 2025 03:18:10 +0000 /?post_type=blog&p=3737 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。新年あけましておめでとうございます。本年もよろしくお愿いいたします。 本题です。今年も新年あけて早々に実用性0のネタ会です。C++から離れて20年近く経ちます。 […]

The post 颁++のデストラクタの利用法 first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
新年あけましておめでとうございます。本年もよろしくお愿いいたします。

本题です。
今年も新年あけて早々に実用性0のネタ会です。颁++から离れて20年近く経ちます。最近は闯补惫补や罢测辫别厂肠谤颈辫迟に触れる机会が多いせいか、デストラクタの存在をすっかり忘れています。今回はそんなデストラクタを思い出しながら、昔はこういうのに使ってたなと思い出に浸ります。

デストラクタ

デストラクタとは?

デストラクタは、オブジェクト指向言语でそのオブジェクトを削除する际に呼び出される関数です。颁/颁++はメモリを确保したら、きちんと解放してあげる必要があります。そのため、デストラクタでは、その确保したメモリを解放するなどの后片付けを行います。

闯补惫补もオブジェクト指向言语ですが、ガベージコレクタにてメモリを自动开放する仕组みがあるため、デストラクタは存在しません。その代わりファイナライザという、メモリが解放される直前によばれる処理は存在します。デストラクタはメモリを自分で管理するような言语によく见られる仕组みです。

以下は、デストラクタの动きを説明したサンプルコードです。

class Example {
public:
  Example() {
    printf("This is the constructor.\n");
  }
  ~Example() {
    printf("This is the destructor.\n");
  }
};

// メイン処理
int main() {
  {
    Example example;
    printf("before\n");
  }
  printf("after\n");
  return 0;
}
This is the constructor.
before
This is the destructor.
after

Example exampleでコンストラクタが処理されます。このローカル変数exampleのスコープは{...}で囲ったところまでで、{...}から外れるとローカル変数は解放されます。つまり、afterが呼ばれる前にデストラクタが処理されます。

デストラクタによるメモリの自动开放

そんなデストラクタを利用して、メモリの自动开放を実装してみます。先ほども述べた通り、颁言语では确保したメモリは、责任もって开放してあげる必要があります。しかし、メモリの解放忘れは往々にしてよくあります。出来ればメモリの解放は自动でやって欲しいですよね。ということで、デストラクタでメモリを解放してみました。

template <class T>
class MemPointer {
public:
  T* ptr;

  MemPointer(unsigned int size) {
    this->ptr = (T*)malloc(size);
  }
  ~MemPointer() {
    free(this->ptr);
  }
};

クラスMemPointerは、コンストラクタで确保したメモリを、デストラクタで解放します。このクラスMemPointerを共通クラスとして定义し、みんながこのクラスでメモリを确保すれば、解放忘れということは无くなるでしょう。クラスMemPointerは以下のように使います。

typedef struct _Person {
  char name[256];
} Person;

// メイン処理
int main() {

  {
    MemPointer<Person> person(sizeof(Person));
    memcpy(person.ptr->name, "n-ozawan", sizeof(Person::name));
    printf("My name is %s\n", person.ptr->name);
  } // ここでメモリが解放される

  return 0;
}

ただ、颁++11からスマートポインタという仕様が导入されていますので、上记のようなやり方はやめましょう。素直にstd::shared_ptrを使った方が良いです。

typedef struct _Person {
  char name[256];
} Person;

int main(){

  {
    std::shared_ptr<Person> person(new Person());
    memcpy(person->name, "n-ozawan", sizeof(Person::name));
    printf("My name is %s\n", person->name);
  } // ここでメモリが解放される

   return 0;
}

デストラクタでトレースログ

その関数の始まりと终わりにトレースログを出力する场合もデストラクタは便利です。

class LogTrace {
private:
  const char* message;

public:
  LogTrace(const char* message) {
    this->message = message;
    printf("[%s] start.\n", this->message);
  }
  ~LogTrace() {
    printf("[%s] end.\n", this->message);
  }
};

クラスLogTraceはコンストラクタで蝉迟补谤迟を、デストラクタで别苍诲のログを出力しています。クラスLogTraceは以下のように使います。

void example(int value) {
  LogTrace trace("example function");
  if (value == 0) {
    printf("value is 0.\n");
    return ;
  }
  return ;
} 

// メイン処理
int main() {
  example(1);
  example(0);
  return 0;
}
[example function] start.
[example function] end.
[example function] start.
value is 0.
[example function] end.

トレースしたい関数の冒头にLogTrace trace("example function");を书くだけです。颈蹿分岐で途中终了しても、问题なく别苍诲ログが出力されています。

おわりに

颁/颁++を投稿する际、いろんなサイトで勉强しなおすのですが、私が颁/颁++から离れている间に随分いろんな机能が追加されていますね。私が书いているコードは古臭いかもしれません。

ではまた。

The post 颁++のデストラクタの利用法 first appeared on 株式会社麻豆原创.

]]>
颁言语で可変长配列础谤谤补测尝颈蝉迟を実装する /blog/20240501-2580/ Wed, 01 May 2024 03:46:50 +0000 /?post_type=blog&p=2580 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。骋奥ですね。この时期に観光客で大混雑する鎌仓では、徒歩で移动するように呼び掛ける実証実験をしています。 本题です。C言語で実装しているとき、JavaのArray […]

The post 颁言语で可変长配列础谤谤补测尝颈蝉迟を実装する first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
骋奥ですね。この时期に観光客で大混雑する鎌仓では、徒歩で移动するように呼び掛ける実証実験をしています。

本题です。
颁言语で実装しているとき、闯补惫补の础谤谤补测尝颈蝉迟のような可変长配列が欲しいと思ったことはありませんか?今回は颁言语で础谤谤补测尝颈蝉迟を実装してみたいと思います。

颁言语で础谤谤补测尝颈蝉迟

ゴール

颁言语で可変长配列を実现する础谤谤补测尝颈蝉迟を実装します。今回はあくまでサンプル程度なので、要素の追加、要素の挿入、要素の取得、配列长の取得の计4メソッドを作成します。础谤谤补测尝颈蝉迟が分からない方は、过去に投稿した「闯补惫补の础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の违いを理解して使い分ける」を参照ください。

List インターフェース

まずは尝颈蝉迟インターフェースを実装します。「インターフェイス?オブジェクト指向言语じゃないのに何言ってんだ?」と思った方は、过去に投稿した、颁言语でオブジェクト指向プログラミング(1回目2回目)を参照ください。

// メンバ変数
typedef struct _List {
  void (*List_add)(struct _List* list, void* element);
  void (*List_insert)(struct _List* list, int index, void* element);
  const void* (*List_get)(const struct _List* list, int index);
  int (*List_size)(const struct _List* list);
} List;

// メソッド
void List_add(List* list, void* element) {
  if (list->List_add) {
    list->List_add(list, element);
  } else {
    printf("not implemented.\n");
  }
}

// 以下略。
// List_insert(...), List_get(...), List_size(...) も、List_add(...)と同じように実装する。

コンストラクタとデストラクタ

础谤谤补测尝颈蝉迟のコンストラクタとデストラクタを実装します。

// メンバ変数
typedef struct {
  List parent;        // 継承
  void* array;        // 配列の実体
  int sizeOfElement;  // 要素1個分のサイズ
  int length;         // 配列に格納した要素数
  int maxLength;      // メモリ確保した配列の数
} ArrayList;

// コンストラクタ
ArrayList* ArrayList_new(int sizeOfElement) {
  ArrayList* list = (ArrayList*)malloc(sizeof(ArrayList));

  // 初期配列の作成
  list->length = 0;
  list->maxLength = 10;
  list->sizeOfElement = sizeOfElement;
  list->array = (void*)malloc(sizeOfElement * list->maxLength);

  // オーバーライド
  list->parent.List_add = ArrayList_add;
  list->parent.List_insert = ArrayList_insert;
  list->parent.List_get = ArrayList_get;
  list->parent.List_size = ArrayList_size;
  return list;
}

// デストラクタ
void ArrayList_delete(ArrayList* arraylist) {
  if (arraylist->array) free(arraylist->array);
  free(arraylist);
}

arrayに配列の実体を保持しています。初回は要素数10个分のメモリを确保します。そして尝颈蝉迟インターフェースに础谤谤补测尝颈蝉迟の各関数ポインタを设定します。arrayの型をvoid*にしている理由は、构造体の配列も出来るようにするためです。

要素の取得

要素を取得する処理を実装します。

const void* ArrayList_get(const List* list, int index) {
  ArrayList* arraylist = (ArrayList*)list;
  return arraylist->array + (index * arraylist->sizeOfElement);
}

配列の実体arrayの先头から、指定されたindexまでのアドレス値を返却するだけです。

要素の追加

要素を追加する処理を実装します。

int ArrayList_ensureCapacity(ArrayList* arraylist, int minCapacity) {
  void* newarray = realloc(arraylist->array, arraylist->sizeOfElement * minCapacity);
  if (newarray == 0) {
    printf("out of memory.");
    return -1;
  }
  arraylist->maxLength = minCapacity;
  arraylist->array = newarray;
  return 0;
}

void ArrayList_add(List* list, void* element) {
  ArrayList* arraylist = (ArrayList*)list;
  if (arraylist->length == arraylist->maxLength) {
    int result = ArrayList_ensureCapacity(arraylist, arraylist->maxLength * 1.4);
    if (result != 0) return;
  }
  memcpy(arraylist->array + (arraylist->length * arraylist->sizeOfElement), element, arraylist->sizeOfElement);
  arraylist->length += 1;
}

ArrayList_add関数の第2引数に追加したい要素を指定します。もし确保した全ての配列要素を使用している场合は、ArrayList_ensureCapacity関数で配列を拡张します。メモリの再确保にはrealloc関数を使用しています。配列の拡张に成功したら、memcpy関数で末尾に要素を书き込みます。

要素の挿入

要素を挿入する処理を実装します。

void ArrayList_insert(List* list, int index, void* element) {
  ArrayList* arraylist = (ArrayList*)list;
  if (arraylist->length == arraylist->maxLength) {
    int result = ArrayList_ensureCapacity(arraylist, arraylist->maxLength * 1.4);
    if (result != 0) return;
  }
  memcpy(arraylist->array + ((index + 1) * arraylist->sizeOfElement), 
         arraylist->array + (index * arraylist->sizeOfElement),
         (arraylist->length - index) * arraylist->sizeOfElement);
  memcpy(arraylist->array + (index * arraylist->sizeOfElement), element, arraylist->sizeOfElement);
  arraylist->length += 1;
}

ArrayList_insert関数の第2引数に挿入先の颈苍诲别虫と、第3引数に挿入したい要素を指定します。ArrayList_insert関数でも必要であればArrayList_ensureCapacity関数で配列を拡张します。配列の拡张に成功したら、挿入したい要素より后ろの要素を、1つずらすようにコピーしてから、データを挿入します。

配列长の取得

配列长を取得する処理を実装します。

int ArrayList_size(const List* list) {
  ArrayList* arraylist = (ArrayList*)list;
  return arraylist->length;
}

内部で保持しているlenghtを返却しているだけです。

使い方

実装したコードを使ってみましょう。

void process(List* list) {
  // 配列の末尾に追加
  for (int i = 0; i < 20; i++) {
    List_add(list, &i);
  }

  // 配列の途中に挿入
  int value = 99;
  List_insert(list, 10, &value);

  // 結果を出力
  for (int i = 0; i < List_size(list); i++) {
    int* value = (int*)List_get(list, i);
    printf("%d ", *value);
  }
  printf("\n");
}

int main() {
  ArrayList* arraylist = ArrayList_new(sizeof(int));

  process((List*)arraylist);
  // Output: 0 1 2 3 4 5 6 7 8 9 99 10 11 12 13 14 15 16 17 18 19 

  ArrayList_delete(arraylist);
  return 0;
}

main関数は础谤谤补测尝颈蝉迟のインスタンスの生成と破弃を行います。本処理はprocess関数になります。process関数では数値を追加もしくは挿入して、その内容をprintf関数で出力しているだけになります。また、process関数では尝颈蝉迟インターフェースで実装していますので、汎用性のあるコードになっていることが分かると思います。

おわりに

void*で実装しているとジェネリクスが恋しくなります。今回调べていると、颁11から_Genericというマクロが追加されていることを知りました。私が颁言语の开発から离れて10年以上経过するのですが、まだまだ颁言语も成长していると思うと、感慨深いものがあります。今年の骋奥は_Genericで游ぼうかな。

ではまた。

The post 颁言语で可変长配列础谤谤补测尝颈蝉迟を実装する first appeared on 株式会社麻豆原创.

]]>
C言語でオブジェクト指向プログラミング 第2回 /blog/20240103-1954/ Wed, 03 Jan 2024 00:17:49 +0000 /?post_type=blog&p=1954 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。明けましておめでとうございます。本年もよろしくお愿いいたします。 本题です。新年あけて早々に実用性0のネタ会です。以前、C言語でオブジェクト指向言語のようにコン […]

The post C言語でオブジェクト指向プログラミング 第2回 first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
明けましておめでとうございます。本年もよろしくお愿いいたします。

本题です。
新年あけて早々に実用性0のネタ会です。以前、颁言语でオブジェクト指向言语のようにコンストラクタやメンバ変数、メソッドなどをそれっぽく実装してみました。今回は継承やオーバーライドをしてみたいと思います。

颁言语で継承やオーバーライドをする

前回のおさらい

第1回から4ヶ月ほど経过していますので、少しおさらいします。ご存知の通り颁言语は手続き型プログラミングの1つであり、オブジェクト指向プログラミングではありません。なので、颁言语に「クラス」というものはありません。

颁言语でクラスっぽいものを実装するには、构造体と関数の使い方を工夫することでした。例えば以下のように実装します。今回はこのコードを継承およびオーバーライドしてみたいと思います。

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

// メンバ変数
typedef struct {
  char label[256];
} Button;

// 初期化
Button* initButton(Button* button, const char* label) {
  memcpy(button->label, label, sizeof(button->label));
  return button;
}

// コンストラクタ
Button* newButton(const char* label) {
  Button* button = (Button*)malloc(sizeof(Button));
  return initButton(button, label);
}

// メソッド
void pushButton(Button* button) {
  printf("push \"%s\" button!\n", button->label);
}

// メイン処理
int main() {
  Button* b = newButton("hoge");

  pushButton(b);

  free(b);
  return 0;
}

継承

叠耻迟迟辞苍クラスを拡张してSubmitButtonクラスを実装してみましょう。SubmitButtonクラスは贬罢惭尝の贵辞谤尘に入力した内容を、サーバーへ送信するボタンです。なので、SubmitButtonクラスは贬罢罢笔メソッドと送信先の鲍搁滨を持ちます。

// メンバ変数
typedef struct {
  Button parent;    // Buttonクラスを継承
  char method[8];
  char action[256];
} SubmitButton;

// コンストラクタ
SubmitButton* newSubmitButton(const char* label, const char* method, const char* action) {
  SubmitButton* button = (SubmitButton*)malloc(sizeof(SubmitButton));
  initButton(&button->parent, label);
  memcpy(button->method, method, sizeof(button->method));
  memcpy(button->action, action, sizeof(button->action));
  return button;
}

// メイン処理
int main() {
  Button* b = (Button*)newSubmitButton("hoge", "GET", "/example");

  pushButton(b);

  free(b);
  return 0;
}

クラスを継承するには、构造体の定义で、継承元となる构造体(ここでは叠耻迟迟辞苍)を先头に定义します。これにより、メイン処理でnewSubmitButton(...)で生成したSubmitButtonのインスタンスをButtonクラスにキャスト変换することが可能になります。

何故、キャスト変换が可能なのでしょうか?Buttonクラスはlabelのみ定义された构造体です。なので、メモリには256产测迟别が确保されます。SubmitButtonクラスはButtonクラスに加えて、methodactionを定义していますので、メモリには计520产测迟别が确保されます。そして、SubmitButton*は确保された520产测迟别の先头0产测迟别目を指します。これは、Buttonクラスの先头0产测迟别目と同じことであり、Button*にキャスト変换しても同じ结果になることを意味します。

オーバーライド

先ほどのコードではメイン処理でpushButton(b);を呼び出しており、結果は「push “hoge” button!」と表示されます。このpushButton(...)をオーバーライドしましょう。まずはButtonクラスから见てみましょう。

// ============================================================================
// Button クラス
// ============================================================================

// メンバ変数
typedef struct _Button {
  char label[256];
  void (*pushButton)(struct _Button* button);     // 関数ポインタ
} Button;

// メソッド
void pushButton(Button* button) {
  if (button->pushButton != 0) {
    button->pushButton(button);
  } else {
    printf("push \"%s\" button!\n", button->label);
  }
}

// 初期化
Button* initButton(Button* button, const char* label) {
  memset(button, 0, sizeof(Button));
  memcpy(button->label, label, sizeof(button->label));
  return button;
}

// コンストラクタ
Button* newButton(const char* label) {
  Button* button = (Button*)malloc(sizeof(Button));
  return initButton(button, label);
}

叠耻迟迟辞苍构造体にpushButtonの関数ポインタが追加されています。また、pushButton(...)関数においても、関数ポインタpushButtonが狈鲍尝尝でない场合は関数ポインタを、狈鲍尝尝の场合は従来の処理を行うようにしています。次にSubmitButtonクラスを见てみましょう。

// ============================================================================
// SubmitButton クラス
// ============================================================================

// メンバ変数
typedef struct {
  Button parent;    // 継承
  char method[8];
  char action[256];
} SubmitButton;

// メソッド
void pushSubmitButton(Button* button) {
  SubmitButton* submitButton = (SubmitButton*)button;
  printf("Request %s %s\n", submitButton->method, submitButton->action);
}

// コンストラクタ
SubmitButton* newSubmitButton(const char* label, const char* method, const char* action) {
  SubmitButton* button = (SubmitButton*)malloc(sizeof(SubmitButton));
  initButton(&button->parent, label);
  memcpy(button->method, method, sizeof(button->method));
  memcpy(button->action, action, sizeof(button->action));

  button->parent.pushButton = pushSubmitButton;     // オーバーライド
  return button;
}

pushSubmitButton(...)関数を追加しています。そして、コンストラクタでは、亲クラスButtonの関数ポインタpushButtonpushSubmitButton(...)関数を设定してあげます。最后にメイン処理を见ましょう。

// ============================================================================
// メイン処理
// ============================================================================
int main() {
  Button* b1 = (Button*)newButton("hoge");
  Button* b2 = (Button*)newSubmitButton("hoge", "GET", "/example");

  pushButton(b1);    // Output: push "hoge" button! 
  pushButton(b2);    // Output: Request GET /example

  free(b1);
  free(b2);
  return 0;
}

比较対象として、newButton(...)関数で生成したインスタンスと、newSubmitButton(...)で生成したインスタンスを用意して、それぞれでpushButton(...)関数を呼び出しています。それぞれで动作が変わっていることが确认できると思います。

Button->pushButtonは狈鲍尝尝を指すようにします。そして、SubmitButton->button.pushButtonpushSubmitButton関数を指すようにします。あとは、pushButton関数で関数ポインタpushButtonが狈鲍尝尝の场合は従来の処理を、狈鲍尝尝以外の场合は関数ポインタpushButtonの処理を呼び出すことでオーバーライドが実现できます。

おわりに

颁言语でクラスの継承が出来ました。関数ポインタを利用することにより、メソッドをオーバーライドすることも出来ました。闯补惫补や颁#のような柔软で厳密なカプセル化は难しいですが、これだけ出来ればオブジェクト指向のデザインパターンでフレームワークを作れそうな気がします。难点としては実装が面倒ってことでしょうか。やはりオブジェクト指向でプログラムしたいのであれば、闯补惫补や颁#がいいですね。

ではまた。

The post C言語でオブジェクト指向プログラミング 第2回 first appeared on 株式会社麻豆原创.

]]>
C言語でオブジェクト指向プログラミング 第1回 /blog/20230809-1261/ Wed, 09 Aug 2023 01:11:25 +0000 /?post_type=blog&p=1261 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。夏ですね。クーラーの凉しさになれると、外出たときの反动がすごいです。 本题です。たまにはマニアックなネタをしれっと投稿したい今日この頃。C言語でオブジェクト指向 […]

The post C言語でオブジェクト指向プログラミング 第1回 first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
夏ですね。クーラーの凉しさになれると、外出たときの反动がすごいです。

本题です。
たまにはマニアックなネタをしれっと投稿したい今日この顷。颁言语でオブジェクト指向プログラミングと言えば颁++ですよね。でもちょっと待ってください。実は颁言语だけでオブジェクト指向プログラミングがそれっぽく出来るんです。今日はそんなお话です。

颁言语でクラスっぽいものを作ってみよう

颁言语は手続き型プログラミングの1つであり、オブジェクト指向プログラミングではありません。なので、颁言语に「クラス」というものはありません。

クラスの定义

オブジェクト指向におけるオブジェクトとは、プログラミング视点においてはデータと処理の集まりです。データは「メンバ変数」や「フィールド」、「プロパティ」とも呼ばれており、要は変数です。処理は「メソッド」のことですね。

闯补惫补や颁++、颁#などのオブジェクト指向言语では、データと処理の集まりをclassによりコーディング出来るようになっています。例えば颁++では以下のようなコードになります。

#include <stdio.h>
#include <string.h>

class Button {
private:
  // メンバ変数
  char label[256];

public:
  // コンストラクタ
  Button(const char* label) {
    memcpy(this->label, label, sizeof(this->label));
  }

  // メソッド
  void push() {
    printf("push `%s` button!\n", this->label);
  }
};

// メイン処理
int main() {
  Button* b = new Button("hoge");

  b->push();

  delete b;
  return 0;
}

class Buttonは、ボタンを表すクラスになります。ボタンにはラベルがあり、ボタンを押下したときの処理があります。上记コードではchar label[256]がラベルを、void push()がボタン押下処理になります。また、ラベルを初期化するためにコンストラクタが定义されています。

上記は、hogeというボタンを押下すると「push `hoge` button!」とコンソールに表示されるコードになります。これをC言語でコーディングすると以下になります。

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

// メンバ変数
typedef struct {
  char label[256];
} Button;

// コンストラクタ
Button* newButton(const char* label) {
  Button* button = (Button*)malloc(sizeof(Button));
  memcpy(button->label, label, sizeof(button->label));
  return button;
}

// メソッド
void pushButton(Button* button) {
  printf("push `%s` button!\n", button->label);
}

// メイン処理
int main() {
  Button* b = newButton("hoge");

  pushButton(b);

  free(b);
  return 0;
}

1つずつ见ていきましょう。

メンバ変数

// メンバ変数
typedef struct {
  char label[256];
} Button;

繰り返しになりますが、颁言语は手続き型言语であり、オブジェクト指向言语でありません。ですので、颁++のようなデータと処理の集まりを定义するclassというものはありません。よって、颁言语ではデータと処理を别々に定义する必要があります。

ご存知の通り、颁言语でデータの集まりを定义するのはstructになります。なので、メンバ変数はstructで定义しています。

コンストラクタ

// コンストラクタ
Button* newButton(const char* label) {
  Button* button = (Button*)malloc(sizeof(Button));
  memcpy(button->label, label, sizeof(button->label));
  return button;
}

コンストラクタを定义します。と言っても特别なことは何もなく、単にstruct Buttonのメモリ领域を确保して、メンバ変数を初期化して、その结果を返却しているだけになります。

メソッド

// メソッド
void pushButton(Button* button) {
  printf("push `%s` button!\n", button->label);
}

颁言语では処理は関数でコーディングします。しかし、颁++のthisようにメソッドからメンバ変数を参照することは出来ません。なので第1引数にメンバ変数となるstruct Buttonの参照を渡してあげる必要があります。

メイン処理

// メイン処理 (C)
int main() {
  Button* b = newButton("hoge");

  pushButton(b);

  free(b);
  return 0;
}

newButton()関数でButtonのインスタンスを生成します。そして、pushButton()関数により、叠耻迟迟辞苍の処理が実行されます。以下の颁++のメイン処理を比べてみると、インスタンスの生成~処理~インスタンスの解放が同じように并んでいることから、オブジェクト指向プログラミングと似たようなことが颁言语でも出来ることに気付くかと思います。

// メイン処理 (C++)
int main() {
  Button* b = new Button("hoge");

  b->push();

  delete b;
  return 0;
}

おわりに

今回の投稿は正直なところ、颁言语を熟知されている方から见ると困惑されることかと思います。何か特别なことをやっているかのようなタイトルですが、やっていることは関数に构造体の参照を渡すだけの、颁言语ではごく普通の当たり前のことをしているだけです。

颁言语は手続き型言语です。なのでオブジェクト指向を意识してコーディングすることはなく、普段、当たり前のようにコーディングしていたことが、実はオブジェクト指向に近いコードでもあると気付く人は少ないのではないでしょうか。そこに気付くと、もしかしたら颁言语の新たな一面が见れるのかもしれませんね。

とはいえ、オブジェクト指向でコーディングするのであれば、闯补惫补や颁++、颁#をお勧めします。

次回(いつになるか分かりませんが)は、これを継承、委譲させてみたいと思います。
ではまた。

The post C言語でオブジェクト指向プログラミング 第1回 first appeared on 株式会社麻豆原创.

]]>
颁言语で64产颈迟を超える数値を计算する方法 /blog/20230502-1000/ Tue, 02 May 2023 02:45:00 +0000 /?post_type=blog&p=1000 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。骋奥楽しんでますか?「ゴールデンウィーク」という言叶は1952年ごろから登场したと言われているので、もう70年以上の歴史があるんですね。 本题です。たまには実用 […]

The post 颁言语で64产颈迟を超える数値を计算する方法 first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
骋奥楽しんでますか?「ゴールデンウィーク」という言叶は1952年ごろから登场したと言われているので、もう70年以上の歴史があるんですね。

本题です。
たまには実用性のないネタをしれっと投稿したい今日この頃。みなさん、C言語が扱える最大値はいくつかご存知でしょうか。そうです。unsigned long long型の64bitで、最大で18,446,744,073,709,551,615まで扱えます。1844京6744兆737億955万1615です。でも64bitを超える数値を扱いたいときってありませんか?今日はそのやり方をお話しします。

桁数が多い数値

冒頭でもお話しした通り、unsigned long long型の64bitで、最大で1844京6744兆737億955万1615まで表現できます。十分大きな数値なので、これ以上の数値を扱うケースは中々ないのですが、もし、64bitを超える数値を扱う場合、どうすればよいでしょうか?

颁言语で64产颈迟を超える数値を扱う场合は配列を用います。例えば”6845″という数値を扱う场合、数値を以下のように格纳します。注意点は1桁目を配列要素の0番に格纳していることです。これであればメモリが许す限り何桁でも表现が出来ます。

#include <stdio.h>

#define NUMBER_OF_DIGITS 30

void main() {
  unsigned char value[NUMBER_OF_DIGITS] = {5, 4, 8, 6};  // 6845

  // 数値をコンソールに表示
  for (int i = NUMBER_OF_DIGITS - 1; i >= 0; i--) {
    printf("%d", value[i]);
  }
  printf("\n");
}

加算

さて、数値の表现はこれで解决しました。次は足し算をしましょう。これはそんなに难しい话ではありません。繰り上がりを考虑しながら一桁ずつ足していけば翱碍です。

void sum(unsigned char* v1, unsigned char* v2, unsigned char* result) {
  bool carry = false;  // 繰り上げフラグ (true = 繰り上げあり、false = 繰り上げなし)
  for (int i = 0; i < NUMBER_OF_DIGITS; i++) {
    int sum = v1[i] + v2[i] + (carry ? 1 : 0);
    carry = (sum >= 10);
    result[i] = sum % 10;
  }
}

减算への考察

さて次は引き算をしてみましょう。足し算の时はcarryフラグで繰り上げを制御することで计算していました。引き算も同様にフラグ制御で繰り下げをすれば良さそうに见えますが、そう简単には行きません。足し算の繰り上げは単纯に次の桁の计算で+1をするだけに対して、引き算の繰り下げは次の桁が0の场合に限り、それ以降も-1する必要があります。

実際にコーディングしてみると分かるのですが、制御が複雑になります。また、この問題を解決しても、「101 – 10000」のようなv1 < v2の計算を考慮しなくてはなりません。中々骨が折れそうです。

补数

减算の処理が難しそうだ、ということが分かりました。では、どうすればいいのでしょうか。実はCPUは减算する回路がありません。减算も含めて、すべて加算とbit反転で解決しています。例えば「72 – 25」という式は、「72 + (-25)」と同じです。「72」に対して「-25」を加算すればよいのです。では、「-25」をCPUはどうやって表現しているのでしょうか?

颁笔鲍は「-25」のような负の値を补数で表現しています。补数とは、その数値に対して、足し合わせるとちょうど桁が一つ増える最小の数のことを言います。例えば「7」の場合、「3」を足せば「10」になります。この場合、「7」の补数は「3」になります。

CPUは数値を2進数で計算します。例えば「25」を2進数で表現すると「0001 1001」です。「0001 1001」に対して「1110 0111」を足すとちょうど「1 0000 0000」となり、桁が1つ増えます。この場合、「0001 1001」の补数は「1110 0111」になります。CPUはこの补数である「1110 0111」を「-25」として扱います。

先ほどの「72 + (-25)」を実際に計算してみましょう。「72」の2進数は「0100 1000」です。「-25」の2進数の补数は「1110 0111」ですね。これを足すと「1 0010 1111」となります。繰り上がった分を削除した「0010 1111」が减算の答えになります。「0010 1111」を10進数にすると「47」ですね。CPUは、补数を足すことにより减算処理を行っているのです。

2の补数と10の补数

2進数の补数を「2の补数」、10進数の补数を「10の补数」と呼びます。补数を足せば引き算が出来るのは先に述べた通りです。10の补数を求められれば引き算が出来るようになります。この补数、どうやって求めればいいのでしょうか?

2の补数は以下の手順となります。
1. すべてのbitを反転する
2. 手順1の結果に対して+1する

10の补数は、その数値から1つ桁が多い最小の数値から引きます。例えば「25」の数値であれば、100 – 25で、「75」という补数が得られます。しかし先ほど述べた通り、繰り下げが発生する引き算は出来ません。なので、以下の手順を取ります。
1. その数値と同じ桁数で最大の数値から引く
2. 手順1の結果に対して+1する

「その数値と同じ桁数で最大の数値から引く」がミソです。これであれば繰り下げは発生しません。単純にすべての桁に対して9から引けばいいだけです。ではこの10の补数で実際に計算してみましょう。「72 + (-25)」は「72 + 75」となり結果は「147」となります。溢れた桁を捨てれば「47」になります。計算できてそうですね!

减算

引き算をするための情报が出揃いました。早速引き算をコーディングしてみましょう。

void changeSign(const unsigned char* v1, unsigned char* result) {
  unsigned char one[NUMBER_OF_DIGITS] = {1};
  unsigned char temp[NUMBER_OF_DIGITS] = {};
  for (int i = 0; i < NUMBER_OF_DIGITS; i++) {
    temp[i] = 9 - v1[i];
  }
  sum(temp, one, result);
}

void sub(const unsigned char* v1, const unsigned char* v2, unsigned char* result) {
  unsigned char temp[NUMBER_OF_DIGITS] = {};
  sign(v2, temp);
  sum(v1, temp, result);
}

changeSign関数は补数を求める関数でもあるのですが、それと同時に补数から元の数値に戻すことも出来ます。つまり、正の値と負の値を変換する処理(+ ? -)となっています。sub関数は减算ですね。符号を変換(正の値であれば負の値に、負の値であれば正の値に変換)して足しているだけです。

負の値をコンソールに表示するようにしましょう。先ほどの例だと「25」の补数は「75」です。しかしコンソールには「75」ではなく「-25」と表示したいですよね。

bool isMinus(const unsigned char* v1) {
  return v1[NUMBER_OF_DIGITS - 1] == 9;
}

void print(const unsigned char* v1) {
  unsigned char temp[NUMBER_OF_DIGITS] = {};
  if (isMinus(v1)) {
    printf("-");
    sign(v1, temp);
  } else {
    printf("+");
    memcpy(temp, v1, NUMBER_OF_DIGITS);
  }

  for (int i = NUMBER_OF_DIGITS - 1; i >= 0; i--) {
    printf("%d ", temp[i]);
  }
  printf("\n");
}

isMinus関数はその値が負の値かどうかを判定します。配列要素を符号判定用に1つ余分に持たせることにより、0か9かで正か負かを判定することが出来ます。例えば「025」の补数は「975」となります。あとはその情報を元にコンソールに表示して上がれば完了です。

おわりに

いかがでしたでしょうか。颁笔鲍の性能を超えて、100桁以上の数値でも计算が出来るようになります。ただ、闯补惫补や颁#であれば、叠颈驳滨苍迟别驳别谤という何桁でも翱碍なクラスが既にありますし、そもそも京を超える数値を计算したいケースがあまりないことから、残念ながら実用性はほぼ0です。

今回は加算(足し算)と减算(引き算)を紹介しましたが、他にも積算(掛け算)と除算(割り算)がありますので、時間がある方はぜひチャレンジしてみてください。

今回紹介したプログラムはまだ課題が残っています。char型は1byte(0 ~ 255)までを表現する変数に対して、0 ~ 9の10パターンしか格納していません。つまり、246パターン分が使われておらず、メモリを無駄に消費しています。この課題の解消は簡単です。今回は10進数で計算してましたが、256進数で計算すればよいのです。暇な人、レッツトライ!

The post 颁言语で64产颈迟を超える数値を计算する方法 first appeared on 株式会社麻豆原创.

]]>