昨日の続きで範囲比較の実装を調べる

(2008-06-07 13:55 ソースを誤読していたため論旨も修正しました。id:bawsin君 指摘Thx. )

・疑問:境界値を含めるか否か?
 →パッケージによってバラバラ。複数のフレームワークを使う場合、各フレームワークごとでの挙動の違いを確認しておく必要があるだろう。
 →「min <= val <= max」と上限、下限とも境界を含んだ範囲が有効とする実装が多い。自分で1から作る機会があるなら、既存にあわせて境界値も含めたほうがベターだろう。

Googleコードサーチでとりあえず「RangeValidator」あたりで検索し、先頭からいくつか実装を見てみたところ、'<='だったり'<'だったりで統一性がなかった。「min < n < max」、「min <= n < max」、「min <= n <= max」てなかんじで、上限も下限もバラバラ。

Googleコードサーチでとりあえず「RangeValidator」あたりで検索し、先頭からいくつか実装を見てみたところ、有効範囲指定のロジックは下記のようになっていた。



http://www.google.com/codesearch?hl=ja&q=+RangeValidator+show:V7ASMnHaCLE:5j_S7paHCjI:V7ASMnHaCLE&sa=N&cd=2&ct=rc&cs_p=http://polarrose-wsf.googlecode.com/svn&cs_f=src/main/java/com/polarrose/wsf/validator/RangeValidator.java

  if (minValue != null) {
      if (value.compareTo(minValue) < 0) {
        // 引用註:最小値よりも小さければ下限エラー
        return "Value must be > " + minValue.toString();
      }
    }
    if (maxValue != null) {
      if (value.compareTo(maxValue) > 0) {
        // 引用註:最大値よりも大きければ上限エラー
        return "Value must be < " + maxValue.toString();
      }
    }
    return null;

// 引用註:有効範囲は 最小値 <= 値 <= 最大値

(2008-06-07 12:09 引用箇所修正)


http://www.google.com/codesearch?hl=ja&q=+RangeValidator+show:Srysnq-Q3XA:Pge64koJE7A:AVASoBvyph8&sa=N&cd=7&ct=rc&cs_p=http://prdownloads.sourceforge.net/hibernate/hibernate-annotations-3.1.0.Beta10.zip&cs_f=hibernate-annotations-3.1.0.Beta10/src/org/hibernate/validator/RangeValidator.java

public boolean isValid(Object value) {
  if ( value == null ) return true;
  if ( value instanceof String ) {
    try {
      double dv = Double.parseDouble( (String) value );
      return dv >= min && dv <= max;
    }
    catch (NumberFormatException nfe) {
      return false;
    }
  }
  else if ( ( value instanceof Double ) || ( value instanceof Float ) ) {
    double dv = ( (Number) value ).doubleValue();
    return dv >= min && dv <= max;
  }
  else if ( value instanceof Number ) {
    long lv = ( (Number) value ).longValue();
    return lv >= min && lv <= max;
  }
  else {
    return false;
  }
}
// 引用註: 有効範囲は 下限値 <= 値 <= 上限値

(2008-06-07 12:26 引用箇所修正)


Seasar-Cubby 1.0.3 から抜粋
http://cubby.seasar.org/download.html

public void validate(final ValidationContext context, final Object value) {
  if (value instanceof String) {
    final String str = (String) value;
    if (StringUtil.isEmpty(str)) {
      return;
    }
    try {
      final long longValue = Long.parseLong(str);
      if (longValue >= min && longValue <= max) {
        return;
      }
    } catch (final NumberFormatException e) {
    }
  } else if (value == null) {
    return;
  }
  context.addMessageInfo(this.messageHelper.createMessageInfo(min, max));
}

// 引用註: 有効範囲は 下限値 <= 値 <= 上限値

(2008-06-07 13:08 引用箇所修正)


http://www.google.com/codesearch?hl=ja&q=+LongRangeValidator+show:_ptXRA0-NN0:QkWL91l5tMo:jsI_gZsNHd4&sa=N&cd=1&ct=rc&cs_p=http://archive.apache.org/dist/myfaces/myfaces-core-current-src.tar.gz&cs_f=myfaces-core-1.1.4/source/javax/faces/validator/LongRangeValidator.java

// VALIDATE
public void validate(FacesContext facesContext,
           UIComponent uiComponent,
           Object value)
    throws ValidatorException
{

  // 引用註:前処理省略...

  double dvalue = parseLongValue(facesContext, uiComponent,value);
  if (_minimum != null && _maximum != null)
  {
    if (dvalue < _minimum.longValue() ||
      dvalue > _maximum.longValue())
    {
      // 引用註: 下限値より小さいか、または上限値より大きい場合エラー
      Object[] args = {_minimum, _maximum,uiComponent.getId()};
      throw new ValidatorException(_MessageUtils.getErrorMessage(facesContext, NOT_IN_RANGE_MESSAGE_ID, args));
    }
  }
  else if (_minimum != null)
  {
    if (dvalue < _minimum.longValue())
    {
      // 引用註: 下限値より小さい場合エラー
      Object[] args = {_minimum,uiComponent.getId()};
      throw new ValidatorException(_MessageUtils.getErrorMessage(facesContext, MINIMUM_MESSAGE_ID, args));
    }
  }
  else if (_maximum != null)
  {
    if (dvalue > _maximum.longValue())
    {
      // 引用註: 上限値より大きい場合エラー
      Object[] args = {_maximum,uiComponent.getId()};
      throw new ValidatorException(_MessageUtils.getErrorMessage(facesContext, MAXIMUM_MESSAGE_ID, args));
    }
  }
}

// 引用註;有効範囲は 下限値 <= 値 <= 上限値

(2008-06-07 13:38 引用箇所修正)




ほとんどのパッケージで境界値の取扱いについて明確にドキュメント化されていない(と感じた)もんだから、デファクトスタンダードがあるからドキュメント化していないのかとか想定してました。が、別にそんなわけでもないようで。大抵は単体テストをサボらなければ発見できるでしょうが、フレームワークが提供してるものを使うときは気をつけたほうが良さそうです。 どうやら「min <= val <= max (下限値以上、上限値以下)」とし、境界値を含んだ実装をしているところが多いようです。自分で実装する機会があるならこれに倣ったほうがいいでしょう。

となると「min < n < max」等の境界値を含まない範囲チェックをしたい場合は、「min+1 <= n <= max-1」みたいに呼び出し側が下限値と上限値を調整しないといけなくなるか。やはり整数以外の場合だと厳密じゃなくなってしまうような。



ちなみに以下のPythonのパッケージの例では「min <= val <= max」でない模様。
http://www.google.com/codesearch?hl=ja&q=+RangeValidator+show:qH_XGY_cntI:-2FpNtTvwKE:ekpAQaY2G4M&sa=N&cd=1&ct=rc&cs_p=http://gentoo.osuosl.org/distfiles/Plone-2.1.4.tar.gz&cs_f=Plone-2.1.4/validation/validators/RangeValidator.py

if minval <= nval < maxval:
  return 1

戻り値=1が「有効範囲内」を表す値なのかまではちょっと確信持てませんが、どちらにしろ下限と上限のいずれかは境界値を含まない実装ですね。
このPythonのパッケージのように「min <= n <= max」でない例がある以上、既存のものを使うときはやっぱり境界値の挙動は確認は一度しておくべきでしょうね。