本ページには広告が含まれています。
小数の値を加算・減算・乗算・除算したときの丸め誤差を最小限に抑えます。第一引数に左項の値(被加数・被減数・被乗数・被除数)、第二引数のに右項の値(加数・減数・乗数・除数)を指定します。
- 構文
- Decimal.add( augend, addend )
Decimal.sub( minuend, subtrahend )
Decimal.mul( multiplier, multiplicand )
Decimal.div( dividend, divisor )
- 引数
- augend (String)必須
- 被加数
- addend (String)必須
- 加数
- minuend (String)必須
- 被減数
- subtrahend (String)必須
- 減数
- multiplier (String)必須
- 被乗数
- multiplicand (String)必須
- 乗数
- dividend (String)必須
- 被除数
- divisor (String)必須
- 除数
- 戻り値
プログラム
//////////////////////////////////////////////////
// 【引数】
// augend : 被加数
// addend : 加数
// minuend : 被減数
// subtrahend : 減数
// multiplier : 被乗数
// multiplicand : 乗数
// dividend : 被除数
// divisor : 除数
// 【戻り値】
//
//////////////////////////////////////////////////
MODULE Decimal
CONST BASE = 1E+7
CONST LOGBASE = 7
FUNCTION add(augend, addend)
IFB isInt(VAL(augend)) AND isInt(VAL(addend)) THEN
RESULT = VAL(augend) + VAL(addend)
ELSE
augend = augend + IIF(POS(".", augend)=0,".", "")
DIM augendIntDec = SPLIT(augend, ".")
DIM ex = LENGTH(augendIntDec[0]) - 1
augendIntDec = strSplit(strPad(augendIntDec[0], CEIL(LENGTH(augendIntDec[0])/7)*7, "0", LEFT) + augendIntDec[1], 7)
augendIntDec[0] = ABS(augendIntDec[0])
DIM x = UBound(augendIntDec)
augendIntDec[x] = strPad(augendIntDec[x], 7, "0", RIGHT)
addend = addend + IIF(POS(".", addend)=0,".", "")
DIM addendIntDec = SPLIT(addend, ".")
DIM ey = LENGTH(addendIntDec[0]) - 1
addendIntDec = strSplit(strPad(addendIntDec[0], CEIL(LENGTH(addendIntDec[0])/7)*7, "0", LEFT) + addendIntDec[1], 7)
addendIntDec[0] = ABS(addendIntDec[0])
DIM y = UBound(addendIntDec)
addendIntDec[y] = strPad(addendIntDec[y], 7, "0", RIGHT)
ex = INT(ex/LOGBASE)
ey = INT(ey/LOGBASE)
IFB ex > ey THEN
FOR i = 1 TO ex - ey
arrayUnshift(addendIntDec, "0000000")
NEXT
ELSEIF ex < ey THEN
FOR i = 1 TO ey - ex
arrayUnshift(augendIntDec, "0000000")
NEXT
ENDIF
DIM augendDecimalLength = LENGTH(augendIntDec)
DIM addendDecimalLength = LENGTH(addendIntDec)
IFB augendDecimalLength > addendDecimalLength THEN
FOR i = 1 TO augendDecimalLength - addendDecimalLength
arrayPush(addendIntDec, "0")
NEXT
ELSEIF augendDecimalLength < addendDecimalLength THEN
FOR i = 1 TO addendDecimalLength - augendDecimalLength
arrayPush(augendIntDec, "0")
NEXT
ENDIF
DIM res[UBound(augendIntDec)]
DIM carry = 0
FOR i = UBound(augendIntDec) TO 0 STEP -1
res[i] = VAL(augendIntDec[i]) + VAL(addendIntDec[i]) + carry
carry = INT(res[i] / BASE)
res[i] = res[i] MOD BASE
NEXT
IF carry <> 0 THEN arrayUnshift(res, carry)
DIM dp = IIF(ex > ey, ex, ey)
FOR i = 1 TO UBound(res)
res[i] = strPad(res[i], 7, "0", LEFT)
NEXT
res[dp] = res[dp] + "."
RESULT = VAL(JOIN(res, ""))
ENDIF
FEND
FUNCTION sub(minuend, subtrahend)
IFB isInt(VAL(minuend)) AND isInt(VAL(subtrahend)) THEN
RESULT = VAL(minuend) - VAL(subtrahend)
ELSE
minuend = minuend + IIF(POS(".", minuend)=0, ".0", "")
DIM minuendIntDec = SPLIT(minuend, ".")
DIM ex = LENGTH(minuendIntDec[0]) - 1
minuendIntDec = strSplit(strPad(minuendIntDec[0], CEIL(LENGTH(minuendIntDec[0])/7)*7, "0", LEFT) + minuendIntDec[1], 7)
minuendIntDec[0] = ABS(minuendIntDec[0])
DIM x = UBound(minuendIntDec)
minuendIntDec[x] = strPad(minuendIntDec[x], 7, "0", RIGHT)
subtrahend = subtrahend + IIF(POS(".", subtrahend)=0,".0", "")
DIM subtrahendIntDec = SPLIT(subtrahend, ".")
DIM ey = LENGTH(subtrahendIntDec[0]) - 1
subtrahendIntDec = strSplit(strPad(subtrahendIntDec[0], CEIL(LENGTH(subtrahendIntDec[0])/7)*7, "0", LEFT) + subtrahendIntDec[1], 7)
subtrahendIntDec[0] = ABS(subtrahendIntDec[0])
DIM y = UBound(subtrahendIntDec)
subtrahendIntDec[y] = strPad(subtrahendIntDec[y], 7, "0", RIGHT)
ex = INT(ex/LOGBASE)
ey = INT(ey/LOGBASE)
IFB ex > ey THEN
FOR i = 1 TO ex - ey
arrayUnshift(subtrahendIntDec, "0000000")
NEXT
ELSEIF ex < ey THEN
FOR i = 1 TO ey - ex
arrayUnshift(minuendIntDec, "0000000")
NEXT
ENDIF
DIM minuendDecimalLength = LENGTH(minuendIntDec)
DIM subtrahendDecimalLength = LENGTH(subtrahendIntDec)
IFB minuendDecimalLength > subtrahendDecimalLength THEN
FOR i = 1 TO minuendDecimalLength - subtrahendDecimalLength
arrayPush(subtrahendIntDec, "0")
NEXT
ELSEIF minuendDecimalLength < subtrahendDecimalLength THEN
FOR i = 1 TO subtrahendDecimalLength - minuendDecimalLength
arrayPush(minuendIntDec, "0")
NEXT
ENDIF
FOR i = 0 TO UBound(minuendIntDec)
IFB minuendIntDec[i] <> subtrahendIntDec[i] THEN
DIM bool = IIF(minuendIntDec[i] < subtrahendIntDec[i], TRUE, FALSE)
BREAK
ENDIF
NEXT
DIM sign = 1
IFB bool THEN
sign = -1 * sign
FOR i = 0 TO UBound(minuendIntDec)
swap(minuendIntDec[i], subtrahendIntDec[i])
NEXT
ENDIF
DIM res[UBound(minuendIntDec)]
FOR i = UBound(minuendIntDec) TO 0 STEP -1
IFB VAL(minuendIntDec[i]) < VAL(subtrahendIntDec[i]) THEN
minuendIntDec[i] = VAL(minuendIntDec[i]) + BASE
minuendIntDec[i-1] = minuendIntDec[i-1] - 1
ENDIF
res[i] = minuendIntDec[i] - subtrahendIntDec[i]
NEXT
DIM dp = IIF(ex > ey, ex, ey)
FOR i = 1 TO UBound(res)
res[i] = strPad(res[i], 7, "0", LEFT)
NEXT
res[dp] = res[dp] + "."
RESULT = mul(sign, VAL(JOIN(res, "")))
ENDIF
FEND
FUNCTION mul(multiplier, multiplicand)
IFB isInt(VAL(multiplier)) AND isInt(VAL(multiplicand)) THEN
RESULT = EVAL("multiplier * multiplicand")
ELSE
multiplier = multiplier + IIF(POS(".", multiplier)=0, ".", "")
DIM multiplierIntDec = SPLIT(multiplier, ".")
multiplier = REPLACE(multiplier, ".", "")
multiplicand = multiplicand + IIF(POS(".", multiplicand)=0, ".", "")
DIM multiplicandIntDec = SPLIT(multiplicand, ".")
multiplicand = REPLACE(multiplicand, ".", "")
DIM n = LENGTH(multiplierIntDec[1]) + LENGTH(multiplicandIntDec[1])
DIM res = mul(multiplier, multiplicand)
res = strPad(res, n, "0", LEFT)
DIM dp = LENGTH(res) - n
RESULT = VAL(COPY(res, 1, dp) + "." + COPY(res, dp + 1))
ENDIF
FEND
FUNCTION div(dividend, divisor)
IFB isInt(VAL(dividend)) AND isInt(VAL(divisor)) THEN
RESULT = EVAL("dividend / divisor")
ELSE
dividend = dividend + IIF(POS(".", dividend)=0,".", "")
DIM dividendIntDec = SPLIT(dividend, ".")
DIM ex = LENGTH(dividendIntDec[1])
divisor = divisor + IIF(POS(".", divisor)=0, ".", "")
DIM divisorIntDec = SPLIT(divisor, ".")
DIM ey = LENGTH(divisorIntDec[1])
DIM x = LENGTH(dividendIntDec[1])
DIM y = LENGTH(divisorIntDec[1])
DIM n = IIF(x > y, x, y)
dividend = VAL(REPLACE(dividend, ".", "")) + strRepeat("0", n - x)
divisor = VAL(REPLACE(divisor, ".", "")) + strRepeat("0", n - y)
RESULT = div(dividend, divisor)
ENDIF
FEND
ENDMODULE
//////////////////////////////////////////////////
// 【引数】
// arr : 追加される配列(参照引数)
// tmp : 追加する配列
// 【戻り値】
// 追加した後の配列の要素数
//////////////////////////////////////////////////
FUNCTION arrayMerge(Var arr[], tmp[])
FOR n = 0 TO UBound(tmp)
arrayPush(arr, tmp[n])
NEXT
RESULT = UBound(arr)
FEND
//////////////////////////////////////////////////
// 【引数】
// array : 要素を追加する配列(参照引数)
// values : 追加する要素をvalue1から指定
// 【戻り値】
// 処理後の配列の要素の数
//////////////////////////////////////////////////
FUNCTION arrayPush(var array[], value1 = EMPTY, value2 = EMPTY, value3 = EMPTY, value4 = EMPTY, value5 = EMPTY, value6 = EMPTY, value7 = EMPTY, value8 = EMPTY, value9 = EMPTY, value10 = EMPTY, value11 = EMPTY, value12 = EMPTY, value13 = EMPTY, value14 = EMPTY, value15 = EMPTY, value16 = EMPTY)
DIM i = 1
WHILE EVAL("value" + i) <> EMPTY
DIM res = RESIZE(array, UBound(array) + 1)
array[res] = EVAL("value" + i)
i = i + 1
WEND
RESULT = LENGTH(array)
FEND
//////////////////////////////////////////////////
// 【引数】
// array : 要素を加えられる配列
// values : 加える値をvalue1から順に指定
// 【戻り値】
// 処理後の配列の要素の数
//////////////////////////////////////////////////
FUNCTION arrayUnshift(var array[], value1 = EMPTY, value2 = EMPTY, value3 = EMPTY, value4 = EMPTY, value5 = EMPTY, value6 = EMPTY, value7 = EMPTY, value8 = EMPTY, value9 = EMPTY, value10 = EMPTY, value11 = EMPTY, value12 = EMPTY, value13 = EMPTY, value14 = EMPTY, value15 = EMPTY, value16 = EMPTY)
DIM tmp[-1]
DIM i = 1
WHILE EVAL("value" + i) <> EMPTY
arrayPush(tmp, EVAL("value" + i))
i = i + 1
WEND
arrayMerge(tmp, array)
RESIZE(array, UBound(tmp))
SETCLEAR(array, EMPTY)
FOR i = 0 TO UBound(tmp)
array[i] = tmp[i]
NEXT
RESULT = LENGTH(array)
FEND
//////////////////////////////////////////////////
// 【引数】
// expr : 評価する式
// truepart : 評価した式がTrueのときに返す値
// falsepart : 評価した式がFalseのときに返す値
// 【戻り値】
// truepart : 評価した式がTrueのとき、falsepart : 評価した式がFalseのとき
//////////////////////////////////////////////////
FUNCTION IIF(expr, truepart, falsepart)
IFB EVAL(expr) THEN
RESULT = truepart
ELSE
RESULT = falsepart
ENDIF
FEND
//////////////////////////////////////////////////
// 【引数】
// variable : 型を調べる変数
// 【戻り値】
// : TRUE : 与えられた変数がブール型である、
// FALSE : 与えられた変数がブール型でない、 :
//////////////////////////////////////////////////
FUNCTION isBoolean(variable)
RESULT = IIF(VARTYPE(variable) = VAR_BOOLEAN, TRUE, FALSE)
FEND
//////////////////////////////////////////////////
// 【引数】
// variable : 型を調べる変数
// 【戻り値】
// : TRUE : 与えられた変数が整数型である、
// FALSE : 与えられた変数が整数型でない、 :
//////////////////////////////////////////////////
FUNCTION isInt(variable)
IFB VAL(variable) <> ERR_VALUE AND !isBoolean(variable) AND !isString(variable) THEN
RESULT = IIF(variable - INT(variable) = 0, TRUE, FALSE)
ELSE
RESULT = FALSE
ENDIF
FEND
//////////////////////////////////////////////////
// 【引数】
// variable : 型を調べる変数
// 【戻り値】
// : TRUE : 与えられた変数が文字列型である、
// FALSE : 与えられた変数が文字列型でない、 :
//////////////////////////////////////////////////
FUNCTION isString(variable)
RESULT = IIF(VARTYPE(variable) = VAR_ASTR OR VARTYPE(variable) = VAR_USTR, TRUE, FALSE)
FEND
//////////////////////////////////////////////////
// 【引数】
// num : 符号を求める数値
// 【戻り値】
// 1 : 正の数、0 : ゼロ、-1 : 負の数、ERR_VALUE : それ以外
//////////////////////////////////////////////////
FUNCTION sign(num)
SELECT TRUE
CASE !CHKNUM(num)
RESULT = ERR_VALUE
CASE num > 0
RESULT = 1
CASE num = 0
RESULT = 0
CASE num < 0
RESULT = -1
SELEND
FEND
//////////////////////////////////////////////////
// 【引数】
// input : 入力文字列
// length : 埋めたあとの長さ
// str : 埋める文字
// type : 埋める方向
// 【戻り値】
// 指定文字で埋めた文字列
//////////////////////////////////////////////////
FUNCTION strPad(input, length, str = " ", type = RIGHT)
DIM s = ""
SELECT type
CASE LEFT
FOR i = 1 TO CEIL((length - LENGTH(input)) / LENGTH(str))
s = s + str
NEXT
input = COPY(s, 1, length - LENGTH(input)) + input
CASE RIGHT
FOR i = 1 TO CEIL((length - LENGTH(input)) / LENGTH(str))
s = s + str
NEXT
input = input + COPY(s, 1, length - LENGTH(input))
SELEND
RESULT = input
FEND
//////////////////////////////////////////////////
// 【引数】
// inputs : 繰り返す文字列
// multiplier : inputsを繰り返す回数
// 【戻り値】
// inputsをmultiplier回を繰り返した文字列を返します
//////////////////////////////////////////////////
FUNCTION strRepeat(inputs, multiplier)
DIM res = ""
FOR n = 1 TO multiplier
res = res + inputs
NEXT
RESULT = res
FEND
//////////////////////////////////////////////////
// 【引数】
// string : 分割する文字列
// length : 各要素の最大長
// 【戻り値】
//
//////////////////////////////////////////////////
FUNCTION strSplit(string, length = 1)
DIM array[-1]
WHILE LENGTH(string)
arrayPush(array, COPY(string, 1, length))
string = COPY(string, length + 1)
WEND
RESULT = SLICE(array)
FEND
//////////////////////////////////////////////////
// 【引数】
// a : bと交換する値。参照引数。
// b : aと交換する値。参照引数。
// 【戻り値】
//
//////////////////////////////////////////////////
PROCEDURE swap(Var a, Var b)
DIM tmp = a
a = b
b = tmp
FEND
//////////////////////////////////////////////////
// 【引数】
// arrayname : 上限値を求める配列の名前
// dimension : 返す次元を示す整数
// 【戻り値】
// 配列の上限値
//////////////////////////////////////////////////
FUNCTION UBound(arrayname[], dimension = 1)
RESULT = EVAL("RESIZE(arrayname" + strRepeat("[0]", dimension - 1) + ")")
FEND
丸め誤差
丸め誤差とは、小数点以下の数を2進数で表現できない(近似値を使わざるを得ない)ことにより発生する誤差です。倍精度浮動小数点数の場合は精度が良いので多少の計算であれば問題ありませんが、数値の型が単精度浮動小数点数型(Single)の場合は小数の計算を行ったときに丸め誤差の影響をより受けることになります。
以下は0.1と0.2を単精度浮動小数点数型に変換したときの値とその和である0.1 + 0.2の計算を行ったときの結果です。
0.1、0.2は2進数では無限小数となり値を正しく表現できないため、計算結果に誤差が出てしまいます。
DIM a = VARTYPE(0.1, VAR_SINGLE)
DIM b = VARTYPE(0.2, VAR_SINGLE)
PRINT a
PRINT b
PRINT a + b
- 結果
0.100000001490116 0.200000002980232 0.300000004470348
丸め誤差は小数の値を扱ったときにのみ誤差が生じるため、Decimalモジュールでは小数の値を整数の値に変換し、計算を行ってから再度小数に戻すという処理を行うことで誤差を最小限に抑えています。
使い方
2数の和を計算
和を求めるにはadd関数を使います。
PRINT Decimal.add("12.3", "10.5")
- 結果
22.8
2数の差を計算
差を求めるにはsub関数を使います。
12.3 - 12を通常の方法とDecimalモジュールで計算したときの比較です。
通常の計算を行ったとき結果は0.300000000000001と間違った結果になりますが、Decimalモジュールを使った計算方法では正しく0.3と求められます。
DIM arg1 = 12.3
DIM arg2 = 12
PRINT arg1 - arg2
PRINT Decimal.sub(arg1, arg2)
- 結果
0.300000000000001 0.3
2数の積を計算
積を求めるにはmul関数を使います。
PRINT Decimal.mul("12.3", "0.1")
- 結果
1.23
2数の商を計算
商を求めるにはdiv関数を使います。
PRINT Decimal.div("55", "2.5")
- 結果
11