czardas Posted August 31, 2015 Posted August 31, 2015 (edited) I needed this library to continue working on one or two of my projects. Hopefully it will be of use to someone else too. These functions attempt to perform accurate calculations for the largest Int-64 values, testing for integer overflow and resorting to floats when results go out of range. This library is primarily intended to be used with Int-32 or Int-64 values and it offers no advantage whatsoever to pass floats as parameters. It only makes sense if you intend to perform accurate calculations on large Int-64 values. I know this can be done using strings too, but I prefer not to speculate as to the difference in time penalties.expandcollapse popup; #INDEX# ====================================================================================================================== ; Title .........: operator64 ; AutoIt Version : 3.3.14.0 ; Language ......: English ; Description ...: Helper functions for basic maths operations to be used with Int-32 and Int-64 numeric data types. ; Notes .........: Tests for integer overflow and attempts to correct for small inaccuracies affecting certain calculations. ; Return values are cast as Int-32 or Int-64 when possible, otherwise the result will be returned as a double. ; If it is not possible to return an Int-64 data type, the @extended flag is always set to non zero. ; Extended return values indicate that 100% accuracy cannot be guaranteed or that the result is ambiguous. ; The following values are valid for all functions in this library ; |@error = 1 The input or first operand is not an integer. ; |@error = 2 The second operand is not an integer. ; |@error = 3 Internal function error (ignore). ; |@extended = 1 The result is limited to double precision. ; |@extended = 2 The result is not an integer. ; |@extended = 3 The result is out of range for Int-64. ; |@extended = 4 The result is infinity. ; |@extended = 5 The result is an imaginary number. ; When an error occurs, the @extended flag is always set to non zero and a value returned regardless. ; Author(s) .....: czardas ; ============================================================================================================================== ; #CURRENT# ==================================================================================================================== ; _Abs64 ; _Add64 ; _Divide64 ; _Multiply64 ; _Power64 ; _Root64 ; _Subtract64 ; ============================================================================================================================== ; #INTERNAL_USE_ONLY#=========================================================================================================== ; __DoubleTo64 ; __Integer64 ; __OverflowDetect ; __Product64 ; __WholeNumberDivision ; ============================================================================================================================== ; #FUNCTION# =================================================================================================================== ; Name...........: _Abs64 ; Description ...: Returns the absolute value of an integer. ; Syntax.........: _Abs64($iInteger) ; Parameters.....; $iInteger - The integer to operate on. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The input parameter is not an integer. ; |@extended = 2 The result is not an integer. ; |@extended = 3 The absolute value (2^63) is out of range for Int-64. ; Author.........: czardas ; ============================================================================================================================== Func _Abs64($iInteger) $iInteger = __Integer64($iInteger) If @error Then Return SetError(1, 2, Abs($iInteger)) If $iInteger = 0x8000000000000000 Then Return SetExtended(3, Abs($iInteger)) Return $iInteger < 0 ? $iInteger *-1 : $iInteger EndFunc ;==> _Abs64 ; #FUNCTION# =================================================================================================================== ; Name...........: _Add64 ; Description ...: Adds two integers together. ; Syntax.........: _Add64($iOperand_1, $iOperand_2) ; Parameters.....; $iOperand_1 - The first operand. ; $iOperand_2 - The second operand. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@extended = 1 The result is limited to double precision. ; |@extended = 3 The result is out of range for Int-64. ; Author.........: czardas ; ============================================================================================================================== Func _Add64($iOperand_1, $iOperand_2) $iOperand_1 = __Integer64($iOperand_1) If @error Then Return SetError(1, 1, __DoubleTo64($iOperand_1 + $iOperand_2)) ; may still return an integer $iOperand_2 = __Integer64($iOperand_2) If @error Then Return SetError(2, 1, __DoubleTo64($iOperand_1 + $iOperand_2)) ; ditto Local $bOverflow, $nResult $bOverflow = __OverflowDetect('+', $iOperand_1, $iOperand_2, $nResult) Return SetExtended($bOverflow *3, $nResult) EndFunc ;==> _Add64 ; #FUNCTION# =================================================================================================================== ; Name...........: _Divide64 ; Description ...: Divides two integers. ; Syntax.........: _Divide64($iOperand_1, $iOperand_2) ; Parameters.....; $iOperand_1 - The first operand. ; $iOperand_2 - The second operand. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@extended = 1 The result is limited to double precision. ; |@extended = 2 The result is not an integer. ; |@extended = 4 The result is infinity. ; Author.........: czardas ; Comments ......; Due to internal rounding, errors may return an integer with the extended flag set to 2. This is not a bug! ; ============================================================================================================================== Func _Divide64($iOperand_1, $iOperand_2) $iOperand_1 = __Integer64($iOperand_1) If @error Then Return SetError(1, 1, __DoubleTo64($iOperand_1 / $iOperand_2)) ; may still return an integer $iOperand_2 = __Integer64($iOperand_2) If @error Then Return SetError(2, 1, __DoubleTo64($iOperand_1 / $iOperand_2)) ; as above If $iOperand_1 = 0x8000000000000000 Then ; this is not dealt with by the internal function If $iOperand_2 = 0x8000000000000000 Then Return 1 ; $iOperand_1 is divided by itself ; divide both values by 2 to remain within the specified range of the function __WholeNumberDivision() $iOperand_1 = 0xC000000000000000 ; -4611686018427387904 $iOperand_2 = __WholeNumberDivision($iOperand_2, 2) If @extended Then Return SetExtended(@extended, $iOperand_1 / $iOperand_2) EndIf Local $nResult = __WholeNumberDivision($iOperand_1, $iOperand_2) Return SetExtended(@extended, $nResult) EndFunc ;==> _Divide64 ; #FUNCTION# =================================================================================================================== ; Name...........: _Multiply64 ; Description ...: Multiplies two integers together. ; Syntax.........: _Multiply64($iOperand_1, $iOperand_2) ; Parameters.....; $iOperand_1 - The first operand. ; $iOperand_2 - The second operand. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@extended = 2 The result is not an integer. ; |@extended = 3 The result is out of range for Int-64. ; Author.........: czardas ; Comments ......; Due to internal rounding, errors may return an integer with the extended flag set to 2. This is not a bug! ; ============================================================================================================================== Func _Multiply64($iOperand_1, $iOperand_2) $iOperand_1 = __Integer64($iOperand_1) If @error Then Return SetError(1, 2, $iOperand_1 * $iOperand_2) $iOperand_2 = __Integer64($iOperand_2) If @error Then Return SetError(2, 2, $iOperand_1 * $iOperand_2) Local $iProduct = __Product64($iOperand_1, $iOperand_2) Return SetExtended(@extended, $iProduct) EndFunc ;==> _Multiply64 ; #FUNCTION# =================================================================================================================== ; Name...........: _Power64 ; Description ...: Raises an integer to the power of a second integer. ; Syntax.........: _Power64($iInteger, $iPower) ; Parameters.....; $iInteger - The integer to operate on. ; $iPower - The power to raise the integer to. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@extended = 1 The result is limited to double precision. ; |@extended = 3 The result is out of range for Int-64. ; Author.........: czardas ; ============================================================================================================================== Func _Power64($iInteger, $iPower) $iInteger = __Integer64($iInteger) If @error Then Return SetError(1, 1, $iInteger ^ $iPower) $iPower = __Integer64($iPower) If @error Then Return SetError(2, 1, $iInteger ^ $iPower) If $iInteger = 1 Or $iPower = 0 Then Return 1 If $iPower < 0 Or $iPower > 63 Then Return SetExtended(1, $iInteger ^ $iPower) Local $iTriggers = 0, $iCount = 0, $iPow = $iPower While $iPow > 1 If Mod($iPow, 2) Then $iPow -= 1 Else ; to reduce calls to __Product64() ; triggers indicate the product should be squared (see next loop) $iTriggers += 2 ^ $iCount ; set trigger $iPow /= 2 EndIf $iCount += 1 WEnd Local $iOperand, $iResult = $iInteger For $i = $iCount -1 To 0 Step -1 ; multiply the product either by itself or the original value $iOperand = BitAND($iTriggers, 2^$i) ? $iResult : $iInteger $iResult = __Product64($iResult, $iOperand) If @extended Then Return SetExtended(3, $iInteger ^ $iPower) Next Return $iResult EndFunc ;==> _Power64 ; #FUNCTION# =================================================================================================================== ; Name...........: _Root64 ; Description ...: Calculates the nth root of an integer. ; Syntax.........: _Root64($iInteger, $iRoot) ; Parameters.....; $iInteger - The integer to operate on. ; $iRoot - The root of the integer to calculate. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@extended = 1 The result is limited to double precision. ; |@extended = 2 The result is not an integer. ; |@extended = 5 The result is an imaginary number. ; Author.........: czardas ; Comments ......; _Root64() has limited application beyond forcing an integer return value whenever possible. ; Roots of negative integers return a defined value if one exists, otherwise the result is always undefined. ; ============================================================================================================================== Func _Root64($iInteger, $iRoot) $iInteger = __Integer64($iInteger) If @error Then Return SetError(1, 1, $iInteger ^ (1 / $iRoot)) $iRoot = __Integer64($iRoot) If @error Then Return SetError(2, 1, $iInteger ^ (1 / $iRoot)) If $iRoot > 63 Or $iRoot < 1 Then Return SetExtended(1, $iInteger ^ (1 / $iRoot)) ; out of range If $iRoot = 1 Then Return $iInteger Local $iSign = $iInteger < 0 ? -1 : 1 If $iSign = -1 And Not Mod($iRoot, 2) Then Return SetExtended(5, $iInteger ^ (1 / $iRoot)) ; undefined If $iInteger = 0x8000000000000000 Then ; here the absolute value cannot be represented by Int-64 ; a small set of defined return values are simply hard coded Local $aMaxRoot = [[3,-2097152],[7,-512],[9,-128],[21,-8],[63,-2]] ; there are only five cases For $i = 0 To 4 If $iRoot = $aMaxRoot[$i][0] Then Return $aMaxRoot[$i][1] Next Return SetExtended(2, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer EndIf ; positive values are used to calculate the odd roots of negative integers Local $iOperand = $iSign = 1 ? $iInteger : $iInteger *-1 ; here the margin of error with Int() is probably too small to necessitate further processing Local $iReturn = Int($iOperand ^ (1 / $iRoot)) ; check the result Local $iCompare = $iReturn For $i = 2 To $iRoot $iCompare *= $iReturn Next ; the chances that this comparison will fail seem negligible - see comments above If $iCompare <> $iOperand Then Return SetExtended(2, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer Return $iReturn * $iSign EndFunc ;==> _Root64 ; #FUNCTION# =================================================================================================================== ; Name...........: _Subtract64 ; Description ...: Subtracts one integer from another. ; Syntax.........: _Subtract64($iOperand_1, $iOperand_2) ; Parameters.....; $iOperand_1 - The first operand. ; $iOperand_2 - The second operand. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@extended = 1 The result is limited to double precision. ; |@extended = 3 The result is out of range for Int-64. ; Author.........: czardas ; ============================================================================================================================== Func _Subtract64($iOperand_1, $iOperand_2) $iOperand_1 = __Integer64($iOperand_1) If @error Then Return SetError(1, 1, __DoubleTo64($iOperand_1 - $iOperand_2)) ; may still return an integer $iOperand_2 = __Integer64($iOperand_2) If @error Then Return SetError(2, 1, __DoubleTo64($iOperand_1 - $iOperand_2)) ; ditto Local $bOverflow, $nResult $bOverflow = __OverflowDetect('-', $iOperand_1, $iOperand_2, $nResult) Return SetExtended($bOverflow *3, $nResult) EndFunc ;==> _Subtract64 #Region - Internal Functions ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __DoubleTo64 ; Description ...: Helper function - converts an integer value double to Int-32 or Int-64. ; Syntax.........: __DoubleTo64($nValue) ; Parameters.....; $nValue - The double to convert ; Return values .: Success returns Int-32 or Int-64 when the interpreter evaluates the converted double equal to the input. ; Failure returns the input parameter without any modification and sets the following flags: ; |@error = 3 Internal error (x2) ==> see comments in the code. ; |@extended = 2 Conversion failed ==> the return value is not an integer. ; Author ........: czardas ; Comments ......; Doubles representing integer values greater than 15 digits cannot be relied on for accuracy. ; ============================================================================================================================== Func __DoubleTo64($nValue) If Not IsNumber($nValue) Then Return SetError(3, 2, $nValue) ; the input is not a number. If $nValue > -1.0e+015 And $nValue < 1.0e+015 Then Local $iVal64 = Number($nValue, 2) ; convert to Int-64 If $iVal64 = $nValue Then ; check to see if conversion was a success [expected range +/- 5.62949953421311e+014] Return ($iVal64 > 2147483647 Or $iVal64 < -2147483648) ? $iVal64 : Number($iVal64, 1) ; Int-64 or Int-32 ElseIf $iVal64 -1 = $nValue Then ; attempt to adjust for inaccuracies [subject to possible change] Return $iVal64 -1 ElseIf $iVal64 +1 = $nValue Then ; as above Return $iVal64 +1 EndIf EndIf Return SetError(3, 2, $nValue) ; conversion failed EndFunc ;==> __DoubleTo64 ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __Integer64 ; Description ...: Checks if a number is an integer and converts doubles to Int-32 or Int-64 if the result appears unambiguous. ; Syntax.........: __Integer64($nParam) ; Parameters.....; $nParam - The number to test ; Return values .: Success returns Int-32 or Int-64 when the interpreter evaluates the returned integer equal to the input. ; Failure returns the input parameter without any modification and sets the following flags: ; |@error = 1 The input is not an integer. ; |@extended = 2 The return value is not an integer. ; Author ........: czardas ; Comments ......; Doubles representing integer values greater than 15 digits cannot be relied on for accuracy. ; ============================================================================================================================== Func __Integer64($nParam) If Not StringInStr(VarGetType($nParam), 'Int') Then Local $iInt64 = __DoubleTo64($nParam) ; attempt conversion If @error Then Return SetError(1, 2, $nParam) ; $fParam <> $iInt64 $nParam = $iInt64 ; float was compared as being equal to an integer EndIf Return $nParam EndFunc ;==> __Integer64 ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __OverflowDetect ; Description ...: Checks for integer overflow on execution of the expression. ; Syntax.........: __OverflowDetect($sOperator, $iOperand_1, $iOperand_2, $nResult) ; Parameters.....; $sOperator - May be +, - or *. ; $iOperand_1 - The first operand. ; $iOperand_2 - The second operand. ; $nResult - [ByRef] Result of executed expression. ; Return values .: Returns True if integer overflow occurs - otherwise returns False. Returns $nResult ByRef. ; Author ........: czardas ; Comments ......; Int-32 or Int-64 only. No error checks! ; ============================================================================================================================== Func __OverflowDetect($sOperator, $iOperand_1, $iOperand_2, ByRef $nResult) Local $iExecute, $fCompare Switch $sOperator Case '+' ; execute the expression $iExecute = $iOperand_1 + $iOperand_2 ; execute the expression with the operands converted to doubles $fCompare = Number($iOperand_1, 3) + Number($iOperand_2, 3) Case '-' ; as above $iExecute = $iOperand_1 - $iOperand_2 $fCompare = Number($iOperand_1, 3) - Number($iOperand_2, 3) Case '*' ; as above $iExecute = $iOperand_1 * $iOperand_2 $fCompare = Number($iOperand_1, 3) * Number($iOperand_2, 3) EndSwitch ; the results should be approximately equal Local $bReturn = StringFormat('%.14e', $iExecute) <> StringFormat('%.14e', $fCompare) ; %.15e is too sensitive $nResult = $bReturn ? $fCompare : $iExecute Return $bReturn EndFunc ;==> __OverflowDetect ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __Product64($iOperand_1, $iOperand_2) ; Description ...: Helper function - multiplies two Int-32 or Int-64 values together. ; Syntax.........: __Product64($iOperand_1, $iOperand_2) ; Parameters.....; $iOperand_1 - The first integer. ; $iOperand_2 - The second integer. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@extended = 3 The result is out of range for Int-64. ; Author.........: czardas ; Comments ......; Int-32 or Int-64 only. No error checks! ; ============================================================================================================================== Func __Product64($iOperand_1, $iOperand_2) Local $bOverflow, $nResult $bOverflow = __OverflowDetect('*', $iOperand_1, $iOperand_2, $nResult) Return SetExtended($bOverflow *3, $nResult) EndFunc ;==> __Product64 ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __WholeNumberDivision ; Description ...: Divides two integers. ; Syntax.........: __WholeNumberDivision($iDividend, $iDivisor) ; Parameters.....; $iDividend - The first operand. ; $iDivisor - The second operand. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets the following flags: ; |@extended = 1 The result is limited to double precision. ; |@extended = 2 The result is not an integer. ; |@extended = 4 The result is infinity. ; Author ........: czardas ; Comments ......; Input is limited to +/- 9223372036854775807 for both consitency and compatibility with future libraries. ; May return an integer with the extended flag set to 2 due to internal rounding. This is not a bug! ; ============================================================================================================================== Func __WholeNumberDivision($iDividend, $iDivisor) ; Input ranges -9223372036854775807 To 9223372036854775807 If $iDivisor = 0 Then Return SetExtended(4, $iDividend / $iDivisor) ; division by zero Local $aDiv = [$iDividend, $iDivisor], _ $iSign = 1 For $i = 0 To 1 If $aDiv[$i] > 0x7FFFFFFFFFFFFFFF Or $aDiv[$i] < 0x8000000000000001 Then Return SetExtended(1, $iDividend / $iDivisor) ; input range exceeded If $aDiv[$i] < 0 Then ; force positive integers $aDiv[$i] *= -1 $iSign *= -1 ; to add back later EndIf Next If Mod($aDiv[0], $aDiv[1]) Then Return SetExtended(2, $iDividend / $iDivisor) ; not divisible If $aDiv[0] = 0 Then Return 0 If $aDiv[1] = 1 Then Return $aDiv[0] * $iSign Local $iDivision = Floor($aDiv[0] / $aDiv[1]), $iDifference, $iIntegral While $iDivision * $aDiv[1] > $aDiv[0] ; division is overstated $iDifference = ($aDiv[1] * $iDivision) - $aDiv[0] $iIntegral = Floor($iDifference / $aDiv[1]) ; avoid shooting beyond the target If $iIntegral = 0 Then $iIntegral = 1 ; prevents hanging in an infinite loop $iDivision -= $iIntegral WEnd While $iDivision * $aDiv[1] < $aDiv[0] ; division is understated $iDifference = $aDiv[0] - ($aDiv[1] * $iDivision) $iIntegral = Floor($iDifference / $aDiv[1]) ; as above If $iIntegral = 0 Then $iIntegral = 1 ; prevents hanging $iDivision += $iIntegral WEnd Return $iDivision * $iSign EndFunc ;==> __WholeNumberDivision #EndRegionThe following tests need updating because values for @error and @extended have changed.expandcollapse popup#include 'operator64.au3' ConsoleWrite("Testing _Abs64()" & @LF) ; ==> Err Ext Result $test = _Abs64(0xC000000000000000) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 4611686018427387904 $test = _Abs64(0x8000000000000000) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 2 1 9.22337203685478e+018 $test = _Abs64(0x8000000000000001) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 9223372036854775807 $test = _Abs64(-0.123) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 1 1 0.123 ;=========================================================== ConsoleWrite(@LF & "Testing _Add64()" & @LF) $test = _Add64(0x7FFFFFFFFFFFFFFF, 1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 1 9.22337203685478e+018 $test = _Add64(0x7FFFFFFFFFFFFFFF, 0) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 9223372036854775807 $test = _Add64(0x7FFFFFFFFFFFFFFF, -1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 9223372036854775806 $test = _Add64(33, 15) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 48 ;=========================================================== ConsoleWrite(@LF & "Testing _Divide64()" & @LF) $test = _Divide64(0x7FFFFFFFFFFFFFFF, -1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 -9223372036854775807 $test = _Divide64(0x8000000000000000, -2) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 4611686018427387904 $test = _Divide64(10.0, 2) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 5 $test = _Divide64(9223372036854775552, -2) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 -4611686018427387776 $test = _Divide64(9223372036854775552, 0) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 2 1 1.#INF $test = _Divide64(675432.0097, -987) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 1 1 -684.328277304965 $test = _Divide64(6922337, 15) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 4 1 461489.133333333 ;=========================================================== ConsoleWrite(@LF & "Testing _Multiply64()" & @LF) $test = _Multiply64(9223372036854775552, -2) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 1 -1.84467440737096e+019 $test = _Multiply64(223372036854775552, 9) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 2010348331692979968 $test = _Multiply64(1.01, -1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 1 1 -1.01 ;=========================================================== ConsoleWrite(@LF & "Testing _Power64()" & @LF) $test = _Power64(-2.0, 63) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 -9223372036854775808 $test = _Power64(37, 12.0) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 6582952005840035281 $test = _Power64(17, 19) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 1 2.39072435685151e+023 ;=========================================================== ConsoleWrite(@LF & "Testing _Subtract64()" & @LF) $test = _Subtract64(1.0, 1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 0 $test = _Subtract64(0x8000000000000000, -1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 -9223372036854775807 $test = _Subtract64(0x8000000000000000, 0) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 0 -9223372036854775808 $test = _Subtract64(0x8000000000000000, 1) ConsoleWrite(@Error & @TAB & @Extended & @TAB & $test & @LF) ; ==> 0 1 -9.22337203685478e+018It's nice to try and figure out something like this for yourself, but it would be quite impossible without the hints and advice I receive from others when I take a wrong turn sometimes. So thanks to those people. Edited September 27, 2015 by czardas Celtic88, coffeeturtle and Biatu 3 operator64  ArrayWorkshop
czardas Posted September 2, 2015 Author Posted September 2, 2015 (edited) New Function added: _Power64()Some optimization but more is needed. This new function is too slow at the moment.Example:#include 'operator64.au3' ConsoleWrite(_Power64(-2, 63) & @LF) ; -9223372036854775808Â Edited September 2, 2015 by czardas operator64Â Â ArrayWorkshop
czardas Posted September 9, 2015 Author Posted September 9, 2015 (edited) I have optimized the code for _Power64() as best as I know how. There is still quite a time penalty, but I see this as a trade off for performing accurate calculations with large integers. I'm sure if there was no time penalty involved then the world would look slightly different. I have also changed the behaviour of the UDF: now all functions accept integer value doubles of up to 15 digits. Edited September 9, 2015 by czardas operator64  ArrayWorkshop
czardas Posted September 10, 2015 Author Posted September 10, 2015 (edited) I said I dared not to speculate about speed, but I lied.  Speed test comparisons with BigNum revealed that operator64 is about 2 to 3 times faster for all maths functions. Of course with operator64, accuracy is limited to the set of whole numbers which can be represented as Int-64 data types. I'm reasonably happy with these results. Perhaps, when I look around, I will discover better solutions to the ones I came up with. Maybe in the future there will be changes to AutoIt which will render some of these functions obsolete. I'm sure there must be plenty of non-native libraries which do a similar job much faster - so be it! expandcollapse popup#include 'operator64.au3' #include 'BigNum.au3' Local $iOperand1, $iOperand2, $iTimer, $iIterations = 10000 ;================================================================= ; Add 2 numbers ConsoleWrite('Addition' & @LF) $iOperand1 = 70009256455583555 $iOperand2 = 198116002927730039 ;ConsoleWrite(_Add64($iOperand1, $iOperand2) & @LF) ;ConsoleWrite(_BigNum_Add($iOperand1, $iOperand2) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _Add64($iOperand1, $iOperand2) Next ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _BigNum_Add($iOperand1, $iOperand2) Next ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF) ;================================================================= ; Subtract 2 numbers ConsoleWrite('Subtraction' & @LF) $iOperand1 = 198116002927730039 $iOperand2 = 70009256455583555 ;ConsoleWrite(_Subtract64($iOperand1, $iOperand2) & @LF) ;ConsoleWrite(_BigNum_Sub($iOperand1, $iOperand2) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _Subtract64($iOperand1, $iOperand2) Next ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _BigNum_Sub($iOperand1, $iOperand2) Next ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF) ;================================================================= ; Divide 2 numbers ConsoleWrite('Division' & @LF) $iOperand1 = 0x8000000000000000 $iOperand2 = -8 ;ConsoleWrite(_Divide64($iOperand1, $iOperand2) & @LF) ;ConsoleWrite(_BigNum_Div($iOperand1, $iOperand2) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _Divide64($iOperand1, $iOperand2) Next ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _BigNum_Div($iOperand1, $iOperand2) Next ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF) ;================================================================= ; Multiply 2 numbers ConsoleWrite('Multiplication' & @LF) $iOperand1 = 999999996 $iOperand2 = 234567873 ;ConsoleWrite(_Multiply64($iOperand1, $iOperand2) & @LF) ;ConsoleWrite(_BigNum_Mul($iOperand1, $iOperand2) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _Multiply64($iOperand1, $iOperand2) Next ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _BigNum_Mul($iOperand1, $iOperand2) Next ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF) ;================================================================= ; raise a number to a power ConsoleWrite('Power Operator' & @LF) $iOperand1 = -2 $iOperand2 = 63 $iIterations = 2000 ;ConsoleWrite(_Power64($iOperand1, $iOperand2) & @LF) ;ConsoleWrite(_BigNum_Pow($iOperand1, $iOperand2) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _Power64($iOperand1, $iOperand2) Next ConsoleWrite('operator64.au3 ... ' & TimerDiff($iTimer) & @LF) $iTimer = TimerInit() For $i = 1 To $iIterations _BigNum_Pow($iOperand1, $iOperand2) Next ConsoleWrite('BigNum.au3 ....... ' & TimerDiff($iTimer) & @LF & @LF)ResultsAddition operator64.au3 ... 611.45171195762 BigNum.au3 ....... 1001.19733099668 Subtraction operator64.au3 ... 593.831397302383 BigNum.au3 ....... 1295.27405555922 Division operator64.au3 ... 753.401508513282 BigNum.au3 ....... 1744.577881667 Multiplication operator64.au3 ... 670.554139638136 BigNum.au3 ....... 1547.65752329044 Power Operator operator64.au3 ... 1174.47467967118 BigNum.au3 ....... 3246.95088321818One further point worth mentioning - this library is intended for calculations which are likely to result in overflow on Int-64, or are that are handled internally by floating point operations: which cannot produce accurate results for the whole Int-64 numeric range. Edited September 10, 2015 by czardas operator64  ArrayWorkshop
czardas Posted September 12, 2015 Author Posted September 12, 2015 (edited) Update:Some values were registering as having overflowed because an internal string comparison was too sensitive. Sensitivity can be reduced down to just a few digits: so this constitutes fine tuning rather than an actual bug.Error values for the function _Divide64() have changed. Edited September 12, 2015 by czardas operator64  ArrayWorkshop
czardas Posted September 12, 2015 Author Posted September 12, 2015 (edited) As I was writing the following, function I kept thinking about Route 66 but that's something else.  I don't really know what use _Root64() might have: it just seemed to be missing from the current library. Perhaps the function is superfluous because roots of integers do not suffer from integer overflow - just tiny FP inaccuracies. It does have some added functionality in that it calculates odd roots of negative integers, but its usefulness is limited to calculating roots when you require an integer to be returned and that's probably not such a common occurrence - hmm! You might want to get back to the original value after using _Power64() and check for errors.  expandcollapse popup#include 'operator64.au3' ConsoleWrite(_Root64(-6071163615208263051, 11) & @LF) ConsoleWrite(_Root64(0x8000000000000000, 7) & @LF) ConsoleWrite(_Root64(4052555153018976267, 13) & @LF) ; #FUNCTION# =================================================================================================================== ; Name...........: _Root64 ; Description ...: Calculates the nth root of an integer. ; Syntax.........: _Root64($iInteger, $iRoot) ; Parameters.....; $iInteger - The integer to operate on. ; $iRoot - The root of the integer to calculate. ; Return values .: Returns an Int-32, or Int-64, value. ; Failure returns the executed expression and sets @error as follows: ; |@error = 1 The first parameter is not an integer. ; |@error = 2 The second parameter is not an integer. ; |@error = 3 The result cannot be represented as an integer. ; |@error = 4 Undefined - the result cannot be represented - period. ; Author.........: czardas ; Comments ......; Experimental because: ; 1. Code to accomodate unexpected exceptions was removed due to lack of evidence that a problem exists. ; 2. _Root64() has limited application beyond forcing an integer return value whenever possible. ; Roots of negative integers return a defined value if one exists, otherwise the result is always undefined. ; ============================================================================================================================== Func _Root64($iInteger, $iRoot) $iInteger = __Integer64($iInteger) If @error Then Return SetError(1, 1, $iInteger ^ (1 / $iRoot)) $iRoot = __Integer64($iRoot) If @error Then Return SetError(2, 1, $iInteger ^ (1 / $iRoot)) If $iRoot > 63 Or $iRoot < 1 Then Return SetError(3, 1, $iInteger ^ (1 / $iRoot)) ; out of range If $iRoot = 1 Then Return $iInteger Local $iSign = $iInteger < 0 ? -1 : 1 If $iSign = -1 And Not Mod($iRoot, 2) Then Return SetError(4, 1, $iInteger ^ (1 / $iRoot)) ; undefined If $iInteger = 0x8000000000000000 Then ; here the absolute value cannot be represented by Int-64 ; a small set of defined return values are simply hard coded Local $aMaxRoot = [[3,-2097152],[7,-512],[9,-128],[21,-8],[63,-2]] ; there are only five cases For $i = 0 To 4 If $iRoot = $aMaxRoot[$i][0] Then Return $aMaxRoot[$i][1] Next Return SetError(3, 1, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer EndIf ; positive values are used to calculate the odd roots of negative integers Local $iOperand = $iSign = 1 ? $iInteger : $iInteger *-1 ; here the margin of error with Int() is probably too small to necessitate further processing Local $iReturn = Int($iOperand ^ (1 / $iRoot)) ; check the result Local $iCompare = $iReturn For $i = 2 To $iRoot $iCompare *= $iReturn Next ; the chances that this comparison will fail seem negligible - see comments above If $iCompare <> $iOperand Then Return SetError(3, 1, $iInteger ^ (1 / $iRoot)) ; the return value is not an integer Return $iReturn * $iSign EndFunc ;==> _Root64 Edited September 12, 2015 by czardas operator64  ArrayWorkshop
czardas Posted September 27, 2015 Author Posted September 27, 2015 (edited) Changes to @error and @extended values. Note division by zero no longer returns an error. The values below are consistent throughout the UDF, applying to all functions. New version in the first post.; |@error = 1 The input or first operand is not an integer. ; |@error = 2 The second operand is not an integer. ; |@error = 3 Internal function error (ignore). ; |@extended = 1 The result is limited to double precision. ; |@extended = 2 The result is not an integer. ; |@extended = 3 The result is out of range for Int-64. ; |@extended = 4 The result is infinity. ; |@extended = 5 The result is an imaginary number.Also added _Root64() to the UDF.I believe this is finished, unless future changes to the AutoIt core render some of these functions outmoded or superfluous. Edited September 27, 2015 by czardas operator64  ArrayWorkshop
czardas Posted December 12, 2016 Author Posted December 12, 2016 (edited) A question about base conversion came up recently. This presents a good opportunity to write a practical example using some of these functions. It makes good sense to post examples here. I have made a couple of modifications to my original code after running a few tests. expandcollapse popup#include 'operator64.au3' Func DecToBase($iInt, $iBase) $iInt = __Integer64($iInt) If @error Or $iInt < 0 Then Return SetError(1, 0, '') ; positive integers only $iBase = __Integer64($iBase) If @error Or $iBase < 2 Or $iBase > 9 Then Return SetError(2, 0, '') ; bases 2 to 9 only Local $iRem, $sRet = '' While $iInt >= $iBase $iRem = Mod($iInt, $iBase) $sRet = $iRem & $sRet $iInt = _Divide64(($iInt - $iRem), $iBase) WEnd Return $iInt & $sRet EndFunc ;==> DecToBase Func BaseToDec($sInt, $iBase) $iBase = __Integer64($iBase) If @error Or $iBase < 2 Or $iBase > 9 Then Return SetError(2, 0, '') ; bases 2 to 9 only $sInt = StringRegExpReplace($sInt, '(\A0+)(0\z|.+)', '$2') ; strip leading zeros If $sInt == '' Or Not __BaseIsValid($sInt, $iBase) Then Return SetError(1, 0, '') ; out of range or invalid digits Local $iRet = 0, $iLen = StringLen($sInt) For $i = 1 To $iLen $iRet += StringMid($sInt, $i, 1) * _Power64($iBase, $iLen - $i) Next Return $iRet EndFunc ;==> BaseToDec Func __BaseIsValid($sInt, $iBase) ; bases 2 to 9 only If StringRegExp($sInt, '[^0-' & $iBase -1 & ']') Then Return False ; non-valid digits detected Local $aMaxVal = ['','', _ '111111111111111111111111111111111111111111111111111111111111111', _ ; binary '2021110011022210012102010021220101220221', _ ; ternary '13333333333333333333333333333333', _ ; quaternary '1104332401304422434310311212', _ ; quinary '1540241003031030222122211', _ ; senary '22341010611245052052300', _ ; septenary '777777777777777777777', _ ; octal '67404283172107811827'] ; nonary Return StringLen($sInt) < StringLen($aMaxVal[$iBase]) Or StringCompare($sInt, $aMaxVal[$iBase]) <= 0 EndFunc ;==> __BaseIsValid ; =========== Example =========== Local $iInt = 9012345678901234567 Local $sBase6 = DecToBase($iInt, 6) ConsoleWrite("base 6 ... " & $sBase6 & @LF) ConsoleWrite("Int 64 ... " & BaseToDec($sBase6, 6) & @LF) If you want negative numbers, you can remove the sign and put it back after conversion. Bear in mind that the value 0xFFFFFFFFFFFFFFFF is a special case because the positive equivalent results in integer overflow. These functions are intended to be used with decimal integers beyond 15 digits. For smaller numbers, using operator64 may be overkill. Beyond Int64 you will need to use BigNum UDF functions. NOTE : I have only used operator64 functions as and when needed. Overflow does not occur with addition or multiplication because validation ensures that input values remain within range. Original Thread: https://www.autoitscript.com/forum/topic/185951-convert-dec-to-binary-but-ones-and-zeros/?do=findComment&comment=1335687 Edited December 13, 2016 by czardas Added: strip leading zeros in the function BaseToDec() - to correct a problem with base validation. operator64  ArrayWorkshop
czardas Posted January 7, 2017 Author Posted January 7, 2017 (edited) !170 expandcollapse popup#include 'operator64.au3' ; slightly crazy For $i = 21 To 171 ConsoleWrite($i & '! ... ' & _Factorial64($i) & @LF) Next ; slightly more crazy Local $j For $i = 21 To 171 $j = Random(1, $i, 1) ConsoleWrite('_Combinations64('& $i & ', ' & $j & ') ... ' & _Combinations64($i, $j) & @LF) Next Func _Factorial64($iInt) $iInt = __Integer64($iInt) If @error Or $iInt < 0 Then Return SetError(1, 0, '') ; positive integers only Local $iResult = 1 For $i = 1 to $iInt <= 20 ? $iInt : 20 $iResult *= $i Next For $i = 21 to $iInt $iResult = _Multiply64($iResult, $i) Next Return SetExtended(@extended, $iResult) EndFunc ; _Factorial64 Func _Combinations64($n, $k) $n = __Integer64($n) If @error Or $n < 1 Then Return SetError(1, 0, '') ; positive integers > 0 only $k = __Integer64($k) If @error Or $k < 1 Then Return SetError(2, 0, '') ; positive integers > 0 only If $k > $n Then Return 0 $k = _Multiply64(_Factorial64($n -$k), _Factorial64($k)) $n = _Factorial64($n) Return _Divide64($n, $k) EndFunc ;==> _Combinations64 There will be small floating point inaccuracies affecting _Combination64() with input values > 20. This is to be expected when internal calculations resort to using large floats. The return values in this case should be considered as being potentially, although not necessarily, understated. The larger the input, the more fuzzy the result. BigNum functions return precise results at the cost of speed. NOTE Operator64 functions attempt to return an integer whenever possible. With large return values, there is a seamless transition from integers to floats: which is one of the main points of this UDF. Edited January 8, 2017 by czardas operator64  ArrayWorkshop
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now