Skip to content

Commit 6f00a78

Browse files
authored
Feature: new methods nextInt(Range) and nextLong(Range) (#1416)
* #1395 add methods `nextInt(Range)` and `nextLong(Range)` to define explicitly if the min/max should be inclusive or exclusive * deprecate method `Number.randomNumber(..., boolean strict)` there is no point to call `randomNumber(strict = false)`. It's the same as just `faker.random().nextLong()`.
1 parent d5f4ffb commit 6f00a78

File tree

11 files changed

+212
-49
lines changed

11 files changed

+212
-49
lines changed

src/main/java/net/datafaker/providers/base/Barcode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ private static int roundToHighestMultiplyOfTen(int number) {
4545

4646
private long ean(int length) {
4747
long firstPart = switch (length) {
48-
case 8, 12, 13, 14 -> this.faker.number().randomNumber(length - 1, true);
48+
case 8, 12, 13, 14 -> this.faker.number().randomNumber(length - 1);
4949
default -> 0;
5050
};
5151
int odd = 0;

src/main/java/net/datafaker/providers/base/DateAndTime.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ public String birthday(int minAge, int maxAge, String pattern) {
310310
/**
311311
* Generates a random Duration lower than max.
312312
*
313-
* @param max the maximum value
313+
* @param max the maximum value (exclusive in most cases)
314314
* @param unit the temporal unit (day or shorter than a day)
315315
* @return a random Duration lower than {@code max}.
316316
* @throws IllegalArgumentException if the {@code unit} is invalid.
@@ -322,8 +322,8 @@ public Duration duration(long max, ChronoUnit unit) {
322322
/**
323323
* Generates a random Duration between min and max.
324324
*
325-
* @param min the minimum value
326-
* @param max the maximum value
325+
* @param min the minimum value (inclusive)
326+
* @param max the maximum value (exclusive in most cases)
327327
* @param unit the temporal unit (day or shorter than a day)
328328
* @return a random Duration between {@code min} inclusive and {@code max} exclusive if {@code max} greater {@code min}.
329329
* @throws IllegalArgumentException if the {@code unit} is invalid.

src/main/java/net/datafaker/providers/base/Number.java

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package net.datafaker.providers.base;
22

3+
import net.datafaker.service.Range;
4+
35
import java.math.BigDecimal;
46
import java.math.RoundingMode;
57

@@ -60,8 +62,8 @@ public int numberBetween(final int min, final int max) {
6062
}
6163

6264
/**
63-
* @param min the lower bound (include min)
64-
* @param max the upper bound (not include max)
65+
* @param min the lower bound (inclusive)
66+
* @param max the upper bound (exclusive in most cases)
6567
* @return a random number on faker.number() between min and max
6668
* if min = max, return min
6769
*/
@@ -71,26 +73,31 @@ public long numberBetween(long min, long max) {
7173
final long realMax = Math.max(min, max);
7274
final long amplitude = realMax - realMin;
7375
if (amplitude >= 0) {
74-
return faker.random().nextLong(amplitude) + realMin;
76+
return faker.random().nextLong(Range.inclusiveExclusive(realMin, realMax));
7577
}
7678
return decimalBetween(realMin, realMax).longValue();
7779
}
7880

7981
/**
8082
* @param numberOfDigits the number of digits the generated value should have
81-
* @param strict whether or not the generated value should have exactly <code>numberOfDigits</code>
83+
* @param strict NOT USED
84+
* @deprecated use {@link #randomNumber(int)} instead
8285
*/
86+
@Deprecated
8387
public long randomNumber(int numberOfDigits, boolean strict) {
88+
return randomNumber(numberOfDigits);
89+
}
90+
91+
/**
92+
* @param numberOfDigits the number of digits the generated value should have
93+
*/
94+
public long randomNumber(int numberOfDigits) {
8495
if (numberOfDigits <= 0) {
85-
return faker.random().nextInt(1);
96+
throw new IllegalArgumentException("Number of digits must be positive");
8697
}
8798
long min = pow(10, numberOfDigits - 1);
88-
if (strict) {
89-
long max = min * 10;
90-
return faker.random().nextLong(max - min) + min;
91-
}
92-
93-
return faker.random().nextLong(min * 10);
99+
long max = min * 10;
100+
return faker.random().nextLong(min, max);
94101
}
95102

96103
private long pow(long value, int d) {
@@ -108,8 +115,7 @@ private long pow(long value, int d) {
108115
* Returns a random number
109116
*/
110117
public long randomNumber() {
111-
int numberOfDigits = faker.random().nextInt(1, 10);
112-
return randomNumber(numberOfDigits, false);
118+
return faker.random().nextLong();
113119
}
114120

115121
public double randomDouble(int maxNumberOfDecimals, int min, int max) {

src/main/java/net/datafaker/service/RandomService.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ public Integer nextInt(int minInclusive, int maxInclusive) {
4141
return random.nextInt(minInclusive, maxInclusive + 1);
4242
}
4343

44+
public int nextInt(Range<Integer> range) {
45+
return (int) nextLong(range.cast(Integer::longValue));
46+
}
47+
4448
@SuppressWarnings("unused")
4549
public float nextFloat() {
4650
return random.nextFloat();
@@ -50,18 +54,28 @@ public long nextLong() {
5054
return random.nextLong();
5155
}
5256

53-
public long nextLong(long maxInclusive) {
54-
if (maxInclusive <= 0) {
55-
throw new IllegalArgumentException("bound must be positive: " + maxInclusive);
57+
public long nextLong(long maxExclusive) {
58+
if (maxExclusive <= 0) {
59+
throw new IllegalArgumentException("bound must be positive: " + maxExclusive);
5660
}
61+
return nextLong(0, maxExclusive);
62+
}
5763

58-
long bits, val;
59-
do {
60-
long randomLong = random.nextLong();
61-
bits = (randomLong << 1) >>> 1;
62-
val = bits % maxInclusive;
63-
} while (bits - val + (maxInclusive - 1) < 0L);
64-
return val;
64+
public long nextLong(Range<Long> range) {
65+
return switch (range.from().end()) {
66+
case EXCLUSIVE -> switch (range.to().end()) {
67+
case EXCLUSIVE -> random.nextLong(plusOne(range.from().value()), range.to().value());
68+
case INCLUSIVE -> random.nextLong(plusOne(range.from().value()), plusOne(range.to().value()));
69+
};
70+
case INCLUSIVE -> switch (range.to().end()) {
71+
case EXCLUSIVE -> random.nextLong(range.from().value(), range.to().value());
72+
case INCLUSIVE -> random.nextLong(range.from().value(), plusOne(range.to().value()));
73+
};
74+
};
75+
}
76+
77+
private static long plusOne(long value) {
78+
return value == Long.MAX_VALUE ? value : value + 1;
6579
}
6680

6781
/**
@@ -70,7 +84,7 @@ public long nextLong(long maxInclusive) {
7084
* Otherwise, {@code max} is exclusive.
7185
*
7286
* @param min lower bound (inclusive)
73-
* @param max upper bound (exclusive in most case)
87+
* @param max upper bound (exclusive in most cases)
7488
* @return a random long value between {@code min} and {@code max}
7589
*/
7690
public long nextLong(long min, long max) {
@@ -83,6 +97,11 @@ public double nextDouble() {
8397
return random.nextDouble();
8498
}
8599

100+
/**
101+
* @param min (inclusive)
102+
* @param max (inclusive)
103+
* @return a random double value between {@code min} and {@code max} (both inclusive)
104+
*/
86105
public double nextDouble(double min, double max) {
87106
return min + (nextDouble() * (max - min));
88107
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package net.datafaker.service;
2+
3+
import java.util.function.Function;
4+
5+
public record Range<T extends Comparable<T>>(Bound<T> from, Bound<T> to) {
6+
public enum End {INCLUSIVE, EXCLUSIVE}
7+
public record Bound<T>(T value, End end) {}
8+
9+
/**
10+
* A range that contains all values
11+
* 1. greater than or equal to {@code from}, and
12+
* 2. less than or equal to {@code to}.
13+
*/
14+
public static <T extends Comparable<T>> Range<T> inclusive(T from, T to) {
15+
if (from.compareTo(to) > 0) {
16+
throw new IllegalArgumentException("Lower bound (%s) > upper bound (%s)".formatted(from, to));
17+
}
18+
return new Range<>(new Bound<>(from, End.INCLUSIVE), new Bound<>(to, End.INCLUSIVE));
19+
}
20+
21+
/**
22+
* A range that contains all values
23+
* 1. greater than or equal to {@code from}, and
24+
* 2. less than {@code to}.
25+
*/
26+
public static <T extends Comparable<T>> Range<T> inclusiveExclusive(T from, T to) {
27+
if (from.compareTo(to) >= 0) {
28+
throw new IllegalArgumentException("Lower bound (%s) >= upper bound (%s)".formatted(from, to));
29+
}
30+
return new Range<>(new Bound<>(from, End.INCLUSIVE), new Bound<>(to, End.EXCLUSIVE));
31+
}
32+
33+
/**
34+
* A range that contains all values
35+
* 1. strictly greater than {@code from}, and
36+
* 2. strictly less than {@code to}.
37+
*/
38+
public static <T extends Number & Comparable<T>> Range<T> exclusive(T from, T to) {
39+
if (to.longValue() == Long.MIN_VALUE) {
40+
throw new IllegalArgumentException("Lower bound (%s) >= upper bound (%s)".formatted(from, to));
41+
}
42+
long upperLimit = to.longValue() - 1;
43+
if (from.longValue() >= upperLimit) {
44+
throw new IllegalArgumentException("Lower bound (%s) >= upper bound-1 (%s)".formatted(from, upperLimit));
45+
}
46+
return new Range<>(new Bound<>(from, End.EXCLUSIVE), new Bound<>(to, End.EXCLUSIVE));
47+
}
48+
49+
/**
50+
* A range that contains all values
51+
* 1. strictly greater than {@code from}, and
52+
* 2. less than or equal to {@code to}.
53+
*/
54+
public static <T extends Comparable<T>> Range<T> exclusiveInclusive(T from, T to) {
55+
if (from.compareTo(to) >= 0) {
56+
throw new IllegalArgumentException("Lower bound (%s) >= upper bound (%s)".formatted(from, to));
57+
}
58+
return new Range<>(new Bound<>(from, End.EXCLUSIVE), new Bound<>(to, End.INCLUSIVE));
59+
}
60+
61+
@SuppressWarnings("unchecked")
62+
public <V extends Comparable<V>> Range<V> cast(Function<T, V> caster) {
63+
return new Range<>(
64+
new Bound<>(caster.apply(from.value), from.end),
65+
new Bound<>(caster.apply(to.value), to.end)
66+
);
67+
}
68+
}

src/test/java/net/datafaker/providers/base/NumberTest.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.function.Supplier;
1616

1717
import static org.assertj.core.api.Assertions.assertThat;
18+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1819

1920
class NumberTest extends BaseFakerTest<BaseFaker> {
2021

@@ -62,27 +63,25 @@ void testRandomNumber() {
6263
void testRandomNumberWithSingleDigitStrict() {
6364
final Number number = faker.number();
6465
for (int i = 0; i < 100; ++i) {
65-
long value = number.randomNumber(1, true);
66+
long value = number.randomNumber(1);
6667
assertThat(value).isLessThan(10L)
6768
.isGreaterThanOrEqualTo(0L);
6869
}
6970
}
7071

7172
@Test
72-
void testRandomNumberWithZeroDigitsStrict() {
73+
void randomNumberWithZeroDigits() {
7374
final Number number = faker.number();
74-
for (int i = 0; i < 100; ++i) {
75-
long value = number.randomNumber(0, true);
76-
assertThat(value).isZero();
77-
}
75+
assertThatThrownBy(() -> number.randomNumber(0))
76+
.isInstanceOf(IllegalArgumentException.class);
7877
}
7978

8079
@Test
8180
void testRandomNumberWithGivenDigitsStrict() {
8281
final Number number = faker.number();
8382
for (int i = 1; i < 9; ++i) {
8483
for (int x = 0; x < 100; ++x) {
85-
long value = number.randomNumber(i, true);
84+
long value = number.randomNumber(i);
8685
String stringValue = String.valueOf(value);
8786
assertThat(stringValue).hasSize(i);
8887
}

src/test/java/net/datafaker/providers/base/TimeAndDateTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,11 @@ private static Stream<Arguments> generateDurationsWithMinMax() {
218218
Arguments.of(65, 98, ChronoUnit.SECONDS),
219219
Arguments.of(76, 100, ChronoUnit.MILLIS),
220220
Arguments.of(879, 1030, ChronoUnit.MICROS),
221-
Arguments.of(879, 1030, ChronoUnit.NANOS)
221+
Arguments.of(879, 1030, ChronoUnit.NANOS),
222+
Arguments.of(0, Long.MAX_VALUE, ChronoUnit.NANOS),
223+
Arguments.of(Long.MIN_VALUE, 0, ChronoUnit.NANOS),
224+
Arguments.of(Long.MAX_VALUE - 1, Long.MAX_VALUE, ChronoUnit.NANOS),
225+
Arguments.of(Long.MIN_VALUE, Long.MAX_VALUE, ChronoUnit.NANOS)
222226
);
223227
}
224228

src/test/java/net/datafaker/providers/base/UniqueTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ void fetchFromYaml_shouldReturnValuesInRandomOrderUsingRandomService() {
3131

3232
faker = new BaseFaker(new Locale("test"), randomService);
3333

34-
Set<String> results = new HashSet<>();
35-
36-
results.add(faker.unique().fetchFromYaml(key));
37-
results.add(faker.unique().fetchFromYaml(key));
38-
results.add(faker.unique().fetchFromYaml(key));
39-
results.add(faker.unique().fetchFromYaml(key));
40-
results.add(faker.unique().fetchFromYaml(key));
34+
Set<String> results = Set.of(
35+
faker.unique().fetchFromYaml(key),
36+
faker.unique().fetchFromYaml(key),
37+
faker.unique().fetchFromYaml(key),
38+
faker.unique().fetchFromYaml(key),
39+
faker.unique().fetchFromYaml(key)
40+
);
4141

4242
assertThat(results)
4343
.hasSize(5)

src/test/java/net/datafaker/service/RandomNumbersTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,34 @@ class RandomNumbersTest {
1414

1515
@Test
1616
void nextInt_minInclusive_maxExclusive() {
17+
assertThat(all(() -> randomService.nextInt(3))).containsExactly(0, 1, 2);
18+
assertThat(all(() -> randomService.nextInt(2, 4))).containsExactly(2, 3, 4); // legacy
1719
assertThat(all(() -> randomService.nextLong(3))).containsExactly(0L, 1L, 2L);
1820
assertThat(all(() -> randomService.nextLong(2, 4))).containsExactly(2L, 3L);
19-
assertThat(all(() -> randomService.nextInt(3))).containsExactly(0, 1, 2);
21+
}
22+
23+
@Test
24+
void range_inclusive() {
25+
assertThat(all(() -> randomService.nextInt(Range.inclusive(2, 4)))).containsExactly(2, 3, 4);
26+
assertThat(all(() -> randomService.nextLong(Range.inclusive(2L, 4L)))).containsExactly(2L, 3L, 4L);
27+
}
28+
29+
@Test
30+
void range_inclusive_exclusive() {
31+
assertThat(all(() -> randomService.nextInt(Range.inclusiveExclusive(2, 5)))).containsExactly(2, 3, 4);
32+
assertThat(all(() -> randomService.nextLong(Range.inclusiveExclusive(2L, 5L)))).containsExactly(2L, 3L, 4L);
33+
}
2034

21-
// inclusive: see https://github.com/datafaker-net/datafaker/issues/1395
22-
assertThat(all(() -> randomService.nextInt(2, 4))).containsExactly(2, 3, 4);
35+
@Test
36+
void range_exclusive_inclusive() {
37+
assertThat(all(() -> randomService.nextInt(Range.exclusiveInclusive(2, 5)))).containsExactly(3, 4, 5);
38+
assertThat(all(() -> randomService.nextLong(Range.exclusiveInclusive(2L, 5L)))).containsExactly(3L, 4L, 5L);
39+
}
40+
41+
@Test
42+
void range_exclusive() {
43+
assertThat(all(() -> randomService.nextLong(Range.exclusive(2L, 5L)))).containsExactly(3L, 4L);
44+
assertThat(all(() -> randomService.nextInt(Range.exclusive(2, 5)))).containsExactly(3, 4);
2345
}
2446

2547
@RepeatedTest(100)
@@ -35,5 +57,4 @@ private <T extends Number & Comparable<T>> SortedSet<T> all(Supplier<T> lambda)
3557
}
3658
return result;
3759
}
38-
3960
}

src/test/java/net/datafaker/service/RandomServiceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ void testLongWithinBoundary(RandomService randomService) {
3333
assertThat(randomService.nextLong(1)).isZero();
3434

3535
for (int i = 1; i < 10; i++) {
36-
assertThat(randomService.nextLong(2)).isLessThan(2L);
36+
assertThat(randomService.nextLong(2)).isGreaterThanOrEqualTo(0).isLessThan(2L);
3737
}
3838
}
3939

@@ -87,7 +87,7 @@ void predictableRandomRange() {
8787
assertThat(f1).isEqualTo(0.41291267F);
8888

8989
assertThat(l1).isEqualTo(1092083446069765248L);
90-
assertThat(l2).isEqualTo(1L);
90+
assertThat(l2).isEqualTo(0L);
9191
assertThat(l3).isEqualTo(538L);
9292

9393
assertThat(b).isFalse();

0 commit comments

Comments
 (0)