Decimalデシマル関数

本ページには広告が含まれています。

小数の値を加算・減算・乗算・除算したときの丸め誤差を最小限に抑えます。第一引数に左項の値(被加数・被減数・被乗数・被除数)、第二引数のに右項の値(加数・減数・乗数・除数)を指定します。

構文
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)必須
除数
戻り値

プログラム

UWSC
//////////////////////////////////////////////////
// 【引数】
//   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.10.2を単精度浮動小数点数型に変換したときの値とその和である0.1 + 0.2の計算を行ったときの結果です。

0.10.2は2進数では無限小数となり値を正しく表現できないため、計算結果に誤差が出てしまいます。

UWSC
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関数を使います。

UWSC
PRINT Decimal.add("12.3", "10.5")
結果
プレーンテキスト
22.8

2数の差を計算

差を求めるにはsub関数を使います。

12.3 - 12を通常の方法とDecimalモジュールで計算したときの比較です。

通常の計算を行ったとき結果は0.300000000000001と間違った結果になりますが、Decimalモジュールを使った計算方法では正しく0.3と求められます。

UWSC
DIM arg1 = 12.3
DIM arg2 = 12

PRINT arg1 - arg2
PRINT Decimal.sub(arg1, arg2)
結果
プレーンテキスト
0.300000000000001
0.3

2数の積を計算

積を求めるにはmul関数を使います。

UWSC
PRINT Decimal.mul("12.3", "0.1")
結果
プレーンテキスト
1.23

2数の商を計算

商を求めるにはdiv関数を使います。

UWSC
PRINT Decimal.div("55", "2.5")
結果
プレーンテキスト
11