cocos2d-xのspine Jsonをint対応にする

cocos2d-xの標準JSONパーサーといえば spine Jsonだけど、spine Jsonでは、数値データはすべてfloatで扱われる。

現在製作中のアプリではデータをJSON形式で保存しているのだけど、日時のデータをJSON形式でファイルに保存し、次に読み込むと、どうも日時がずれていることに気がついた。
日時のデータは time_t 形式の値で、JSON上ではintとして扱っている。
ところが、一旦保存して次にこれを読み込む際には、spine Jsonはこの値をfloat値としてパースする。
調べてみたところ、このとき僅かではあるが、JSON文字列に記述された値とfloatにパースされた値との間に誤差が生じていることが分かった。

float計算には誤差がつきものと言うが、どうやら、大きな整数値をfloat計算でパースしようとすると、この誤差が無視できなくなってしまうようだ。

そこで、整数値は整数値としてパースするように spine Json を修正することにした。
まずは、spineのJson.hのstruct Jsonに、値が整数値かfloat値かを示すフラグ変数を新たに追加する。

/* The Json structure: */
typedef struct Json {
    struct Json* next;
    struct Json* prev;
    struct Json* child;

    int type;
    const char* valuestring;
    int valueint;
    float valuefloat;
    bool isInteger;  // 整数値かfloat値かを判別するためのフラグ
    
    const char* name;
} Json;

整数値かfloat値かを示すフラグ変数を導入する代わりに、 Json_NumberをJson_IntとJson_Floatに分けるやりかたもあるかと思うが、このやり方のほうが他のコードへの影響が小さくて良いのではないかと思う。

次に、Json.cppの static const char* parse_number (Json *item, const char* num) を次のように修正する。

/* Parse the input text to generate a number, and populate the result into item. */
static const char* parse_number (Json *item, const char* num) {
     // 途中の計算はすべてintで行う。
    int n = 0, sign = 1, scale = 0, subscale = 0, signsubscale = 1;
    
    if (*num == '-') sign = -1, num++;
    if (*num == '0') num++;
    if (*num >= '1' && *num <= '9') do
        n = (n * 10) + (*num++ - '0');
    while (*num >= '0' && *num <= '9');
    if (*num == '.' && num[1] >= '0' && num[1] <= '9') {
        num++;
        do
            n = (n * 10) + (*num++ - '0'), scale--;
            while (*num >= '0' && *num <= '9');
    }
    if (*num == 'e' || *num == 'E')
    {
        num++;
        if (*num == '+')
            num++;
            else if (*num == '-') signsubscale = -1, num++;
            while (*num >= '0' && *num <= '9')
                subscale = (subscale * 10) + (*num++ - '0');
    }
    
    int s = scale + subscale * signsubscale;
    if (s == 0) {
        // 整数
        n = sign * n;
        item->valuefloat = (float)n;
        item->valueint = n;
        item->isInteger = true;
    }
    else if (s > 0) {
        // 10のs乗の整数
        n = sign * n * (int)pow(10.0, s);
        item->valuefloat = (float)n;
        item->valueint = n;
        item->isInteger = true;
    }
    else {
        // sが負数なので、小数
        float n2 = (float)sign * (float)n * (float)pow(10.0f, s);
        item->valuefloat = n2;
        item->valueint = (int)n2;
        item->isInteger = false;
    }
    item->type = Json_Number;
    return num;
}

これであとは、たとえばこんなふうに使えばいい。

CCObject *CCJson::parseValue(Json *pJsonValue) {
    if (pJsonValue->type == Json_Number) {
        if (pJsonValue->isInteger) {
            // 整数値
            return CCInteger::create(pJsonValue->valueint);
        }
        else {
            // float値
            return CCFloat::create(pJsonValue->valuefloat);
        }
    }
}

コメントを残す