Skip to content

Commit d48cec7

Browse files
authored
Merge pull request #33 from pietroppeter/fix-empty-limbs
Fix empty limbs
2 parents c6d7229 + ea37c11 commit d48cec7

File tree

4 files changed

+200
-34
lines changed

4 files changed

+200
-34
lines changed

bigints.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Package
22

3-
version = "0.4.3"
3+
version = "0.4.4"
44
author = "Dennis Felsing"
55
description = "Arbitrary-precision integers implemented in pure Nim"
66
license = "MIT"

readme.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ For examples of usage see the [examples](examples) folder.
2929
## Current limitations and possible enhancements
3030

3131
* cannot multiply a number with itself (x *= x). An issue like this exist also for addition (#27) and possibly other operations
32-
* an uninitialized `BigInt` might raise error when performing some operations (e.g. #26)
3332
* not expected to work on 32 bit
3433
* some common bitwise operations (`and`, `or`, `xor`, `not`) are not implemented
3534
* operations between `BigInt` and standard integer types besides `int32` are not implemented
@@ -121,25 +120,47 @@ template initBigInt(val: uint): BigInt
121120
proc initBigInt(val: BigInt): BigInt
122121
```
123122

123+
## **const** zero
124+
125+
126+
```nim
127+
zero = ([0'u], {})
128+
```
129+
130+
## **const** one
131+
132+
133+
```nim
134+
one = ([1'u], {})
135+
```
136+
124137
## **proc** cmp
125138

139+
Returns:
140+
* a value less than zero, if <tt class="docutils literal"><span class="pre">a &lt; b</span></tt>
141+
* a value greater than zero, if <tt class="docutils literal"><span class="pre">a > b</span></tt>
142+
* zero, if <tt class="docutils literal"><span class="pre">a == b</span></tt>
126143

127144
```nim
128145
proc cmp(a, b: BigInt): int64
129146
```
130147

131148
## **proc** cmp
132149

150+
Returns:
151+
* a value less than zero, if <tt class="docutils literal"><span class="pre">a &lt; b</span></tt>
152+
* a value greater than zero, if <tt class="docutils literal"><span class="pre">a > b</span></tt>
153+
* zero, if <tt class="docutils literal"><span class="pre">a == b</span></tt>
133154

134155
```nim
135-
proc cmp(a: int32; b: BigInt): int64
156+
proc cmp(a: BigInt; b: int32): int64
136157
```
137158

138159
## **proc** cmp
139160

140161

141162
```nim
142-
proc cmp(a: BigInt; b: int32): int64
163+
proc cmp(a: int32; b: BigInt): int64
143164
```
144165

145166
## **proc** `<`

src/bigints.nim

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,26 @@ else:
6666
proc initBigInt*(val: BigInt): BigInt =
6767
result = val
6868

69-
const zero = initBigInt(0)
70-
const one = initBigInt(1)
69+
const zero* = initBigInt(0)
70+
const one* = initBigInt(1)
71+
72+
proc isZero(a: BigInt): bool {.inline.} =
73+
for i in countdown(a.limbs.high, 0):
74+
if a.limbs[i] != 0'u32:
75+
return false
76+
return true
7177

7278
proc unsignedCmp(a: BigInt, b: int32): int64 =
79+
# here a and b have same sign a none of them is zero.
80+
# in particular we have that a.limbs.len >= 1
7381
result = int64(a.limbs.len) - 1
7482

7583
if result != 0:
7684
return
7785

7886
result = int64(a.limbs[0]) - int64(b)
7987

80-
proc unsignedCmp(a: int32, b: BigInt): int64 =
81-
-unsignedCmp(b, a)
88+
proc unsignedCmp(a: int32, b: BigInt): int64 = -unsignedCmp(b, a)
8289

8390
proc unsignedCmp(a, b: BigInt): int64 =
8491
result = int64(a.limbs.len) - int64(b.limbs.len)
@@ -93,40 +100,52 @@ proc unsignedCmp(a, b: BigInt): int64 =
93100
return
94101

95102
proc cmp*(a, b: BigInt): int64 =
96-
if Negative in a.flags and a.limbs != @[0'u32]:
97-
if Negative in b.flags and b.limbs != @[0'u32]:
98-
return unsignedCmp(b, a)
99-
else:
100-
return -1
101-
else:
102-
if Negative in b.flags:
103+
## Returns:
104+
## * a value less than zero, if `a < b`
105+
## * a value greater than zero, if `a > b`
106+
## * zero, if `a == b`
107+
if a.isZero:
108+
if b.isZero:
109+
return 0
110+
elif Negative in b.flags: # b.isNegative
103111
return 1
104-
else:
105-
return unsignedCmp(a, b)
106-
107-
proc cmp*(a: int32, b: BigInt): int64 =
108-
if a < 0:
109-
if Negative in b.flags and b.limbs != @[0'u32]:
110-
return unsignedCmp(b, a)
111112
else:
112113
return -1
113-
else:
114-
if Negative in b.flags:
114+
elif Negative in a.flags: # a.isNegative
115+
if b.isZero or Negative notin b.flags: # b >= 0
116+
return -1
117+
else:
118+
return unsignedCmp(b, a)
119+
else: # a > 0
120+
if b.isZero or Negative in b.flags: # b <= 0
115121
return 1
116122
else:
117123
return unsignedCmp(a, b)
118124

119125
proc cmp*(a: BigInt, b: int32): int64 =
120-
if Negative in a.flags and a.limbs != @[0'u32]:
126+
## Returns:
127+
## * a value less than zero, if `a < b`
128+
## * a value greater than zero, if `a > b`
129+
## * zero, if `a == b`
130+
if a.isZero:
121131
if b < 0:
122-
return unsignedCmp(b, a)
132+
return 1
133+
elif b == 0:
134+
return 0
123135
else:
124136
return -1
125-
else:
137+
elif Negative in a.flags: # a.isNegative
126138
if b < 0:
139+
return unsignedCmp(b, a)
140+
else:
141+
return -1
142+
else: # a > 0
143+
if b <= 0:
127144
return 1
128145
else:
129-
return unsignedCmp(a, b)
146+
return unsignedCmp(b, a)
147+
148+
proc cmp*(a: int32, b: BigInt): int64 = -cmp(b, a)
130149

131150
proc `<` *(a, b: BigInt): bool = cmp(a, b) < 0
132151
proc `<` *(a: BigInt, b: int32): bool = cmp(a, b) < 0
@@ -285,7 +304,9 @@ proc unsignedSubtraction(a: var BigInt, b, c: BigInt) =
285304
negate(a)
286305

287306
proc additionInt(a: var BigInt, b: BigInt, c: int32) =
288-
if Negative in b.flags:
307+
if b.isZero:
308+
a = c.initBigInt
309+
elif Negative in b.flags:
289310
if c < 0:
290311
unsignedAdditionInt(a, b, c)
291312
a.flags.incl(Negative)
@@ -333,7 +354,9 @@ template optAddInt*{x = y + z}(x,y: BigInt, z: int32) = additionInt(x, y, z)
333354
template optAdd*{x = y + z}(x,y,z: BigInt) = addition(x, y, z)
334355

335356
proc subtractionInt(a: var BigInt, b: BigInt, c: int32) =
336-
if Negative in b.flags:
357+
if b.isZero:
358+
a = (-c).initBigInt
359+
elif Negative in b.flags:
337360
if c < 0:
338361
# TODO: is this right?
339362
unsignedSubtractionInt(a, b, c)
@@ -390,6 +413,8 @@ template unsignedMultiplicationInt(a: BigInt, b: BigInt, c: int32, bl) =
390413
normalize(a)
391414

392415
template unsignedMultiplication(a: BigInt, b, c: BigInt, bl, cl) =
416+
# always called with bl >= cl
417+
393418
for i in 0 ..< bl:
394419
tmp += uint64(b.limbs[i]) * uint64(c.limbs[0])
395420
a.limbs[i] = uint32(tmp and uint32.high)
@@ -445,21 +470,20 @@ proc multiplicationInt(a: var BigInt, b: BigInt, c: int32) =
445470

446471
# This doesn't work when a = b
447472
proc multiplication(a: var BigInt, b, c: BigInt) =
473+
if b.isZero or c.isZero:
474+
a = zero
475+
return
448476
let
449477
bl = b.limbs.len
450478
cl = c.limbs.len
451479
var tmp: uint64
452480

453481
a.limbs.setXLen(bl + cl)
454-
455482
if cl > bl:
456483
unsignedMultiplication(a, c, b, cl, bl)
457484
else:
458485
unsignedMultiplication(a, b, c, bl, cl)
459486

460-
if a.limbs == @[0'u32]:
461-
return
462-
463487
if Negative in b.flags:
464488
if Negative in c.flags:
465489
a.flags.excl(Negative)
@@ -872,6 +896,8 @@ proc pow*(base: int32|BigInt, exp: int32|BigInt): BigInt =
872896
base *= tmp
873897

874898
proc toString*(a: BigInt, base: range[2..36] = 10): string =
899+
if a.isZero:
900+
return "0"
875901
if base in multiples:
876902
return toStringMultipleTwo(a, base)
877903

tests/tester.nim

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,122 @@ test "validate digits in string parsing (https://github.com/def-/nim-bigints/iss
109109
discard initBigInt("2", 2)
110110
expect ValueError:
111111
discard initBigInt("z", 35)
112+
113+
test "empty limbs when uninitialized (https://github.com/def-/nim-bigints/issues/26)":
114+
# reported issue has an example about multiplication and it is due to a call to a.limbs[0] for an uninitialized a: BigInt
115+
# besides multiplication, one could look at appearances of [0] in source to find possible failures
116+
# failures bound to reaching a line with [0] are fatal
117+
# besides appearances of [0], also logic implemented through a.limbs.len might (and indeed does) show error
118+
# logic around sign might also play a role
119+
var
120+
zeroEmpty: BigInt # should be treated as zero, same with -zeroEmpty
121+
let
122+
zeroInt32: int32 = 0
123+
oneInt32: int32 = 1
124+
bigOne: BigInt = initBigInt(@[0.uint32, 1])
125+
126+
# unsignedCmp(a: BigInt, b: int32) has [0]; used by public cmp and <
127+
# never reached in the following cases (no fatal), since it cannot be reached (see comment in code)
128+
# still, errors can be found in comparing zero to zero
129+
check zeroEmpty < oneInt32 # ok
130+
check zeroEmpty > -oneInt32 # ok
131+
check -zeroEmpty < oneInt32 # ok
132+
check -zeroEmpty > -oneInt32 # ok
133+
check not(zeroEmpty < zeroInt32) # error: fixed
134+
check not(zeroEmpty > zeroInt32) # ok
135+
check not(-zeroEmpty < zeroInt32) # error: fixed
136+
check not(-zeroEmpty > zeroInt32) # ok
137+
check zeroEmpty == zeroInt32 # error: fixed
138+
check -zeroEmpty == zeroInt32 # error: fixed
139+
140+
# this came up in the above testing and can be though as secondary effect of unitialization (fix in negate?)
141+
check $zero == "0" # ok
142+
check $zeroEmpty == "0" # ok
143+
check $(-zeroEmpty) == "0" # error: fixed
144+
145+
# unsignedCmp(a, b: BigInt) has no [0] but it has logic with limbs.len
146+
check zeroEmpty < one # ok
147+
check zeroEmpty > -one # ok
148+
check -zeroEmpty < one # ok
149+
check -zeroEmpty > -one # ok
150+
check not (zeroEmpty < zeroInt32) # error: fixed
151+
check not (zeroEmpty > zeroInt32) # ok
152+
check not (zeroEmpty < zero) # error: fixed
153+
check not (zeroEmpty > zero) # ok
154+
check zeroEmpty == zero # error: fixed
155+
check -zeroEmpty == zero # error: fixed
156+
157+
# proc unsignedAdditionInt(a: var BigInt, b: BigInt, c: int32)
158+
check zeroEmpty + 1.int32 == one # fixed: fatal[IndexError] in bigints.nim(181) unsignedAdditionInt
159+
check -zeroEmpty + 1.int32 == one # fixed: fatal[IndexError] in bigints.nim(245) unsignedSubtractionInt
160+
check zeroEmpty + (-1).int32 == -one # fixed: fatal[IndexError] in bigints.nim(245) unsignedSubtractionInt
161+
check -zeroEmpty + (-1).int32 == -one # fixed: fatal[IndexError] in bigints.nim(181) unsignedAdditionInt
162+
163+
# proc unsignedAddition(a: var BigInt, b, c: BigInt)
164+
check zeroEmpty + one == one # ok
165+
check one + zeroEmpty == one # ok
166+
check -zeroEmpty + one == one # ok
167+
check one + -zeroEmpty == one # ok
168+
check zeroEmpty + zeroEmpty == zero # ok
169+
check -zeroEmpty + zeroEmpty == zero # ok
170+
check -zeroEmpty + -zeroEmpty == zero # ok
171+
check zeroEmpty + -zeroEmpty == zero # ok
172+
check bigOne + zeroEmpty == bigOne # ok
173+
check bigOne + -zeroEmpty == bigOne # ok
174+
check zeroEmpty + bigOne == bigOne # ok
175+
check -zeroEmpty + bigOne == bigOne # ok
176+
check -bigOne + zeroEmpty == -bigOne # ok
177+
check -bigOne + -zeroEmpty == -bigOne # ok
178+
check zeroEmpty + -bigOne == -bigOne # ok
179+
check -zeroEmpty + -bigOne == -bigOne # ok
180+
181+
# proc unsignedSubtractionInt(a: var BigInt, b: BigInt, c: int32)
182+
check zeroEmpty - 1.int32 == -one # fixed: fatal[IndexError] in bigints.nim(245) unsignedSubtractionInt
183+
check -zeroEmpty - 1.int32 == -one # fixed: fatal[IndexError] in bigints.nim(181) unsignedAdditionInt
184+
check zeroEmpty - (-1).int32 == one # fixed: fatal[IndexError] in bigints.nim(181) unsignedAdditionInt
185+
check -zeroEmpty - (-1).int32 == one # fixed: fatal[IndexError] in bigints.nim(245) unsignedSubtractionInt
186+
187+
# proc unsignedSubtraction(a: var BigInt, b, c: BigInt)
188+
check zeroEmpty - one == -one # ok
189+
check one - zeroEmpty == one # ok
190+
check -zeroEmpty - one == -one # ok
191+
check one - -zeroEmpty == one # ok
192+
check zeroEmpty - zeroEmpty == zero # ok
193+
check -zeroEmpty - zeroEmpty == zero # ok
194+
check -zeroEmpty - -zeroEmpty == zero # ok
195+
check zeroEmpty - -zeroEmpty == zero # ok
196+
check bigOne - zeroEmpty == bigOne # ok
197+
check bigOne - -zeroEmpty == bigOne # ok
198+
check zeroEmpty - bigOne == -bigOne # ok
199+
check -zeroEmpty - bigOne == -bigOne # ok
200+
check -bigOne - zeroEmpty == -bigOne # ok
201+
check -bigOne - -zeroEmpty == -bigOne # ok
202+
check zeroEmpty - -bigOne == bigOne # ok
203+
check -zeroEmpty - -bigOne == bigOne # ok
204+
205+
# multiplication
206+
check zeroEmpty * 1.int32 == zero
207+
check -zeroEmpty * 1.int32 == zero
208+
check zeroEmpty * one == zero
209+
check -zeroEmpty * one == zero
210+
check one * zeroEmpty == zero
211+
check one * -zeroEmpty == zero
212+
213+
# https://github.com/def-/nim-bigints/issues/26
214+
block:
215+
var
216+
a: BigInt
217+
b: BigInt = 12.initBigInt
218+
219+
check a*b == 0
220+
221+
# division does not have issues, but let's add some checks
222+
check zeroEmpty div one == zero
223+
check -zeroEmpty div one == zero
224+
check zeroEmpty mod one == zero
225+
check -zeroEmpty mod one == zero
226+
227+
check zeroEmpty div 1.int32 == zero
228+
check -zeroEmpty div 1.int32 == zero
229+
check zeroEmpty mod 1.int32 == zero
230+
check -zeroEmpty mod 1.int32 == zero

0 commit comments

Comments
 (0)