'Classes/ActiveBasic/Strings/SPrintF.ab Namespace ActiveBasic Namespace Strings Namespace Detail /*! @brief 浮動小数点数を文字列化する低水準な関数。符号、指数、仮数に分けて出力。 @author Egtra @date 2007/09/18 @param[in] x 文字列化する浮動小数点数 @param[out] e 指数 @param[out] sign 符号 @return 仮数 仮数は1の位から下へ17桁で、小数点を含まない。そのため、誤差を無視すればVal(仮数) * 10 ^ (e - 17) = Abs(x)が成り立つ。 xに無限大、非数を渡した場合の動作は未定義。 */ Function FloatToChars(x As Double, ByRef e As Long, ByRef sign As Boolean) As String Imports System '0を弾く If x = 0 Then If GetQWord(VarPtr(x) As *QWord) And &h8000000000000000 Then sign = True Else sign = False End If e = 0 FloatToChars = "00000000000000000" Exit Function End If '符号の判断(同時に符号を取り除く) If x < 0 Then sign = True x = -x Else sign = False End If '1e16 <= x < 1e17へ正規化 '(元のx) = (正規化後のx) ^ (d - 17)である。 Dim d = Math.Floor(Math.Log10(x)) As Long If d < 16 Then x *= ipow(10, +17 - d) ElseIf d > 16 Then x /= ipow(10, -17 + d) End If '補正 While x < 1e16 x *= 10 d-- Wend While x >= 1e17 x /= 10 d++ Wend d-- e = d FloatToChars = FormatIntegerLU((x As Int64) As QWord, 17, 0, None) End Function /*! @brief 書式化関数群で使用するフラグ。 @author Egtra @date 2007/09/18 */ Const Enum FormatFlags '! 何も指定がない。 None = &h0 /*! 符号、+。符号付変換[diAaEeFfGg]のとき、正の値でも符号を付ける。 AdjustFieldWidthの仕様から、Format関数郡内からAdjustFieldWidthにかけて、 単に数値が符号付である(負の値である)ことを示す意味でも用いられる。 */ Sign = &h1 /*! 空白、空白文字。 符号付変換[diAaEeFfGg]のとき、正の値ならば符号分の空白を開ける。Signが立っているときには無視される。 */ Blank = &h2 /*! ゼロ、0。 [diouXxAaEeFfGg]で、フィールドの空きを0で埋める。leftが立っているときには無視される。 */ Zero = &h4 '! 左揃え、-。フィールド内で左揃えにする。 Left = &h8 /*! 代替表記、#。
  • [AaEeFfGg]では、精度0でも小数点を付ける。
  • [Gg]では、それに加え、小数部末尾の0の省略を行わないようにする。 */ Alt = &h10 '! 大文字。使用するアルファベットを大文字にする。[aefgx]を[AEFGX]化する。 Cap = &h20 /*! 内部処理用に予約。 @note Minusとして使用されている。 */ Reserved = &h80000000 End Enum /*! @brief 浮動小数点数をprintfの%e, %E(指数形式、十進法)相当の変換で文字列化する関数。 @author Egtra @date 2007/09/18 @param[in] x 文字列化する浮動小数点数値。 @param[in] d 精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。 @param[in] field フィールド幅。 @param[in] flags 書式フラグ。 @return xの文字列表現 @todo 他の実装での末尾桁の扱いを調べる(このコードでは何もしていないので切捨となっている)。 */ Function FormatFloatE(x As Double, d As DWord, field As DWord, flags As FormatFlags) As String If d = DWORD_MAX Then d = 6 End If Dim e As Long, negative As Boolean Dim s = FloatToChars(x, e, negative) Dim sb = New System.Text.StringBuilder With sb AppendSign(sb, negative, flags) .Append(s[0]) If (flags And Alt) Or d > 0 Then .Append(".") Dim outputLen = s.Length - 1 If outputLen >= d Then .Append(s, 1, d) Else 'sで用意された桁が指定された精度より少ないとき .Append(s, 1, outputLen) .Append(&h30 As StrChar, d - outputLen) '足りない桁は0埋め End If End If If flags And Cap Then .Append("E") Else .Append("e") End If .Append(FormatIntegerD(e, 2, 0, Sign Or Zero)) AdjustFieldWidth(sb, field, flags) End With FormatFloatE = sb.ToString() End Function /*! @brief 浮動小数点数をprintfの%f(小数形式、十進法)相当の変換で文字列化する関数。 @author Egtra @date 2007/10/23 @param[in] x 文字列化する浮動小数点数値。 @param[in] precision 精度。小数点以下の桁数。DWORD_MAXのとき、指定なしとして既定値6となる。 @param[in] field フィールド幅。 @param[in] flags 書式フラグ。 @return xの文字列表現 */ Function FormatFloatF(x As Double, precision As DWord, field As DWord, flags As FormatFlags) As String If precision = DWORD_MAX Then precision = 6 End If Dim e As Long, negative As Boolean Dim s = FloatToChars(x, e, negative) Dim sb = New System.Text.StringBuilder With sb AppendSign(sb, negative, flags) Dim intPartLen = e + 1 Dim outputDigit = 0 As DWord If intPartLen >= 17 Then '有効桁が全て整数部に収まる場合 .Append(s) .Append(&h30 As StrChar, intPartLen - 17) outputDigit = 17 ElseIf intPartLen > 0 Then '有効桁の一部が整数部にかかる場合 .Append(s, 0, intPartLen) outputDigit = intPartLen Else '有効桁が全く整数部にかからない場合 .Append(&h30 As StrChar) End If If precision > 0 Or (flags And Alt) Then .Append(".") Dim lastDigit = s.Length - outputDigit If lastDigit >= precision Then '変換して得られた文字列の桁数が精度以上ある場合 Dim zeroDigit = 0 If intPartLen < 0 Then '1.23e-4 = 0.000123のように指数が負のため小数点以下に0が続く場合 zeroDigit = System.Math.Min(-intPartLen As DWord, precision) .Append(&h30 As StrChar, zeroDigit As Long) End If .Append(s, outputDigit, (precision - zeroDigit) As Long) Else .Append(s, outputDigit, lastDigit) .Append(&h30 As StrChar, (precision - lastDigit) As Long) '残りの桁は0埋め End If End If AdjustFieldWidth(sb, field, flags) End With FormatFloatF = sb.ToString() End Function /*! @brief 先頭に符号もしくはその分の空白を出力する。FormatFloat用。 @author Egtra @date 2007/10/23 @param[in, out] sb 出力先 @param[in] negative 符号 @param[in, out] flags フラグ。negative = Trueなら、Signを立てて返す。 */ Sub AppendSign(sb As System.Text.StringBuilder, negative As Boolean, ByRef flags As FormatFlags) With sb If negative Then .Append("-") flags Or= Sign Else If flags And Sign Then .Append("+") ElseIf flags And Blank Then .Append(" ") End If End If End With End Sub /*! @brief DWordの最大値4294967295の文字数 - 1。FormatIntegerU内で使用。 @author Egtra @date 2007/09/18 */ Const MaxSizeU = 9 /*! @brief 符号無し整数をprintfの%u(十進法表現)相当の変換で文字列化する関数。 @author Egtra @date 2007/09/18 @param[in] x 文字列化する整数値。 @param[in] d 精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。 @param[in] field フィールド幅。 @param[in] flags 書式フラグ。 @return xの文字列表現 */ Function FormatIntegerU(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String Return FormatIntegerEx(TraitsIntegerDU, x, d, field, flags And (Not (Sign Or Blank))) End Function /*! @brief FormatIntegerUのQWord版 @author Egtra @date 2007/10/26 */ Function FormatIntegerLU(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String Return FormatIntegerEx(TraitsIntegerLDU, x, d, field, flags And (Not (Sign Or Blank))) End Function /*! @brief 符号有り整数をprintfの%d(十進法表現)相当の変換で文字列化する関数。 @author Egtra @date 2007/10/13 @param[in] x 文字列化する整数値。 @param[in] d 精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。 @param[in] field フィールド幅。 @param[in] flags 書式フラグ。 @return xの文字列表現 */ Function FormatIntegerD(x As Long, d As DWord, field As DWord, flags As FormatFlags) As String Dim unsignedX As DWord If x < 0 Then unsignedX = (-x) As DWord flags Or= Minus Else unsignedX = x As DWord End If Return FormatIntegerEx(TraitsIntegerDU, unsignedX, d, field, flags) End Function /*! @brief FormatIntegerDのInt64版 @author Egtra @date 2007/10/26 */ Function FormatIntegerLD(x As Int64, d As DWord, field As DWord, flags As FormatFlags) As String Dim unsignedX As QWord If x < 0 Then unsignedX = (-x) As QWord flags Or= Minus Else unsignedX = x As QWord End If Return FormatIntegerEx(TraitsIntegerLDU, unsignedX, d, field, flags) End Function /*! @author Egtra @date 2007/10/26 */ Dim TraitsIntegerDU As IntegerConvertTraits With TraitsIntegerDU .Convert = AddressOf(IntegerDU_Convert) .Prefix = AddressOf(IntegerDU_Prefix) .MaxSize = MaxSizeU End With /*! @author Egtra @date 2007/10/26 */ Dim TraitsIntegerLDU As IntegerConvertTraits With TraitsIntegerLDU .Convert = AddressOf(IntegerLDU_Convert) .Prefix = AddressOf(IntegerDU_Prefix) .MaxSize = MaxSizeLU End With /*! @brief 負数を表すフラグ。FormatIntegerD, LDからIntegerDU_Prefixまでの内部処理用。 @author Egtra @date 2007/10/26 */ Const Minus = Reserved /*! @author Egtra @date 2007/10/26 */ Function IntegerDU_Convert(buf As *StrChar, xq As QWord, flags As FormatFlags) As DWord Dim x = xq As DWord Dim i = MaxSizeU While x <> 0 buf[i] = (x As Int64 Mod 10 + &h30) As StrChar 'Int64への型変換は#117対策 x \= 10 i-- Wend Return i End Function /*! @brief IntegerDU_ConvertのQWord版 @author Egtra @date 2007/10/26 @bug #117のため、現在Int64の最大値を超える値を正しく処理できない。 */ Function IntegerLDU_Convert(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord Dim i = MaxSizeLU While x <> 0 buf[i] = (x As Int64 Mod 10 + &h30) As StrChar 'Int64への型変換は#117対策 x \= 10 i-- Wend Return i End Function /*! @author Egtra @date 2007/10/26 */ Function IntegerDU_Prefix(x As QWord, flags As FormatFlags) As String If flags And Minus Then IntegerDU_Prefix = "-" ElseIf flags And Sign Then IntegerDU_Prefix = "+" ElseIf flags And Blank Then IntegerDU_Prefix = " " End If End Function /*! @brief DWordの最大値の八進法表現37777777777の文字数 - 1 + 1。IntegerO_Convert内で使用。 @author Egtra @date 2007/10/19 上の式で1を加えているのは、八進接頭辞の分。 */ Const MaxSizeO = 11 /*! @brief QWordの最大値の八進法表現1777777777777777777777の文字数 - 1 + 1。IntegerO_Convert内で使用。 @author Egtra @date 2007/10/26 上の式で1を加えているのは、八進接頭辞の分。 */ Const MaxSizeLO = 22 /*! @author Egtra @date 2007/10/22 */ Dim TraitsIntegerO As IntegerConvertTraits With TraitsIntegerO .Convert = AddressOf(IntegerO_Convert) .Prefix = AddressOf(IntegerO_Prefix) .MaxSize = MaxSizeO End With /*! @author Egtra @date 2007/10/26 */ Dim TraitsIntegerLO As IntegerConvertTraits With TraitsIntegerLO .Convert = AddressOf(IntegerLO_Convert) .Prefix = AddressOf(IntegerO_Prefix) .MaxSize = MaxSizeLO End With /*! @brief 符号無し整数をprintfの%o(八進法表現)相当の変換で文字列化する関数。 @author Egtra @date 2007/10/19 @param[in] x 文字列化する整数値。 @param[in] d 精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。 @param[in] field フィールド幅。 @param[in] flags 書式フラグ。 @return xの文字列表現 */ Function FormatIntegerO(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String Return FormatIntegerEx(TraitsIntegerO, x, d, field, flags) End Function /*! @brief FormatIntegerOのQWord版。 @author Egtra @date 2007/10/26 */ Function FormatIntegerLO(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String Return FormatIntegerEx(TraitsIntegerLO, x, d, field, flags) End Function /*! @author Egtra @date 2007/10/22 */ Function IntegerO_Convert(buf As *StrChar, xq As QWord, flags As FormatFlags) As DWord Dim x = xq As DWord Dim i = MaxSizeO While x <> 0 buf[i] = ((x And &o7) + &h30) As StrChar x >>= 3 i-- Wend If flags And Alt Then buf[i] = &h30 i-- End If Return i End Function /*! @brief IntegerO_ConvertのQWord版。 @author Egtra @date 2007/10/26 */ Function IntegerLO_Convert(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord Dim i = MaxSizeLO While x <> 0 buf[i] = ((x And &o7) + &h30) As StrChar x >>= 3 i-- Wend If flags And Alt Then buf[i] = &h30 i-- End If Return i End Function /*! @author Egtra @date 2007/10/22 @note #フラグ (Alt)の処理は、IntegerO/LO_Convert内で行うので、ここで処理することはない。 */ Function IntegerO_Prefix(x As QWord, flags As FormatFlags) As String End Function /*! @brief DWordの最大値の十六進法表現ffffffffの文字数 - 1。FormatIntegerO内で使用。 @author Egtra @date 2007/10/24 */ Const MaxSizeX = 7 /*! @brief QWordの最大値の十六進法表現ffffffffffffffffの文字数 - 1。FormatIntegerO内で使用。 @author Egtra @date 2007/10/26 */ Const MaxSizeLX = 15 /*! @author Egtra @date 2007/10/24 */ Dim TraitsIntegerX As IntegerConvertTraits With TraitsIntegerX .Convert = AddressOf(IntegerX_Convert) .Prefix = AddressOf(IntegerX_Prefix) .MaxSize = MaxSizeX End With /*! @author Egtra @date 2007/10/26 */ Dim TraitsIntegerLX As IntegerConvertTraits With TraitsIntegerLX .Convert = AddressOf(IntegerLX_Convert) .Prefix = AddressOf(IntegerX_Prefix) .MaxSize = MaxSizeLX End With /*! @brief 整数をprintfの%x, %X(十六進法)相当の変換で文字列化する関数。 @author Egtra @date 2007/10/19 @param[in] x 文字列化する整数値。 @param[in] d 精度、最小限表示される桁数。DWORD_MAXのとき、指定なしとして、既定値1となる。 @param[in] field フィールド幅。 @param[in] flags 書式フラグ。 @return xの文字列表現 */ Function FormatIntegerX(x As DWord, d As DWord, field As DWord, flags As FormatFlags) As String Return FormatIntegerEx(TraitsIntegerX, x, d, field, flags) End Function /*! @brief FormatIntegerXのQWord版。 @author Egtra @date 2007/10/22 */ Function FormatIntegerLX(x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String Return FormatIntegerEx(TraitsIntegerLX, x, d, field, flags) End Function /*! @author Egtra @date 2007/10/22 */ Function IntegerX_Convert(buf As *StrChar, xq As QWord, flags As FormatFlags) As DWord Dim i = MaxSizeX Dim x = xq As DWord While x <> 0 buf[i] = _System_HexadecimalTable[x And &h0f] x >>= 4 i-- Wend Return i End Function /*! @brief IntegerX_ConvertのQWord版。 @author Egtra @date 2007/10/22 */ Function IntegerLX_Convert(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord Dim i = MaxSizeLX While x <> 0 buf[i] = _System_HexadecimalTable[x And &h0f] x >>= 4 i-- Wend Return i End Function /*! @author Egtra @date 2007/10/24 */ Function IntegerX_Prefix(x As QWord, flags As FormatFlags) As String If flags And Alt Then If x <> 0 Then IntegerX_Prefix = "0X" End If End If End Function /*! @brief FormatIntegerExへ渡す変換特性を表す構造体型。 @author Egtra @date 2007/10/22 */ Type IntegerConvertTraits '!変換を行う関数へのポインタ。 Convert As *Function(buf As *StrChar, x As QWord, flags As FormatFlags) As DWord '!接頭辞を取得する関数へのポインタ。 Prefix As *Function(x As QWord, flags As FormatFlags) As String '!必要なバッファの大きさ。 MaxSize As DWord End Type /*! @brief 整数変換全てを行う関数。これを雛形とし、形式毎の差異はIntegerConvertTraitsで表現する。 @author Egtra @date 2007/10/22 @param[in] tr 特性情報。 @param[in] x 変換元の数値。 @param[in] d 精度。ここでは最低限出力する桁数。 @param[in] field フィールド幅。 @param[in] flags フラグ。 */ Function FormatIntegerEx(ByRef tr As IntegerConvertTraits, x As QWord, d As DWord, field As DWord, flags As FormatFlags) As String If d = DWORD_MAX Then d = 1 Else '精度が指定されているとき、ゼロフラグは無視される。 '仕様上、左揃えのときも無視されるが、それはAdjustFieldWidthが行ってくれる。 flags And= Not Zero End If Dim sb = New System.Text.StringBuilder With sb Dim prefixFunc = tr.Prefix Dim prefix = prefixFunc(x, flags) sb.Append(prefix) Dim prefixLen = 0 As DWord If String.IsNullOrEmpty(prefix) = False Then prefixLen = prefix.Length As DWord End If Dim buf = GC_malloc_atomic((tr.MaxSize + 1) * SizeOf (StrChar)) As *StrChar Dim convertFunc = tr.Convert Dim bufStartPos = convertFunc(buf, x, flags) Dim len = (tr.MaxSize - bufStartPos) As Long If len < 0 Then Debug End If If len < d Then .Append(&h30 As StrChar, d - len) End If .Append(buf, bufStartPos + 1, len) AdjustFieldWidth(sb, field, flags And (Not (Sign Or Blank)), prefixLen) End With FormatIntegerEx = sb.ToString() If (flags And Cap) = 0 Then FormatIntegerEx = FormatIntegerEx.ToLower() End If End Function /*! @brief QWordの最大値18446744073709551615の文字数 - 1。FormatIntegerLU内で使用。 @author Egtra @date 2007/09/18 */ Const MaxSizeLU = 19 /*! @brief 文字列をフィールド幅まで満たされるように空白などを挿入する。 @author Egtra @date 2007/10/13 @param[in,out] sb 対象文字列 @param[in] field フィールド幅 @param[in] hasSign 符号を持っている(負の値か)か否か @param[in] flags フラグ @param[in] prefixLen (あれば)接頭辞の文字数。ゼロ埋めする際、この数だけ挿入位置を後ろにする。 sbが"-1"のように負符号を持っている場合は、呼出元でSignフラグ(またはBlank)を立てること。 */ Sub AdjustFieldWidth(sb As System.Text.StringBuilder, field As DWord, flags As FormatFlags, prefixLen = 0 As DWord) With sb If .Length < field Then Dim embeddedSize = field - .Length If flags And Left Then .Append(&h20, embeddedSize) Else Dim insPos As Long If (flags And Zero) <> 0 Then If (flags And Blank) Or (flags And Sign) Then insPos++ End If insPos += prefixLen .Insert(insPos, String$(embeddedSize, "0")) Else .Insert(insPos, String$(embeddedSize, " ")) End If End If End If End With End Sub End Namespace 'Detail End Namespace 'Strings End Namespace 'ActiveBasic