Skip to content

Instantly share code, notes, and snippets.

@dSalieri
Last active January 20, 2023 05:08
Show Gist options
  • Save dSalieri/167294aa08b802d6dadfb1228ae4ca1c to your computer and use it in GitHub Desktop.
Save dSalieri/167294aa08b802d6dadfb1228ae4ca1c to your computer and use it in GitHub Desktop.
Объект для чисел с плавающей точкой (решение проблемы неточности плавающих чисел)
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",
},
};
})();
/// Список для проверки
/// Если результат конструкции 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