Last active
January 20, 2023 05:08
-
-
Save dSalieri/167294aa08b802d6dadfb1228ae4ca1c to your computer and use it in GitHub Desktop.
Объект для чисел с плавающей точкой (решение проблемы неточности плавающих чисел)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const BigFloat = (function () { | |
const obtainData = (a, b) => { | |
[a, b].forEach((v) => { | |
if (typeof v !== "string") throw TypeError("Arguments have incorrect type, should be string"); | |
}); | |
return { | |
a: { | |
sign: a.includes("-") ? -1n : 1n, | |
intAndFloat: Object.assign(["0", "0"], a.split(".")).map((v) => { | |
BigInt(v); | |
if (v.includes("-")) return v.slice(1); | |
return v; | |
}), | |
}, | |
b: { | |
sign: b.includes("-") ? -1n : 1n, | |
intAndFloat: Object.assign(["0", "0"], b.split(".")).map((v) => { | |
BigInt(v); | |
if (v.includes("-")) return v.slice(1); | |
return v; | |
}), | |
}, | |
get maxLength() { | |
return { | |
int: Math.max(this.a.intAndFloat[0].length, this.b.intAndFloat[0].length), | |
float: Math.max(this.a.intAndFloat[1].length, this.b.intAndFloat[1].length), | |
full: Math.max(this.a.intAndFloat.join("").length, this.b.intAndFloat.join("").length), | |
}; | |
}, | |
}; | |
}; | |
const removeZeroes = (str, type, defaultValue) => { | |
type = type ?? "both"; | |
const job = (str, vector) => { | |
let i; | |
if (str.length === 0) return ""; | |
if (vector === "+") i = 0; | |
if (vector === "-") i = str.length - 1; | |
while (true) { | |
if (str[i] !== "0") break; | |
if (vector === "+") i++; | |
if (vector === "-") i--; | |
} | |
if (vector === "+") return str.slice(i); | |
if (vector === "-") return str.slice(0, i + 1); | |
}; | |
let result; | |
if (type === "start") result = job(str, "+"); | |
if (type === "end") result = job(str, "-"); | |
if (type === "both") result = job(job(str, "+"), "-"); | |
return result.length === 0 ? defaultValue : result; | |
}; | |
const collapseFloat = (str) => { | |
const splited = Object.assign([], ["0", "0"], str.split(".")); | |
splited[1] = removeZeroes(splited[1], "end", ""); | |
const result = splited.join("."); | |
return result[result.length - 1] === "." ? splited[0] : result; | |
}; | |
const roundFloat = (str) => { | |
const splited = Object.assign([], ["0", "0"], str.split(".")); | |
const index = splited[1].indexOf("99"); | |
if (index < 1) { | |
const result = removeZeroes(str, "end", ""); | |
if (result[result.length - 1] === ".") return result.slice(0, result.length - 1); | |
return result; | |
} | |
splited[1] = splited[1].slice(0, index + 1); | |
let valuableFloat = String(BigInt(splited[1])); | |
const zeroesL = splited[1].length - valuableFloat.length; | |
while (true) { | |
valuableFloat = removeZeroes(String(BigInt(valuableFloat) + 1n), "end", ""); | |
if (valuableFloat.length === splited[1].length || valuableFloat === "1" || valuableFloat.indexOf("99") === -1) break; | |
} | |
if (valuableFloat === "1") { | |
if (zeroesL === 0) { | |
return String(BigInt(splited[0]) + 1n); | |
} | |
return splited[0].concat(".").concat("0".repeat(zeroesL - 1).concat(valuableFloat)); | |
} | |
return splited[0].concat(".").concat("0".repeat(zeroesL).concat(valuableFloat)); | |
}; | |
const alignNumber = (strNumber, radix, direction) => { | |
if (direction === "right") direction = 0; | |
if (direction === "left") direction = 1; | |
return BigInt(shiftNumber(strNumber, radix, direction, false)); | |
}; | |
const shiftNumber = (string, pos, mode, dotFlag) => { | |
dotFlag = dotFlag ?? true; | |
if (typeof string !== "string") throw TypeError("'string' should be a string type"); | |
if (typeof pos !== "number") throw TypeError("'pos' should be a number type"); | |
const thunk = (...args) => args.reduce((acc, value) => (acc = acc.concat(value)), ""); | |
const data = { | |
sign: string[0] === "-" ? "-" : "", | |
number: string[0] === "-" ? string.slice(1) : string, | |
}; | |
const dot = dotFlag ? "." : ""; | |
const zero = dotFlag ? "0" : ""; | |
if (pos > 0) { | |
let result = data.number.slice(0, pos); | |
if (pos >= data.number.length) { | |
result = !mode ? thunk(result, "0".repeat(pos - data.number.length), dot, zero) : thunk(zero, dot, "0".repeat(pos - data.number.length), result); | |
} else { | |
result = !mode ? thunk(result, dot, data.number.slice(pos, data.number.length)) : thunk(data.number.slice(0, -pos), dot, data.number.slice(-pos)); | |
} | |
return data.sign.concat(result); | |
} else { | |
return data.sign.concat(!mode ? thunk(zero, dot, "0".repeat(Math.abs(pos)), data.number) : thunk(data.number, "0".repeat(Math.abs(pos)), dot, zero)); | |
} | |
}; | |
const newthonDivision = (a, b) => { | |
if (a === 0n && b === 0n) return "NaN"; | |
if (b === 0n) return "Infinity"; | |
if (a === 0n) return "0"; | |
const base = (base, x, options) => { | |
options = Object.assign( | |
{ | |
iterations: 5, | |
lengthOfNumber: 128, | |
}, | |
options | |
); | |
if (base === x) return 1n; | |
if (x === 0n) return 0n; | |
const limit = 10n ** BigInt(options.lengthOfNumber - 1); | |
const getLength = (b, x) => { | |
const value = BigInt("1".concat("0".repeat(String(b * x).length - 1))); | |
if (2n * value > b * x) return value; | |
return value * 10n; | |
}; | |
let length = getLength(base, x); | |
let getOnce = false; | |
let shortExit = -1; | |
let i = 0; | |
while (i++ < options.iterations || x < limit) { | |
x = x * (2n * length - base * x); | |
shortExit = String(x).indexOf("9".repeat(8)); | |
if (shortExit !== -1) { | |
return BigInt(String(x).slice(0, shortExit)) + 1n; | |
} | |
if (x >= limit) { | |
x = BigInt(String(x).slice(0, options.lengthOfNumber)); | |
length = !getOnce ? getLength(base, x) : length; | |
} else { | |
length *= length; | |
} | |
} | |
return x; | |
}; | |
const goodValue = String(base(b, 1n)).slice(0, 8); | |
const result = base(b, BigInt(goodValue)); | |
const aR = String(a * result); | |
const bR = String(b * result); | |
const actualLength = roundFloat("0.".concat(bR)) === "1" ? bR.length + 1 : bR.length; | |
const parts = shiftNumber(aR, actualLength - 1, 1).split("."); | |
return roundFloat(((parts[1] = parts[1].slice(0, 64)), parts.join("."))); | |
}; | |
return { | |
addition(a, b, options) { | |
options = Object.assign({ round: false }, options); | |
let round = roundFloat; | |
if (options.round === false) round = collapseFloat; | |
const { a: a1, b: b1, maxLength } = obtainData(a, b); | |
const aBigInt = alignNumber(a1.intAndFloat.join(""), maxLength.full, "right"); | |
const bBigInt = alignNumber(b1.intAndFloat.join(""), maxLength.full, "right"); | |
return round(shiftNumber(String(aBigInt * a1.sign + bBigInt * b1.sign), maxLength.float, 1)); | |
}, | |
subtraction(a, b, options) { | |
options = Object.assign({ round: false }, options); | |
let round = roundFloat; | |
if (options.round === false) round = collapseFloat; | |
const { a: a1, b: b1, maxLength } = obtainData(a, b); | |
const aBigInt = alignNumber(a1.intAndFloat.join(""), maxLength.full, "right"); | |
const bBigInt = alignNumber(b1.intAndFloat.join(""), maxLength.full, "right"); | |
return round(shiftNumber(String(aBigInt * a1.sign - bBigInt * b1.sign), maxLength.float, 1)); | |
}, | |
multiplication(a, b, options) { | |
options = Object.assign({ round: false }, options); | |
let round = roundFloat; | |
if (options.round === false) round = collapseFloat; | |
const { a: a1, b: b1 } = obtainData(a, b); | |
return round( | |
shiftNumber(String(BigInt(a1.intAndFloat.join("")) * a1.sign * BigInt(b1.intAndFloat.join("")) * b1.sign), a1.intAndFloat[1].length + b1.intAndFloat[1].length, 1) | |
); | |
}, | |
division(a, b, mode) { | |
switch (true) { | |
case mode === undefined: { | |
const { a: a1, b: b1, maxLength } = obtainData(a, b); | |
const aBigInt = BigInt(a1.intAndFloat.join("").concat("0".repeat(maxLength.float - a1.intAndFloat[1].length))); | |
const bBigInt = BigInt(b1.intAndFloat.join("").concat("0".repeat(maxLength.float - b1.intAndFloat[1].length))); | |
const signStr = a1.sign * b1.sign === 1n ? "" : "-"; | |
const newthon = newthonDivision(aBigInt, bBigInt); | |
return ["0", "NaN"].some((v) => v === newthon) ? newthon : signStr.concat(newthon); | |
} | |
case mode === "default": { | |
return String(a / b); | |
} | |
} | |
}, | |
__proto__: { | |
[Symbol.toStringTag]: "BigFloat", | |
}, | |
}; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Список для проверки | |
/// Если результат конструкции true, значит список для проверки полностью проходит тест | |
[[["1.1","0.8"], {addition: "1.9", subtraction:"0.3", multiplication:"0.88", division:"1.375"}], | |
[["0.8","0.9"], {addition: "1.7", subtraction:"-0.1", multiplication:"0.72", division:"0.8888888888888888888888888888888888888888888888888888888888888888"}], | |
[["-1.1","0.8"], {addition: "-0.3", subtraction:"-1.9", multiplication:"-0.88", division:"-1.375"}], | |
[["-1.8","0.9"], {addition: "-0.9", subtraction:"-2.7", multiplication:"-1.62", division:"-2"}], | |
[["1.1","-0.8"], {addition: "0.3", subtraction:"1.9", multiplication:"-0.88", division:"-1.375"}], | |
[["1.8","-0.9"], {addition: "0.9", subtraction:"2.7", multiplication:"-1.62", division:"-2"}], | |
[["-1.1","-0.8"], {addition: "-1.9", subtraction:"-0.3", multiplication:"0.88", division:"1.375"}], | |
[["-0.8","-0.9"], {addition: "-1.7", subtraction:"0.1", multiplication:"0.72", division:"0.8888888888888888888888888888888888888888888888888888888888888888"}], | |
[["-1", "0.4"], {addition: "-0.6", subtraction:"-1.4", multiplication:"-0.4", division:"-2.5"}], | |
[["0", "5.1"], {addition: "5.1", subtraction:"-5.1", multiplication:"0", division:"0"}], | |
[["5.793450009909345345345", "0.4"], {addition: "6.193450009909345345345", subtraction:"5.393450009909345345345", multiplication:"2.317380003963738138138", division:"14.4836250247733633633625"}]] | |
.map((subArr) => { | |
return { | |
addition: BigFloat.addition(...subArr[0]) === subArr[1].addition, | |
subtraction: BigFloat.subtraction(...subArr[0]) === subArr[1].subtraction, | |
multiplication: BigFloat.multiplication(...subArr[0]) === subArr[1].multiplication, | |
division: BigFloat.division(...subArr[0]) === subArr[1].division | |
} | |
}) | |
.every((item) => item.addition === item.subtraction === item.multiplication === item.division) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment