Don’t be a hero, young man. There’s no percentage in it. ———— Harlan Potter
年轻人,别逞英雄了。 没有什么好处的。 ———— Harlan Potter
A big integer system can solve many problems, but it is obviously constrained to just integers, and there are some problems that integers can not solve. So let’s build a big floating point system. A floating point system is concerned with three numbers, a coefficient, an exponent, and a basis. The three numbers determine a value.
value = coefficient * (basis ** exponent)
一个大整数系统可以解决许多问题,但显然它只局限于整数,并且仍存在一些问题是整数无法解决的。 因此我们需要构建一个大浮点数系统。 浮点系统确定一个值,与三个数值有关,系数coefficient、指数exponent 和基数basis。
value = coefficient * (basis ** exponent)
The IEEE 754 format that JavaScript uses for its numbers has a basis of 2. This provides the benefit that hardware implementations in the 1950s were feasible. Moore’s Law has eliminated the constraints that made 2 the only reasonable basis,so let’s consider other possibilities.
JavaScript用于其数字的IEEE 754格式的基数为2。这为上世纪50年代硬件实施的可行性提供了好处。摩尔定律已经消除了将2当成唯一合理基数的约束,所以我们可以考虑其他可能性。
Our Big Integer package has an affinity for 2 ** 24. If we made 16777216 our basis, then some alignment operations would simply insert or delete elements from the array. That could provide very good performance. And it is consistent with the very popular practice of trading away correctness for performance.
我们的大整数包与2 ** 24
有关。如果我们将16777216作为基数,那么进行一些对比操作时,便可以简单地从数组中插入或删除元素。这不仅提供了非常好的性能,而且与用准确性换取性能的流行做法是一致的。
I think the basis should be 10. If the basis is 10, then all decimal fractions can be represented exactly. That is important because most humans use decimal fractions, so a floating point system with a basis of 10 would be good for humanity.
我认为基数应该是10。如果基数为10,那么所有的小数部分都能被精确表示。这很重要,因为大多数人都使用小数,因此以10为基数的浮点数系统对人类更友好。
Big integers are an ideal representation of the coefficient. Most of the weirdness of floating point systems is due to size constraints. If size is unbounded, there is little weirdness. Since weirdness can cause bugs, it is good to get rid of it when we can.
大整数是理想的系数。大多数浮点系统的奇怪之处是由于大小限制。如果大小是无限的,就没什么奇怪的。既然怪异会导致bug
,那么当我们可以摆脱它的时候就摆脱它。
We could also use big integers for the exponent, but that would be overkill. JavaScript’s numbers will be just fine. We would exhaust our gigabytes of memory long before Number.MAX_SAFE_INTEGER becomes a constraint.
我们也可以用大整数作为指数,但是这样有点小题大做了。JavaScript的number
可以,但是在Number.MAX_SAFE_INTEGER
成为约束之前就过早地耗尽千兆字节的内存。
We represent big floating point numbers as objects with coefficient and exponent properties.
我们将大浮点数表示为具有coefficient
(系数)和exponent
(指数)属性的对象。
Armed with big integers, floating point really isn’t very complicated.
有了大整数,浮点数并不是很复杂。
import big_integer from "./big_integer.js";
The is_big_float function is used to identify a big float object.
is_big_float
函数用于标识一个大浮点数对象。
function is_big_float(big) {
return (
typeof big === "object"
&& big_integer.is_big_integer(big.coefficient)
&& Number.isSafeInteger(big.exponent)
);
}
function is_negative(big) {
return big_integer.is_negative(big.coefficient);
}
function is_positive(big) {
return big_integer.is_positive(big.coefficient);
}
function is_zero(big) {
return big_integer.is_zero(big.coefficient);
}
A single zero value represents all zeros.
单个0值表示所有的0
const zero = Object.create(null);
zero.coefficient = big_integer.zero;
zero.exponent = 0;
Object.freeze(zero);
function make_big_float(coefficient, exponent) {
if (big_integer.is_zero(coefficient)) {
return zero;
}
const new_big_float = Object.create(null);
new_big_float.coefficient = coefficient;
new_big_float.exponent = exponent;
return Object.freeze(new_big_float);
}
const big_integer_ten_million = big_integer.make(10000000);
The number function converts a big floating point number into a JavaScript number. The conversion is not guaranteed to be exact if the number is outside of the safe integer zone. We also try to make sense out of other types.
number
函数将一个大浮点数转换成一个JavaScript数字。如果数字在安全整数区之外,则不能保证转换是精确的。我们也试图理解其他数字类型的情况。
function number(a) {
return (
is_big_float(a)
? (
a.exponent === 0
? big_integer.number(a.coefficient)
: big_integer.number(a.coefficient) * (10 **
a.exponent)
)
: (
typeof a === "number"
? a
: (
big_integer.is_big_integer(a)
? big_integer.number(a)
: Number(a)
)
)
);
}
We need an absolute value function and a negation function.
我们需要一个绝对值函数和一个否定函数。
function neg(a) {
return make_big_float(big_integer.neg(a.coefficient), a.exponent);
}
function abs(a) {
return (
is_negative(a)
? neg(a)
: a
);
}
Addition and subtraction are really easy: We just add the coefficients together, but only if the exponents are equal. If the exponents are not equal, we must make them conform. Because addition and subtraction are so similar, I made a function that makes the add and sub functions. If you pass big_integer.add into conform_op, you get the floating point add function. If you pass big_integer.sub into conform_op, you get the floating point sub function.
加法和减法很简单:只是把系数加在一起,但前提是指数要相等。如果指数不相等,则必须保证它们一致。因为加法和减法十分相似,所以我做了一个函数来实现add
和sub
函数。如果将big_integer.add
传递给conform_op
,则得到add
函数;若将big_integer.sub
传递给 conform_op
,则得到sub
函数。
function conform_op(op) {
return function (a, b) {
const differential = a.exponent - b.exponent;
return (
differential === 0 ?
make_big_float(op(a.coefficient,
b.coefficient), a.exponent) :
(
differential > 0 ?
make_big_float(
op(
big_integer.mul(a.coefficient,
big_integer.power(big_integer.ten, differential)
),
b.coefficient
),
b.exponent
) :
make_big_float(
op(
a.coefficient,
big_integer.mul(
b.coefficient,
big_integer.power(big_integer.ten, -differential)
)
),
a.exponent
)
)
);
};
}
const add = conform_op(big_integer.add);
const sub = conform_op(big_integer.sub);
Multiplication is even easier. We just multiply the coefficients and add the exponents.
乘法更容易,只需乘以系数并加上指数。
function mul(multiplicand, multiplier) {
return make_big_float(
big_integer.mul(multiplicand.coefficient,
multiplier.coefficient),
multiplicand.exponent + multiplier.exponent
);
}
The difficulty with division is knowing when to stop. Stopping is easy with integer division. You stop when you run out of digits. It is also easy with fixed size floating point. You stop when you run out of bits, but using big integers, there is no limit. We could continue to divide until we get an exact result, but there is no guarantee that such a result is ever attainable. So we will leave it up to the programmer. The div function takes an optional third argument which is the precision of the result. You indicate a decimal place. The units position is zero. Fractional positions are negative. The division returns at least as many decimal places as you specify. The default is -4, which is four digits after the decimal point.
除法的困难在于何时停止。整数除法很容易停止,数字用完了就停止。对于大小固定的浮点数也很简单,当位数用完时就停止。但是使用大整数时,是没有限制的。我们可以继续除法,直到得到一个精确的结果,但不能保证这样的结果可以得到。因此我们把这个问题留给程序员。div
函数接受可选的第三个参数,即结果的精度。指定一个小数位,单位位置为0,分数位置为负的。除法返回的小数位数至少与指定的相同。默认值是-4
,即小数点后四位。
function div(dividend, divisor, precision = -4) {
if (is_zero(dividend)) {
return zero;
}
if (is_zero(divisor)) {
return undefined;
}
let {coefficient, exponent} = dividend;
exponent -= divisor.exponent;
Scale the coefficient to the desired precision.
将系数缩放到所需的精度。
if (typeof precision !== "number") {
precision = number(precision);
}
if (exponent > precision) {
coefficient = big_integer.mul(
coefficient,
big_integer.power(big_integer.ten, exponent - precision)
);
exponent = precision;
}
let remainder;
[coefficient, remainder] = big_integer.divrem(
coefficient,
divisor.coefficient
);
Round the result if necessary.
如有必要,将结果四舍五入。
if (!big_integer.abs_lt(
big_integer.add(remainder, remainder),
divisor.coefficient
)) {
coefficient = big_integer.add(
coefficient,
big_integer.signum(dividend.coefficient)
);
}
return make_big_float(coefficient, exponent);
}
A big floating point number is normalized if the exponent is as close to zero as possible without losing significance.
如果让指数尽可能地接近 0 而又不失去意义,则大浮点数就标准了。
function normalize(a) {
let {coefficient, exponent} = a;
if (coefficient.length < 2) {
return zero;
}
If the exponent is zero, it is already normal.
如果指数为 0,就已经好了。
if (exponent !== 0) {
If the exponent is positive, multiply the coefficient by 10 ** exponent.
如果指数为正,则系数乘以10的指数次幂
if (exponent > 0) {
coefficient = big_integer.mul(
coefficient,
big_integer.power(big_integer.ten, exponent)
);
exponent = 0;
} else {
let quotient;
let remainder;
While the exponent is negative, if the coefficient is divisible by ten,
then we do the division and add 1 to the exponent.
如果指数为负,且系数能被10整除时,则除以10并将指数加1。
To help this go a little faster, we first try units of ten million,
reducing 7 zeros at a time.
为了加快速度,首先尝试1000万个单位,一次减少7个0。
while (exponent <= -7 && (coefficient[1] & 127) === 0) {
[quotient, remainder] = big_integer.divrem(
coefficient,
big_integer_ten_million
);
if (remainder !== big_integer.zero) {
break;
}
coefficient = quotient;
exponent += 7;
}
while (exponent < 0 && (coefficient[1] & 1) === 0) {
[quotient, remainder] = big_integer.divrem(
coefficient,
big_integer.ten
);
if (remainder !== big_integer.zero) {
break;
}
coefficient = quotient;
exponent += 1;
}
}
}
return make_big_float(coefficient, exponent);
}
The make function takes a big integer, or a string, or a JavaScript number, and convert it into big floating point. The conversion is exact.
make
函数接受一个大整数、字符串或一个JavaScript数字,并将其精确转换为大浮点数。
const number_pattern = /
^
( -? \d+ )
(?: \. ( \d* ) )?
(?: e ( -? \d+ ) )?
$
/;
// Capturing groups
// [1] int
// [2] frac
// [3] exp
function make(a, b) {
// (big_integer)
// (big_integer, exponent)
// (string)
// (string, radix)
// (number)
if (big_integer.is_big_integer(a)) {
return make_big_float(a, b || 0);
}
if (typeof a === "string") {
if (Number.isSafeInteger(b)) {
return make(big_integer.make(a, b), 0);
}
let parts = a.match(number_pattern);
if (parts) {
let frac = parts[2] || "";
return make(
big_integer.make(parts[1] + frac),
(Number(parts[3]) || 0) - frac.length
);
}
}
If a is a number, then we deconstruct it into its basis 2 exponent
and coefficient, and then reconstruct as a precise big float.
若 a 是 number
类型,则将其分解表示为 2 的幂乘以一个系数,然后据此重新计算出精确的浮点数。
if (typeof a === "number" && Number.isFinite(a)) {
if (a === 0) {
return zero;
}
let {sign, coefficient, exponent} = deconstruct(a);
if (sign < 0) {
coefficient = -coefficient;
}
coefficient = big_integer.make(coefficient);
If the exponent is negative, then we can divide by 2 ** abs(exponent).
如果指数为负,则可以除以 2 ** abs
(指数)。
if (exponent < 0) {
return normalize(div(
make(coefficient, 0),
make(big_integer.power(big_integer.two, -exponent), 0),
b
));
}
If the exponent is greater than zero, then we can multiply the
coefficient by 2 ** exponent.
如果指数为正,那么可以计算出 2 的指数次幂,然后乘以系数
if (exponent > 0) {
coefficient = big_integer.mul(
coefficient,
big_integer.power(big_integer.two, exponent)
);
exponent = 0;
}
return make(coefficient, exponent);
}
if (is_big_float(a)) {
return a;
}
}
The string function converts a big floating point number into a string. The conversion is exact. Most of the work involves inserting the decimal point and zero filling. A similar function for binary floating point would be vastly more complicated.
string函数将一个大浮点数精确转换成字符串。大多数工作涉及到插入小数点和零填充。对于二进制浮点数来说,类似的函数要复杂得多。
function string(a, radix) {
if (is_zero(a)) {
return "0";
}
if (is_big_float(radix)) {
radix = normalize(radix);
return (
(radix && radix.exponent === 0)
? big_integer.string(integer(a).coefficient, radix.coefficient)
: undefined
);
}
a = normalize(a);
let s = big_integer.string(big_integer.abs(a.coefficient));
if (a.exponent < 0) {
let point = s.length + a.exponent;
if (point <= 0) {
s = "0".repeat(1 - point) + s;
point = 1;
}
s = s.slice(0, point) + "." + s.slice(point);
} else if (a.exponent > 0) {
s += "0".repeat(a.exponent);
}
if (big_integer.is_negative(a.coefficient)) {
s = "-" + s;
}
return s;
}
There are two popular conventions for representing the decimal point: . period and , comma. Most countries use wun or the other. Within the borders of a country, it does not matter which. Both work well. But it is a hazard to international communication because 1,024 can have two very different interpretations. I predict that we will ultimately settle on . period as the international standard because programming languages use . period and ultimitely most information flows thru our programs written in those languages.
表示小数点有两种常用的惯例: .(句点)和,(逗号)。大多数国家使用wun或其他的。在一个国家,使用哪个并不重要。两者都很好。但它对国际交流是一种危害,因为1,024
个可以有两种截然不同的解释。我预测我们最终会达成一致,将.(句点)作为国际标准,因为编程语言使用的是.(句点),并且绝大多数的信息都是周期性、最终性的流动。
The scientific function converts a big floating point number into a string with the e notation.
scientific
函数将大浮点数转换成一个带e
的字符串。
function scientific(a) {
if (is_zero(a)) {
return "0";
}
a = normalize(a);
let s = big_integer.string(big_integer.abs(a.coefficient));
let e = a.exponent + s.length - 1;
if (s.length > 1) {
s = s.slice(0, 1) + "." + s.slice(1);
}
if (e !== 0) {
s += "e" + e;
}
if (big_integer.is_negative(a.coefficient)) {
s = "-" + s;
}
return s;
}
Finally, all of this goodness is exported as a module.
最后,所有这些优点都作为一个模块导出。
export default Object.freeze({
abs,
add,
div,
eq,
fraction,
integer,
is_big_float,
is_negative,
is_positive,
is_zero,
lt,
make,
mul,
neg,
normalize,
number,
scientific,
string,
sub,
zero
});
This is a library that is suitable for calculators, financial processing, or any other activity that requires correctness with decimal fractions. At this stage it is pretty minimal, but it could be enhanced with all of the functions and operators you could ever need.
这个库适用于计算器、财务处理或任何其他需要使用小数部分进行正确性验证的活动。在这个阶段,它非常小,但是可以使用你需要的任何函数和操作符来增强它。
I used this library to expose the truth in Chapter 2. Whilst it is very powerful, I think a more conventional fixed size decimal floating point type would be better suited to most applications. The problem with JavaScript’s number type isn’t its limited range or precision. The problem is that it can not accurately represent the numbers that are most interesting to humanity: the numbers made of decimal digits. So something like DEC64 would be a better choice for the next language. www.DEC64.com
我在第二章中使用这个库来揭示真相。虽然它非常强大,但我认为更传统的固定大小的十进制浮点类型更适合大多数应用程序。JavaScript数字类型的问题不在于其有限的范围或精度,而是它不能准确地表示人类最感兴趣的数字:由十进制数字组成的数字。所以对于下一种语言来说,类似于DEC64会是更好的选择。www.DEC64.com
Neither binary floating point nor decimal floating point can exactly represent things like 100/3. We will consider that problem next.
二进制浮点数和十进制浮点数都不能准确地表示像 100/3 这样的数。接下来我们会考虑此问题。