BobHanson / java2script

Java2Script provides an Eclipse Java to JavaScript transpiler, with a nearly complete implementation of the Java Virtual Machine with AWT and Swing in JavaScript, with simple, automated parallel creation of both class files and js files. To date, over 600 applets have been converted.
https://chemapps.stolaf.edu/swingjs/examples.htm
Other
24 stars 11 forks source link

64-bit long/Long - finally! java2script 3.3.1 #202

Closed BobHanson closed 3 years ago

BobHanson commented 3 years ago

Really just closure on the original issue of not supporting 64-bit long. java2script 3.3.1 introduces 64-bit long fully implemented in Math and Long, as well as complete Boxing/Unboxing support with Long and (long). The methods can be found in j2sClazz.js, where they all start with Long.$..... For example, Long.$add, Long.$sub, Long.$mul. Specifically, the following 28 methods were implemented:

$add $and $cmp $div $dup $dval $eq $fval $ge $gt $inc $ival $le $lt $lval $mod $mul $n $ne $neg $not $or $s $sign $sl $sr $sub $usr

All of these methods accept a simple number or a three-element array. In addition, the method Clazz.toLong() works as a general casting method from String and other primitives (double, float, int, short, byte) to long.

For performance, the implementation uses simple numbers and number math for long values up to 2^53-1 (9007199254740991). Within or outside that range, a simple Array(3) is used. These arrays are just standard JavaScript arrays; they have no special fields -- exactly as for primitives in Java.

The array is just [r,m,s] where r is the "remainder" low 24 binary digits, m is the higher part of the mantissa, and s is the sign flag (1,0,-1). So [3,0,1] is +3. Long.MAX_VALUE is [16777215,549755813887,1]; Long.MIN_VALUE is [0,549755813888,-1]. In general, n = (r + m 2^24) s.

Internally, this array is convertable to a three element array [r,l,h], (remainder, low, high), which takes the form of the standard twos-complement (8000000000000000 - 7FFFFFFFFFFFFFFF) but broken into manageable integers of 24, 24, and 16 bits, respectively. These sizes were chosen to match 3, 3, and 2 bytes, with only the very largest long values having anything in the high field other than 8000 to FFFF for negative numbers.

An interesting and little discussed aspect of storing integers in the mantissa of 64-bit floats is that they can overflow 53 bits and still be fully operable. JavaScript has "sparse" numbers way past 2^53, and those numbers appropriately add, subtract, divide, multiply, and mod. In fact, the java2script 3.3.1 scheme supports storage and manipulation of integers in full precision up to 78 bits -- 2^78 - 1 (1fffffffffffffffffff). Thus, 40000000000016777216 is fully representable in JavaScript (even though it displays as 40000000000016780000). For example:

a = 40000000000016777216; b = BigInt(a); b == 40000000000016777216n true

and

BigInt(a/0x10000) == b/0x10000n true

The only restriction is that we need to do the math on quantities that are have MOD 0x1000 of 0 (clear lower 12 bits -- needed for the Float64 exponent). So that is what we do. In any case, we don't need anything like that amount of size. Still, it turns out to be useful for simple high-number multiplication and division without the necessity of BigInt.

For example, addition is basically just :

r = a[0] + s b[0] b[2]; m = a[1] + s b[1] b[2]; return [r, m, s]

(There are a few other considerations, certainly.)

Multiplication is simply:

a = toLongRLH(a);
b = toLongRLH(b);
var r = a[0]*b[0];
var c = (r-r%MAXR)/MAXR;
var l = a[0]*b[1]+a[1]*b[0]+c;
c = (l-l%MAXL)/MAXL;
var h = a[0]*b[2]+a[2]*b[0]+a[1]*b[1]+c;
a = fromLongRLH([r,l,h]);

where we multiply at most two 24-bit numbers -- well within the 52-bit Float64 mantissa allowance.

In fact, since Safari does not (yet) support BigInt, the implementation does not utilize it. We fall back to Java's BigInteger for full 64-bit division, but that is all, and that of course just uses long and int itself. However, if BigInt is available, the implementation utilizes it specifically for long/long division instead of java.math.BigInteger. Everything else is implemented in simple JavaScript.

That said, we also had to consider how to manage these array structures within the java2script transpiler framework.
It occurred to me that we have full control over the transpiler (of course!). The trick was to design an immutable "long" object that could work also with small (< 2^53) integers as well as those in the "high long" range of 2^53 - 2^64 (above). Then to carefully identify all instances of long conversion in Java abstract syntax tree processing, adjusting the transpiler to properly cast into and out of this long format using the given static methods. This took some careful programming but in the end was not too bad. See Java2ScriptVisitor.java

warownia1 commented 9 months ago

Looks like BigInt is already supported by Safari as well as all other major browsers. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility

BobHanson commented 9 months ago

Yes. Not a huge deal. SwingJS has long supported long and bigint.

On Mon, Jan 29, 2024, 7:12 AM Mateusz Warowny @.***> wrote:

Looks like BigInt is already supported by Safari ass well as all other major browsers.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#browser_compatibility

— Reply to this email directly, view it on GitHub https://github.com/BobHanson/java2script/issues/202#issuecomment-1914669442, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEHNCW7FL5G5XUV2HR6YZI3YQ6N3BAVCNFSM4VP42OAKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOJRGQ3DMOJUGQZA . You are receiving this because you modified the open/close state.Message ID: @.***>