[BigInt] Using tests from “Violet - Python VM written in Swift”
LiarPrincess opened this issue · comments
This is a parent issue for merging BigInt
tests from Violet - Python VM written in Swift. In total is is about 400 tests (about 70% of Violet test cases) split into a multiple smaller pull requests.
Please note that this may seem like a lot of tests, but I still can't guarantee that everything works even after passing all of them. A few minutes ago I was debugging crash in division, just to discover that this will also fail:
// -9223372036854775808 = Int64.min, obviously '-Int64.min' overflows
let int: Int64 = -9223372036854775808
let big = BigInt(int)
XCTAssertEqual(-big, big * -1)
Also, I have NOT looked into your implementation. I just know that you use 2 complement, but nothing more. Once we fix all of the crashes/failures I may look into your code and add some more targeted tests.
Reference implementation
BigInt
module inside Violet - Python VM written in Swift passes all of the tests (look at the swift-numerics branch).
Internal Violet implementation (see documentation for more details):
Under the hood it is a union (via tagged pointer) of
Int32
(calledSmi
, after V8) and a heap allocation (magnitude + sign representation) with ARC for garbage collection.
Since Violet contains 2 different representations of BigInt
(Smi
for small numbers and BigIntHeap
for the rest) in a lot of tests you will see separate test_int_xxx
(for small integers) and test_big_xxx
(for big integers). All of the test cases finish in ~10s (2014 rMBP, lowest spec, serial execution), so they are fast enough to keep int
tests even if swift-numerics
implementation does not have a Smi
equivalent. This also tests if all of the BigInt
operations have the same semantic as Int
(div/shift rounding, reminder sign etc.).
Platforms
All of the tests results come from 2014 rMBP -> mac 11.7 (Big Sur), Xcode 13.2.1, Intel.
Not tested:
- M1 - I don't have one.
- 32 bit (or more precisely: configuration where
Int.bitWidth != 64
, which is not the same as 32 bit, but whatever) - the whole Violet was designed for 64 bit (BigInt
was actually the main reason for this), so I may have unintentionally backed some assumptions here and there. Code review needed.
I can confirm that Violet implementation passes tests on:
- mac 11.7 (Big Sur) + Xcode 13.2.1 (Swift 5.5) + Intel
- Ubuntu 21.04 + Swift 5.4.2 + Intel G4560
- Docker
swift:latest
(5.6.0)swift:5.3.2
So, the test cases should be resistant to mac/linux/docker differences.
Missing stuff
- CMake - in other modules you have
CMakeLists.txt
, but not inBigIntTests
. I don't want to be the person to introduce it, so lets just say that I will follow theBigInt
convention of not having one. floating point tests - you already have some tests inADDEDBigIntTests.swift
.- missing
Sendable
Other engines
In some pull requests/code I will be referencing other BigInt
implementations:
-
WolframAlpha.com - almost all of the operations have the same semantic as Swift, except for
>>
. For example:-1932735284 >> 5
:Engine Result Comment Wolfram Alpha -60397977 Swift 5.3.2* -60397978 This is in Int64
range, so you can just-1932735284 >> 5
to test it.Node v17.5.0 -60397978 Python 3.7.4 -60397978 Both are correct, it depends on the rounding mode, but you already know this: swift-numerics/IntegerUtilities/ShiftWithRounding.swift.
-
Python - there is a difference in
div/mod
for negative numbers:(-5)//4
(-5)%4
Python 3.7.4 -2 3 Swift 5.3.2 -1 -1 Both correct:
lhs/rhs = q rem r -> lhs = q * rhs + r
.
Helpers
directory
Shared code for all of the test cases is inside Helpers
directory.
Since all of the pull requests contain the same Helpers
code, it may be easier for us to first review and merge tests while ignoring Helpers
. Then, after all is merged, do any changes in Helpers
. Well… unless there is something really broken in Helpers
.
License
Unless stated otherwise I am the author of this code, so feel free to slap any license you need.
Recommended merging order
In total this is around 10k hand written + 22k generated lines of code. I split them up, so that each PR contains 2-4 files (excluding Helpers
, see above).
(✅ - pass, ❌ - fail, 💀 - crash, 🐰 - to discuss)
-
Node
- [BigInt tests] ❌ Nodejs tests - they can be either 1st or last. Fixing them will solve most of the problems in other tests. But also, fixing problems other tests will fix Node tests. There is a certain overlap.
-
Inits and protocols
-
Operations
-
Other
-
No merge
Final
I know that with so many pull requests it may be a bit hard to see the big picture, but eventually we should arrive at the state from the image below (please ignore the Apple
and Performance
directories, BigIntTests.swift
are the tests currently present). I think that this is quite clean (1 file for every operation), and it will be quite easy to add/modify tests.
[3.2.2023] EDIT to main issue:
- [BigInt tests] 💀 Init from float 754
- [BigInt tests] ✅🐰 Codable
- many changes in performance tests
Things to do in Helpers
(but I'm not going to update 15 PRs, so we will do it later):
-
add
reserveCapacity
ingenerate[Big]Ints
- this does not matter in normal tests (count ~1000), but it matters in performance tests where we need to generate100_000
values -
rename
BigIntPrototype.compare
->BigIntPrototype.compareMagnitude
-
rewrite
BigIntPrototype.create
- shifts 10x faster than multiplication:internal static func create<T: FixedWidthInteger>( isPositive: Bool, magnitude: [T] ) -> BigInt { assert(!T.isSigned) var result = BigInt() for (index, word) in magnitude.enumerated() { var bits = BigInt(word) bits <<= index * T.bitWidth result |= bits } if !isPositive { result.negate() } return result }
If we use sign+magnitude with
UInt
asWord
then there is another possible optimisation that avoids calculations altogether.
I wrote Violet XsProMax which is a Violet implementation with following changes:
- no small inlined integer (
Smi
) - magnitude is always stored on the heap - no restrictions on the size -
isNegative
is stored in-line (and not on the heap like in Violet);count
andcapacity
are on the heap because I don't want to stray too much fromViolet
.
Which gives us:
struct BigInt {
struct Header {
var count: UInt32
var capacity: UInt32
}
var flags: UInt8
var buffer: ManagedBufferPointer<Header, UInt>
}
I updated the performance PR with the results.
The issue below (from @LiarPrincess) appears fixed in my latest updates (see pull request #261)
// -9223372036854775808 = Int64.min, obviously '-Int64.min' overflows
let int: Int64 = -9223372036854775808
let big = BigInt(int)
XCTAssertEqual(-big, big * -1)
All remaining issues (at least the published ones) appear to have been fixed with the pull request #262.
There were some problems with division/modulo signs, initialization, corner-case fixes.