Skip to content

Commit 355d8eb

Browse files
committed
Fixed a long-standing bug in computation of backing-array sizes
1 parent d75a9af commit 355d8eb

File tree

3 files changed

+33
-9
lines changed

3 files changed

+33
-9
lines changed

CHANGES

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
8.5.15
2+
3+
- Fixed a very long-standing subtle bug that was causing unnecessary
4+
rehashings on large tables. Maximum fills and backing-array sizes
5+
were computed using float precision, rather than double precision,
6+
making it impossible to represent all possible sizes exactly. Thanks
7+
to Captain-S0L0 for reporting this bug.
8+
19
8.5.14
210

311
- Potential improvements by array caching thanks to mouse0w0@github.com.

build.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ javadoc.base=/usr/share/javadoc
33

44
build.sysclasspath=ignore
55

6-
version=8.5.14
6+
version=8.5.15
77

88
dist=dist
99
src=src

src/it/unimi/dsi/fastutil/HashCommon.java

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ public static int long2int(final long l) {
156156
* @param x an integer smaller than or equal to 2<sup>30</sup>.
157157
* @return the least power of two greater than or equal to the specified value.
158158
*/
159-
public static int nextPowerOfTwo(int x) {
159+
public static int nextPowerOfTwo(final int x) {
160160
return 1 << (32 - Integer.numberOfLeadingZeros(x - 1));
161161
}
162162

@@ -167,7 +167,7 @@ public static int nextPowerOfTwo(int x) {
167167
* @param x a long integer smaller than or equal to 2<sup>62</sup>.
168168
* @return the least power of two greater than or equal to the specified value.
169169
*/
170-
public static long nextPowerOfTwo(long x) {
170+
public static long nextPowerOfTwo(final long x) {
171171
return 1L << (64 - Long.numberOfLeadingZeros(x - 1));
172172
}
173173

@@ -180,8 +180,12 @@ public static long nextPowerOfTwo(long x) {
180180
*/
181181
public static int maxFill(final int n, final float f) {
182182
/* We must guarantee that there is always at least
183-
* one free entry (even with pathological load factors). */
184-
return Math.min((int)Math.ceil(n * f), n - 1);
183+
* one free entry (even with pathological load factors).
184+
*
185+
* The cast to double is essential to avoid a precision
186+
* loss due to a cast to float before the call to Math.ceil.
187+
*/
188+
return Math.min((int)Math.ceil(n * (double)f), n - 1);
185189
}
186190

187191
/** Returns the maximum number of entries that can be filled before rehashing.
@@ -192,8 +196,12 @@ public static int maxFill(final int n, final float f) {
192196
*/
193197
public static long maxFill(final long n, final float f) {
194198
/* We must guarantee that there is always at least
195-
* one free entry (even with pathological load factors). */
196-
return Math.min((long)Math.ceil(n * f), n - 1);
199+
* one free entry (even with pathological load factors).
200+
*
201+
* The cast to double is essential to avoid a precision
202+
* loss due to a cast to float before the call to Math.ceil.
203+
*/
204+
return Math.min((long)Math.ceil(n * (double)f), n - 1);
197205
}
198206

199207
/** Returns the least power of two smaller than or equal to 2<sup>30</sup> and larger than or equal to {@code Math.ceil(expected / f)}.
@@ -204,7 +212,11 @@ public static long maxFill(final long n, final float f) {
204212
* @throws IllegalArgumentException if the necessary size is larger than 2<sup>30</sup>.
205213
*/
206214
public static int arraySize(final int expected, final float f) {
207-
final long s = Math.max(2, nextPowerOfTwo((long)Math.ceil(expected / f)));
215+
/*
216+
* The cast to double is essential to avoid a precision
217+
* loss due to a cast to float before the call to Math.ceil.
218+
*/
219+
final long s = Math.max(2, nextPowerOfTwo((long)Math.ceil(expected / (double)f)));
208220
if (s > (1 << 30)) throw new IllegalArgumentException("Too large (" + expected + " expected elements with load factor " + f + ")");
209221
return (int)s;
210222
}
@@ -216,6 +228,10 @@ public static int arraySize(final int expected, final float f) {
216228
* @return the minimum possible size for a backing big array.
217229
*/
218230
public static long bigArraySize(final long expected, final float f) {
219-
return nextPowerOfTwo((long)Math.ceil(expected / f));
231+
/*
232+
* The cast to double is essential to avoid a precision
233+
* loss due to a cast to float before the call to Math.ceil.
234+
*/
235+
return nextPowerOfTwo((long)Math.ceil(expected / (double)f));
220236
}
221237
}

0 commit comments

Comments
 (0)