diff --git a/std/cpp/_std/haxe/numeric/Int64Native.hx b/std/cpp/_std/haxe/numeric/Int64Native.hx index ed63c3b232a..fd54a64e3f3 100644 --- a/std/cpp/_std/haxe/numeric/Int64Native.hx +++ b/std/cpp/_std/haxe/numeric/Int64Native.hx @@ -124,6 +124,11 @@ private abstract Int64NativeImpl(cpp.Int64) from cpp.Int64 to cpp.Int64 { public static function sub(a:Int64Native, b:Int64Native):Int64Native; public static function mul(a:Int64Native, b:Int64Native):Int64Native; public static function divMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native}; + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native}; + public static function utoString(x:Int64Native):String; + public static function uparseString(sParam:String):Int64Native; + public static function ufromFloat(f:Float):Int64Native; + public static function utoFloat(x:Int64Native):Float; public static function eq(a:Int64Native, b:Int64Native):Bool; public static function neq(a:Int64Native, b:Int64Native):Bool; public static function complement(x:Int64Native):Int64Native; @@ -235,5 +240,25 @@ private abstract Int64NativeImpl(cpp.Int64) from cpp.Int64 to cpp.Int64 { public static inline function fromFloat(f:Float):Int64Native { return haxe.numeric.Int64Helper.fromFloat(f); } + + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native} { + return haxe.numeric.UInt64Helper.udivMod(dividend, divisor); + } + + public static function utoString(x:Int64Native):String { + return haxe.numeric.UInt64Helper.utoString(x); + } + + public static function uparseString(sParam:String):Int64Native { + return haxe.numeric.UInt64Helper.parseString(sParam); + } + + public static function ufromFloat(f:Float):Int64Native { + return haxe.numeric.UInt64Helper.fromFloat(f); + } + + public static function utoFloat(x:Int64Native):Float { + return haxe.numeric.UInt64Helper.toFloat(x); + } #end } diff --git a/std/eval/_std/haxe/numeric/Int64Native.hx b/std/eval/_std/haxe/numeric/Int64Native.hx index e7dec6eedce..89f61b3460f 100644 --- a/std/eval/_std/haxe/numeric/Int64Native.hx +++ b/std/eval/_std/haxe/numeric/Int64Native.hx @@ -150,4 +150,24 @@ private abstract Int64NativeImpl(EvalInt64) from EvalInt64 to EvalInt64 { public static inline function fromFloat(f:Float):Int64Native { return haxe.numeric.Int64Helper.fromFloat(f); } + + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native} { + return haxe.numeric.UInt64Helper.udivMod(dividend, divisor); + } + + public static function utoString(x:Int64Native):String { + return haxe.numeric.UInt64Helper.utoString(x); + } + + public static function uparseString(sParam:String):Int64Native { + return haxe.numeric.UInt64Helper.parseString(sParam); + } + + public static function ufromFloat(f:Float):Int64Native { + return haxe.numeric.UInt64Helper.fromFloat(f); + } + + public static function utoFloat(x:Int64Native):Float { + return haxe.numeric.UInt64Helper.toFloat(x); + } } diff --git a/std/haxe/UInt64.hx b/std/haxe/UInt64.hx new file mode 100644 index 00000000000..0916e37b2f8 --- /dev/null +++ b/std/haxe/UInt64.hx @@ -0,0 +1,321 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe; + +import haxe.numeric.Int64Native; + +/** + A cross-platform unsigned 64-bit integer type. + + Built on top of `haxe.numeric.Int64Native`, sharing the same backing + representation as `haxe.Int64`. All bit-identical operations (addition, + subtraction, multiplication, bitwise) delegate directly to `Int64Native`. + Operations that differ for unsigned interpretation (division, comparison, + right shift, toString) use unsigned-specific implementations provided + by `Int64Native` (udivMod, utoString, etc.), which native targets can + override for better performance. +**/ +abstract UInt64(Int64Native) from Int64Native to Int64Native { + private inline function new(x:Int64Native) + this = x; + + /** + Makes a copy of `this` UInt64. + **/ + public inline function copy():UInt64 + return make(high, low); + + /** + Construct a UInt64 from two 32-bit words `high` and `low`. + **/ + public static inline function make(high:Int32, low:Int32):UInt64 + return new UInt64(Int64Native.make(high, low)); + + /** + Returns a UInt64 with the value of the Int `x`. + `x` is sign-extended to fill 64 bits (same bit pattern as `Int64.ofInt`). + **/ + @:from public static inline function ofInt(x:Int):UInt64 + return new UInt64(Int64Native.ofInt(x)); + + /** + Returns the low 32 bits of `x` as an Int. + The top 32 bits are discarded. + **/ + public static inline function toInt(x:UInt64):Int + return x.low; + + /** + Returns `true` if `x` is exactly zero. + **/ + public static inline function isZero(x:UInt64):Bool + return Int64Native.isZero(x); + + /** + Compares `a` and `b` as unsigned 64-bit integers. + Returns a negative value if `a < b`, positive if `a > b`, + or 0 if `a == b`. + **/ + public static inline function compare(a:UInt64, b:UInt64):Int + return Int64Native.ucompare(a, b); + + /** + Returns an unsigned decimal `String` representation of `x`. + **/ + public inline function toString():String + return Int64Native.utoString(this); + + /** + Parses an unsigned decimal string into a UInt64. + Throws on invalid input, negative values, or overflow. + **/ + public static inline function parseString(sParam:String):UInt64 { + return Int64Native.uparseString(sParam); + } + + /** + Converts a non-negative Float to UInt64. + Throws on negative, NaN, Infinite, or values exceeding `2^53-1`. + **/ + public static inline function fromFloat(f:Float):UInt64 { + return Int64Native.ufromFloat(f); + } + + /** + Converts this unsigned 64-bit value to Float. + Values above `2^53` may lose precision. + **/ + public inline function toFloat():Float { + return Int64Native.utoFloat(this); + } + + /** + Performs unsigned integer division of `dividend` by `divisor`. + Returns `{ quotient : UInt64, modulus : UInt64 }`. + **/ + public static function divMod(dividend:UInt64, divisor:UInt64):{quotient:UInt64, modulus:UInt64} { + var r = Int64Native.udivMod(dividend, divisor); + return {quotient: r.quotient, modulus: r.modulus}; + } + + /** + Reinterprets the bits of an `Int64` as a `UInt64`. + **/ + public static inline function fromInt64(x:Int64):UInt64 + return new UInt64((x : Int64Native)); + + /** + Reinterprets the bits of this `UInt64` as an `Int64`. + **/ + public inline function toInt64():Int64 + return (this : Int64Native); + + /** + Returns the two's complement negation of `x`. + **/ + @:op(-A) public static inline function neg(x:UInt64):UInt64 + return Int64Native.neg(x); + + @:op(++A) private inline function preIncrement():UInt64 { + this = Int64Native.add(this, Int64Native.ofInt(1)); + return this; + } + + @:op(A++) private inline function postIncrement():UInt64 { + var ret = this; + this = Int64Native.add(this, Int64Native.ofInt(1)); + return ret; + } + + @:op(--A) private inline function preDecrement():UInt64 { + this = Int64Native.sub(this, Int64Native.ofInt(1)); + return this; + } + + @:op(A--) private inline function postDecrement():UInt64 { + var ret = this; + this = Int64Native.sub(this, Int64Native.ofInt(1)); + return ret; + } + + /** + Returns the sum of `a` and `b`. + **/ + @:op(A + B) public static inline function add(a:UInt64, b:UInt64):UInt64 + return Int64Native.add(a, b); + + @:op(A + B) @:commutative private static inline function addInt(a:UInt64, b:Int):UInt64 + return add(a, b); + + /** + Returns `a` minus `b`. + **/ + @:op(A - B) public static inline function sub(a:UInt64, b:UInt64):UInt64 + return Int64Native.sub(a, b); + + @:op(A - B) private static inline function subInt(a:UInt64, b:Int):UInt64 + return sub(a, b); + + @:op(A - B) private static inline function intSub(a:Int, b:UInt64):UInt64 + return sub(a, b); + + /** + Returns the product of `a` and `b`. + **/ + @:op(A * B) public static inline function mul(a:UInt64, b:UInt64):UInt64 + return Int64Native.mul(a, b); + + @:op(A * B) @:commutative private static inline function mulInt(a:UInt64, b:Int):UInt64 + return mul(a, b); + + /** + Returns the unsigned quotient of `a` divided by `b`. + **/ + @:op(A / B) public static inline function div(a:UInt64, b:UInt64):UInt64 + return Int64Native.udivMod(a, b).quotient; + + @:op(A / B) private static inline function divInt(a:UInt64, b:Int):UInt64 + return div(a, b); + + @:op(A / B) private static inline function intDiv(a:Int, b:UInt64):UInt64 + return div(a, b); + + /** + Returns the unsigned modulus of `a` divided by `b`. + **/ + @:op(A % B) public static inline function mod(a:UInt64, b:UInt64):UInt64 + return Int64Native.udivMod(a, b).modulus; + + @:op(A % B) private static inline function modInt(a:UInt64, b:Int):UInt64 + return mod(a, b); + + @:op(A % B) private static inline function intMod(a:Int, b:UInt64):UInt64 + return mod(a, b); + + /** + Returns `true` if `a` is equal to `b`. + **/ + @:op(A == B) public static inline function eq(a:UInt64, b:UInt64):Bool + return Int64Native.eq(a, b); + + @:op(A == B) @:commutative private static inline function eqInt(a:UInt64, b:Int):Bool + return eq(a, b); + + /** + Returns `true` if `a` is not equal to `b`. + **/ + @:op(A != B) public static inline function neq(a:UInt64, b:UInt64):Bool + return Int64Native.neq(a, b); + + @:op(A != B) @:commutative private static inline function neqInt(a:UInt64, b:Int):Bool + return neq(a, b); + + @:op(A < B) private static inline function lt(a:UInt64, b:UInt64):Bool + return compare(a, b) < 0; + + @:op(A < B) private static inline function ltInt(a:UInt64, b:Int):Bool + return lt(a, b); + + @:op(A < B) private static inline function intLt(a:Int, b:UInt64):Bool + return lt(a, b); + + @:op(A <= B) private static inline function lte(a:UInt64, b:UInt64):Bool + return compare(a, b) <= 0; + + @:op(A <= B) private static inline function lteInt(a:UInt64, b:Int):Bool + return lte(a, b); + + @:op(A <= B) private static inline function intLte(a:Int, b:UInt64):Bool + return lte(a, b); + + @:op(A > B) private static inline function gt(a:UInt64, b:UInt64):Bool + return compare(a, b) > 0; + + @:op(A > B) private static inline function gtInt(a:UInt64, b:Int):Bool + return gt(a, b); + + @:op(A > B) private static inline function intGt(a:Int, b:UInt64):Bool + return gt(a, b); + + @:op(A >= B) private static inline function gte(a:UInt64, b:UInt64):Bool + return compare(a, b) >= 0; + + @:op(A >= B) private static inline function gteInt(a:UInt64, b:Int):Bool + return gte(a, b); + + @:op(A >= B) private static inline function intGte(a:Int, b:UInt64):Bool + return gte(a, b); + + /** + Returns the bitwise NOT of `a`. + **/ + @:op(~A) private static inline function complement(a:UInt64):UInt64 + return Int64Native.complement(a); + + /** + Returns the bitwise AND of `a` and `b`. + **/ + @:op(A & B) public static inline function and(a:UInt64, b:UInt64):UInt64 + return Int64Native.and(a, b); + + /** + Returns the bitwise OR of `a` and `b`. + **/ + @:op(A | B) public static inline function or(a:UInt64, b:UInt64):UInt64 + return Int64Native.or(a, b); + + /** + Returns the bitwise XOR of `a` and `b`. + **/ + @:op(A ^ B) public static inline function xor(a:UInt64, b:UInt64):UInt64 + return Int64Native.xor(a, b); + + /** + Returns `a` left-shifted by `b` bits. + **/ + @:op(A << B) public static inline function shl(a:UInt64, b:Int):UInt64 + return Int64Native.shl(a, b); + + /** + Returns `a` right-shifted by `b` bits with zero-extension (logical shift). + For unsigned types, both `>>` and `>>>` perform logical (unsigned) right shift. + **/ + @:op(A >> B) public static inline function shr(a:UInt64, b:Int):UInt64 + return Int64Native.ushr(a, b); + + /** + Returns `a` right-shifted by `b` bits with zero-extension (logical shift). + **/ + @:op(A >>> B) public static inline function ushr(a:UInt64, b:Int):UInt64 + return Int64Native.ushr(a, b); + + public var high(get, never):Int32; + + private inline function get_high() + return this.high; + + public var low(get, never):Int32; + + private inline function get_low() + return this.low; +} diff --git a/std/haxe/numeric/Int64Native.hx b/std/haxe/numeric/Int64Native.hx index 6d3dedd0b6c..12d89bd5245 100644 --- a/std/haxe/numeric/Int64Native.hx +++ b/std/haxe/numeric/Int64Native.hx @@ -233,6 +233,79 @@ private class Int64NativeImpl { #end } + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native} { + if (divisor.high == 0) { + switch (divisor.low) { + case 0: + throw "divide by zero"; + case 1: + return {quotient: make(dividend.high, dividend.low), modulus: ofInt(0)}; + } + } + + var modulus = make(dividend.high, dividend.low); + var quotient = ofInt(0); + var mask = ofInt(1); + + while (!isNeg(divisor)) { + var cmp = ucompare(divisor, modulus); + divisor = shl(divisor, 1); + mask = shl(mask, 1); + if (cmp >= 0) + break; + } + + while (!isZero(mask)) { + if (ucompare(modulus, divisor) >= 0) { + quotient = or(quotient, mask); + modulus = sub(modulus, divisor); + } + mask = ushr(mask, 1); + divisor = ushr(divisor, 1); + } + + return { + quotient: quotient, + modulus: modulus + }; + } + + public static function utoString(x:Int64Native):String { + if (x.high == 0 && x.low == 0) + return "0"; + var d3 = (x.high >>> 16) & 0xFFFF; + var d2 = x.high & 0xFFFF; + var d1 = (x.low >>> 16) & 0xFFFF; + var d0 = x.low & 0xFFFF; + var str = ""; + while (d3 != 0 || d2 != 0 || d1 != 0 || d0 != 0) { + var r = d3 % 10; + d3 = Std.int(d3 / 10); + var v = r * 65536 + d2; + d2 = Std.int(v / 10); + r = v % 10; + v = r * 65536 + d1; + d1 = Std.int(v / 10); + r = v % 10; + v = r * 65536 + d0; + d0 = Std.int(v / 10); + str = (v % 10) + str; + } + return str; + } + + public static function uparseString(sParam:String):Int64Native { + return haxe.numeric.UInt64Helper.parseString(sParam); + } + + public static function ufromFloat(f:Float):Int64Native { + return haxe.numeric.UInt64Helper.fromFloat(f); + } + + public static function utoFloat(x:Int64Native):Float { + return haxe.numeric.UInt64Helper.toFloat(x); + } + public static inline function parseString(sParam:String):Int64Native { return haxe.numeric.Int64Helper.parseString(sParam); } diff --git a/std/haxe/numeric/UInt64Helper.hx b/std/haxe/numeric/UInt64Helper.hx new file mode 100644 index 00000000000..bb0ccc5f539 --- /dev/null +++ b/std/haxe/numeric/UInt64Helper.hx @@ -0,0 +1,189 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + Helpers for unsigned 64-bit integer operations on top of `Int64Native`. + + These implement the operations that differ between signed and unsigned + interpretation: division, modulo, toString, parseString, and fromFloat. + All other 64-bit operations (add, sub, mul, bitwise, shifts) are + bit-identical for signed and unsigned and can use `Int64Native` directly. +**/ +class UInt64Helper { + /** + Performs unsigned 64-bit integer division. + Returns `{ quotient, modulus }` treating both operands as unsigned. + + This is the same algorithm as the signed `Int64Native.divMod` but + without sign handling, demonstrating that unsigned operations can + be built on top of the shared `Int64Native` representation. + **/ + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native} { + if (divisor.high == 0) { + switch (divisor.low) { + case 0: + throw "divide by zero"; + case 1: + return {quotient: Int64Native.make(dividend.high, dividend.low), modulus: Int64Native.ofInt(0)}; + } + } + + var modulus = Int64Native.make(dividend.high, dividend.low); + var quotient = Int64Native.ofInt(0); + var mask = Int64Native.ofInt(1); + + while (!Int64Native.isNeg(divisor)) { + var cmp = Int64Native.ucompare(divisor, modulus); + divisor = Int64Native.shl(divisor, 1); + mask = Int64Native.shl(mask, 1); + if (cmp >= 0) + break; + } + + while (!Int64Native.isZero(mask)) { + if (Int64Native.ucompare(modulus, divisor) >= 0) { + quotient = Int64Native.or(quotient, mask); + modulus = Int64Native.sub(modulus, divisor); + } + mask = Int64Native.ushr(mask, 1); + divisor = Int64Native.ushr(divisor, 1); + } + + return { + quotient: quotient, + modulus: modulus + }; + } + + /** + Returns the unsigned decimal string representation of the given 64-bit value. + + Uses the same 16-bit chunk algorithm as the signed toString but + treats all bits as unsigned (no negative sign handling). + **/ + public static function utoString(x:Int64Native):String { + if (x.high == 0 && x.low == 0) + return "0"; + var d3 = (x.high >>> 16) & 0xFFFF; + var d2 = x.high & 0xFFFF; + var d1 = (x.low >>> 16) & 0xFFFF; + var d0 = x.low & 0xFFFF; + var str = ""; + while (d3 != 0 || d2 != 0 || d1 != 0 || d0 != 0) { + var r = d3 % 10; + d3 = Std.int(d3 / 10); + var v = r * 65536 + d2; + d2 = Std.int(v / 10); + r = v % 10; + v = r * 65536 + d1; + d1 = Std.int(v / 10); + r = v % 10; + v = r * 65536 + d0; + d0 = Std.int(v / 10); + str = (v % 10) + str; + } + return str; + } + + /** + Parses an unsigned decimal string into an `Int64Native` value. + Throws on invalid input, negative values, or overflow. + **/ + public static function parseString(sParam:String):Int64Native { + var base = Int64Native.ofInt(10); + var current = Int64Native.ofInt(0); + var multiplier = Int64Native.ofInt(1); + + var s = StringTools.trim(sParam); + if (s.charAt(0) == "-") + throw "NumberFormatError: negative value for unsigned type"; + + var len = s.length; + for (i in 0...len) { + var digitInt = s.charCodeAt(len - 1 - i) - '0'.code; + + if (digitInt < 0 || digitInt > 9) + throw "NumberFormatError"; + + if (digitInt != 0) { + var digit = Int64Native.ofInt(digitInt); + var prev = current; + current = Int64Native.add(current, Int64Native.mul(multiplier, digit)); + if (Int64Native.ucompare(current, prev) < 0) + throw "NumberFormatError: Overflow"; + } + + multiplier = Int64Native.mul(multiplier, base); + } + return current; + } + + /** + Converts a non-negative `Float` to an unsigned `Int64Native` value. + The float must be in the range `[0, 2^53-1]` to avoid precision loss. + Throws on negative, NaN, Infinite, or out-of-range values. + **/ + public static function fromFloat(f:Float):Int64Native { + if (Math.isNaN(f) || !Math.isFinite(f)) + throw "Number is NaN or Infinite"; + + var noFractions = f - (f % 1); + + if (noFractions < 0) + throw "Conversion: negative value for unsigned type"; + + if (noFractions > 9007199254740991) + throw "Conversion overflow"; + + var result = Int64Native.ofInt(0); + var rest = noFractions; + + var i = 0; + while (rest >= 1) { + var curr = rest % 2; + rest = rest / 2; + if (curr >= 1) { + result = Int64Native.add(result, Int64Native.shl(Int64Native.ofInt(1), i)); + } + i++; + } + + return result; + } + + /** + Converts an unsigned 64-bit value to its `Float` representation. + All 64 bits are treated as an unsigned magnitude. + Values above `2^53` may lose precision. + **/ + public static function toFloat(x:Int64Native):Float { + var f:Float = x.low; + if (f < 0) + f += 4294967296.0; + var h:Float = x.high; + if (h < 0) + h += 4294967296.0; + return h * 4294967296.0 + f; + } +} diff --git a/std/hl/_std/haxe/numeric/Int64Native.hx b/std/hl/_std/haxe/numeric/Int64Native.hx index 7d3fb69a7a2..bd8985571b6 100644 --- a/std/hl/_std/haxe/numeric/Int64Native.hx +++ b/std/hl/_std/haxe/numeric/Int64Native.hx @@ -147,6 +147,26 @@ private abstract Int64NativeImpl(hl.I64) from hl.I64 to hl.I64 { public static inline function fromFloat(f:Float):Int64Native { return haxe.numeric.Int64Helper.fromFloat(f); } + + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native} { + return haxe.numeric.UInt64Helper.udivMod(dividend, divisor); + } + + public static function utoString(x:Int64Native):String { + return haxe.numeric.UInt64Helper.utoString(x); + } + + public static function uparseString(sParam:String):Int64Native { + return haxe.numeric.UInt64Helper.parseString(sParam); + } + + public static function ufromFloat(f:Float):Int64Native { + return haxe.numeric.UInt64Helper.fromFloat(f); + } + + public static function utoFloat(x:Int64Native):Float { + return haxe.numeric.UInt64Helper.toFloat(x); + } } #end diff --git a/std/jvm/_std/haxe/numeric/Int64Native.hx b/std/jvm/_std/haxe/numeric/Int64Native.hx index 71090f8f028..ef022ee0181 100644 --- a/std/jvm/_std/haxe/numeric/Int64Native.hx +++ b/std/jvm/_std/haxe/numeric/Int64Native.hx @@ -150,4 +150,24 @@ private abstract Int64NativeImpl(jvm.Int64) from jvm.Int64 to jvm.Int64 { public static inline function fromFloat(f:Float):Int64Native { return haxe.numeric.Int64Helper.fromFloat(f); } + + public static function udivMod(dividend:Int64Native, divisor:Int64Native):{quotient:Int64Native, modulus:Int64Native} { + return haxe.numeric.UInt64Helper.udivMod(dividend, divisor); + } + + public static function utoString(x:Int64Native):String { + return haxe.numeric.UInt64Helper.utoString(x); + } + + public static function uparseString(sParam:String):Int64Native { + return haxe.numeric.UInt64Helper.parseString(sParam); + } + + public static function ufromFloat(f:Float):Int64Native { + return haxe.numeric.UInt64Helper.fromFloat(f); + } + + public static function utoFloat(x:Int64Native):Float { + return haxe.numeric.UInt64Helper.toFloat(x); + } } diff --git a/tests/unit/src/unit/TestMain.hx b/tests/unit/src/unit/TestMain.hx index e9ed4c285d1..c564c6e4081 100644 --- a/tests/unit/src/unit/TestMain.hx +++ b/tests/unit/src/unit/TestMain.hx @@ -47,6 +47,7 @@ function main() { new TestResource(), new TestInt32(), new TestInt64(), + new TestUInt64(), new TestReflect(), new TestSerialize(), new TestSerializerCrossTarget(), diff --git a/tests/unit/src/unit/TestUInt64.hx b/tests/unit/src/unit/TestUInt64.hx new file mode 100644 index 00000000000..161806d2925 --- /dev/null +++ b/tests/unit/src/unit/TestUInt64.hx @@ -0,0 +1,422 @@ +package unit; + +import haxe.UInt64; + +class TestUInt64 extends Test { + public function testMake() { + var a:UInt64; + + a = UInt64.make(0, 42); + eq(a.high, 0); + eq(a.low, 42); + + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); + eq(a.high, 0xFFFFFFFF); + eq(a.low, 0xFFFFFFFF); + + a = UInt64.make(0x80000000, 0); + eq(a.high, 0x80000000); + eq(a.low, 0); + + a = UInt64.make(1, 0); + eq(a.high, 1); + eq(a.low, 0); + } + + public function testOfInt() { + var a:UInt64; + + a = UInt64.ofInt(0); + eq(a.high, 0); + eq(a.low, 0); + + a = UInt64.ofInt(1); + eq(a.high, 0); + eq(a.low, 1); + + // Negative int is sign-extended (same bit pattern as Int64) + a = UInt64.ofInt(-1); + eq(a.high, 0xFFFFFFFF); + eq(a.low, 0xFFFFFFFF); + } + + public function testToInt() { + eq(UInt64.toInt(UInt64.make(0, 42)), 42); + eq(UInt64.toInt(UInt64.make(0, 0)), 0); + // toInt returns low 32 bits even when high is nonzero + eq(UInt64.toInt(UInt64.make(1, 5)), 5); + // Negative low word returned as-is + eq(UInt64.toInt(UInt64.make(0, 0xFFFFFFFF)), 0xFFFFFFFF); + } + + public function testToString() { + var a:UInt64; + + a = UInt64.make(0, 0); + eq(Std.string(a), "0"); + + a = UInt64.make(0, 1); + eq(Std.string(a), "1"); + + a = UInt64.make(0, 1000000); + eq(Std.string(a), "1000000"); + + // 2^32 = 4294967296 + a = UInt64.make(1, 0); + eq(Std.string(a), "4294967296"); + + // MAX_UINT64 = 2^64 - 1 = 18446744073709551615 + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); + eq(Std.string(a), "18446744073709551615"); + + // 2^63 = 9223372036854775808 (would be MIN_INT64 in signed) + a = UInt64.make(0x80000000, 0); + eq(Std.string(a), "9223372036854775808"); + + // 2^63 - 1 = 9223372036854775807 + a = UInt64.make(0x7FFFFFFF, 0xFFFFFFFF); + eq(Std.string(a), "9223372036854775807"); + } + + public function testComparison() { + var a:UInt64, b:UInt64; + + // Equal values + a = UInt64.make(0, 1); + b = UInt64.make(0, 1); + t(a == b); + f(a != b); + t(a <= b); + f(a < b); + t(a >= b); + f(a > b); + eq(UInt64.compare(a, b), 0); + + // Simple ordering + a = UInt64.make(0, 10); + b = UInt64.make(0, 20); + f(a == b); + t(a != b); + t(a < b); + t(a <= b); + f(a > b); + f(a >= b); + t(UInt64.compare(a, b) < 0); + + // Key unsigned test: 0x80000000_00000000 > 0x7FFFFFFF_FFFFFFFF + // (In signed Int64, 0x80000000_00000000 would be negative and LESS than 0x7FFFFFFF_FFFFFFFF) + a = UInt64.make(0x80000000, 0); + b = UInt64.make(0x7FFFFFFF, 0xFFFFFFFF); + t(a > b); + f(a < b); + f(a == b); + t(UInt64.compare(a, b) > 0); + + // MAX > 0 + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); + b = UInt64.make(0, 0); + t(a > b); + f(a < b); + + // High-word comparison + a = UInt64.make(2, 0); + b = UInt64.make(1, 0xFFFFFFFF); + t(a > b); + + // Both have high bit set + a = UInt64.make(0xFFFFFFFF, 0); + b = UInt64.make(0x80000000, 0); + t(a > b); + } + + public function testAddition() { + var a:UInt64, b:UInt64; + + a = UInt64.make(0, 100); + b = UInt64.make(0, 200); + uint64eq(a + b, UInt64.make(0, 300)); + + // Carry from low to high + a = UInt64.make(0, 0xFFFFFFFF); + b = UInt64.make(0, 1); + uint64eq(a + b, UInt64.make(1, 0)); + + // Wrap around at 2^64 + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); + b = UInt64.make(0, 1); + uint64eq(a + b, UInt64.make(0, 0)); + + // UInt64 + Int + a = UInt64.make(0, 100); + uint64eq(a + 50, UInt64.make(0, 150)); + } + + public function testSubtraction() { + var a:UInt64, b:UInt64; + + a = UInt64.make(0, 300); + b = UInt64.make(0, 100); + uint64eq(a - b, UInt64.make(0, 200)); + + // Borrow from high to low + a = UInt64.make(1, 0); + b = UInt64.make(0, 1); + uint64eq(a - b, UInt64.make(0, 0xFFFFFFFF)); + + // Wrap around at 0 (underflow wraps to MAX) + a = UInt64.make(0, 0); + b = UInt64.make(0, 1); + uint64eq(a - b, UInt64.make(0xFFFFFFFF, 0xFFFFFFFF)); + } + + public function testMultiplication() { + var a:UInt64, b:UInt64; + + a = UInt64.make(0, 1000); + b = UInt64.make(0, 1000); + uint64eq(a * b, UInt64.make(0, 1000000)); + + // Multiplication with overflow into high word + a = UInt64.make(0, 0x10000); + b = UInt64.make(0, 0x10000); + uint64eq(a * b, UInt64.make(1, 0)); + + // UInt64 * Int + a = UInt64.make(0, 7); + uint64eq(a * 6, UInt64.make(0, 42)); + } + + public function testDivision() { + var a:UInt64, b:UInt64; + + // Simple division + a = UInt64.make(0, 100); + b = UInt64.make(0, 10); + uint64eq(a / b, UInt64.make(0, 10)); + uint64eq(a % b, UInt64.make(0, 0)); + + // Division with remainder + a = UInt64.make(0, 103); + b = UInt64.make(0, 10); + uint64eq(a / b, UInt64.make(0, 10)); + uint64eq(a % b, UInt64.make(0, 3)); + + // Key unsigned test: divide a value > MAX_INT64 + // 2^63 / 2 = 2^62 + a = UInt64.make(0x80000000, 0); + b = UInt64.make(0, 2); + uint64eq(a / b, UInt64.make(0x40000000, 0)); + uint64eq(a % b, UInt64.make(0, 0)); + + // MAX_UINT64 / 2 = 2^63 - 1 (remainder 1) + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); + b = UInt64.make(0, 2); + uint64eq(a / b, UInt64.make(0x7FFFFFFF, 0xFFFFFFFF)); + uint64eq(a % b, UInt64.make(0, 1)); + + // divMod + a = UInt64.make(0, 47); + b = UInt64.make(0, 5); + var result = UInt64.divMod(a, b); + uint64eq(result.quotient, UInt64.make(0, 9)); + uint64eq(result.modulus, UInt64.make(0, 2)); + + // Divide by self + a = UInt64.make(0x12345678, 0x9ABCDEF0); + uint64eq(a / a, UInt64.make(0, 1)); + uint64eq(a % a, UInt64.make(0, 0)); + + // Divide smaller by larger + a = UInt64.make(0, 5); + b = UInt64.make(0, 100); + uint64eq(a / b, UInt64.make(0, 0)); + uint64eq(a % b, UInt64.make(0, 5)); + + // Divide by zero throws + var threw = false; + try { + UInt64.divMod(UInt64.make(0, 1), UInt64.make(0, 0)); + } catch (e:Dynamic) { + threw = true; + } + t(threw); + + // Large dividend, large divisor (both with high bit set) + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); // MAX + b = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); // MAX + uint64eq(a / b, UInt64.make(0, 1)); + uint64eq(a % b, UInt64.make(0, 0)); + } + + public function testBitwiseOps() { + var a:UInt64, b:UInt64; + + a = UInt64.make(0x0FFFFFFF, 0x00000001); + b = UInt64.make(0, 0x8FFFFFFF); + uint64eq(a & b, UInt64.make(0, 1)); + uint64eq(a | b, UInt64.make(0x0FFFFFFF, 0x8FFFFFFF)); + uint64eq(a ^ b, UInt64.make(0x0FFFFFFF, 0x8FFFFFFE)); + uint64eq(~a, UInt64.make(0xF0000000, 0xFFFFFFFE)); + } + + public function testShifts() { + var a:UInt64; + + a = UInt64.make(0, 1); + uint64eq(a << 32, UInt64.make(1, 0)); + uint64eq(a << 63, UInt64.make(0x80000000, 0)); + + // >> on UInt64 is logical (unsigned) shift, NOT arithmetic + a = UInt64.make(0x80000000, 0); + uint64eq(a >> 1, UInt64.make(0x40000000, 0)); + + // Verify >> does NOT sign-extend (key unsigned behavior) + a = UInt64.make(0xFFFFFFFF, 0xFFFFFFFF); // all bits set + uint64eq(a >> 4, UInt64.make(0x0FFFFFFF, 0xFFFFFFFF)); + + // >>> same as >> + uint64eq(a >>> 4, UInt64.make(0x0FFFFFFF, 0xFFFFFFFF)); + + // Shift by 0 + a = UInt64.make(1, 1); + uint64eq(a << 0, a); + uint64eq(a >> 0, a); + uint64eq(a >>> 0, a); + } + + public function testIncrement() { + var a:UInt64, b:UInt64; + + a = UInt64.make(0, 0); + b = a; + a++; + f(a == b); + uint64eq(a, UInt64.make(0, 1)); + + a = UInt64.make(0, 0xFFFFFFFF); + b = a; + var c = UInt64.make(1, 0); + uint64eq(a++, b); + uint64eq(a--, c); + uint64eq(++a, c); + uint64eq(--a, b); + } + + public function testNeg() { + var a:UInt64; + + a = UInt64.make(0, 1); + uint64eq(-a, UInt64.make(0xFFFFFFFF, 0xFFFFFFFF)); // -1 == MAX_UINT64 + + a = UInt64.make(0, 0); + uint64eq(-a, UInt64.make(0, 0)); // -0 == 0 + } + + public function testInt64Conversion() { + // UInt64 <-> Int64 round-trip preserves bits + var u = UInt64.make(0x80000000, 0x12345678); + var i = u.toInt64(); + eq(i.high, 0x80000000); + eq(i.low, 0x12345678); + var u2 = UInt64.fromInt64(i); + t(u == u2); + + // Zero round-trip + var u0:UInt64 = UInt64.make(0, 0); + var i0 = u0.toInt64(); + t(haxe.Int64.isZero(i0)); + uint64eq(UInt64.fromInt64(i0), u0); + } + + public function testParseString() { + eq(Std.string(UInt64.parseString("0")), "0"); + eq(Std.string(UInt64.parseString("1")), "1"); + eq(Std.string(UInt64.parseString("42")), "42"); + eq(Std.string(UInt64.parseString("4294967296")), "4294967296"); // 2^32 + eq(Std.string(UInt64.parseString("9223372036854775807")), "9223372036854775807"); // MAX_INT64 + eq(Std.string(UInt64.parseString("9223372036854775808")), "9223372036854775808"); // MAX_INT64 + 1 + eq(Std.string(UInt64.parseString("18446744073709551615")), "18446744073709551615"); // MAX_UINT64 + + // Trims whitespace + eq(Std.string(UInt64.parseString(" 42 ")), "42"); + + // Negative throws + var threw = false; + try { + UInt64.parseString("-1"); + } catch (e:Dynamic) { + threw = true; + } + t(threw); + + // Invalid chars throw + threw = false; + try { + UInt64.parseString("abc"); + } catch (e:Dynamic) { + threw = true; + } + t(threw); + } + + public function testFromFloat() { + uint64eq(UInt64.fromFloat(0.0), UInt64.make(0, 0)); + uint64eq(UInt64.fromFloat(1.0), UInt64.make(0, 1)); + uint64eq(UInt64.fromFloat(4294967296.0), UInt64.make(1, 0)); // 2^32 + uint64eq(UInt64.fromFloat(9007199254740991.0), UInt64.parseString("9007199254740991")); // 2^53-1 + + // Negative throws + var threw = false; + try { + UInt64.fromFloat(-1.0); + } catch (e:Dynamic) { + threw = true; + } + t(threw); + + // NaN throws + threw = false; + try { + UInt64.fromFloat(Math.NaN); + } catch (e:Dynamic) { + threw = true; + } + t(threw); + } + + public function testToFloat() { + var a:UInt64; + + a = UInt64.make(0, 0); + feq(a.toFloat(), 0.0); + + a = UInt64.make(0, 42); + feq(a.toFloat(), 42.0); + + a = UInt64.make(1, 0); + feq(a.toFloat(), 4294967296.0); + + // Value with high bit set (would be negative in signed) + a = UInt64.make(0x80000000, 0); + feq(a.toFloat(), 9223372036854775808.0); + } + + public function testZero() { + t(UInt64.isZero(UInt64.make(0, 0))); + f(UInt64.isZero(UInt64.make(0, 1))); + f(UInt64.isZero(UInt64.make(1, 0))); + f(UInt64.isZero(UInt64.make(0xFFFFFFFF, 0xFFFFFFFF))); + } + + public function testCopy() { + var a = UInt64.make(0x12345678, 0x9ABCDEF0); + var b = a.copy(); + t(a == b); + eq(a.high, b.high); + eq(a.low, b.low); + } + + function uint64eq(v:UInt64, v2:UInt64, ?pos:haxe.PosInfos) { + t(v == v2, pos); + } +}