diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 825aa7b87..2fec1efc5 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -1,6 +1,10 @@ +/* +Package core:math/rand implements various random number generators +*/ package rand import "core:intrinsics" +import "core:mem" Rand :: struct { state: u64, @@ -12,17 +16,82 @@ Rand :: struct { @(private) global_rand := create(u64(intrinsics.read_cycle_counter())) +/* +Sets the seed used by the global random number generator. + +Inputs: +- seed: The seed value + +Example: + import "core:math/rand" + import "core:fmt" + + set_global_seed_example :: proc() { + rand.set_global_seed(1) + fmt.println(rand.uint64()) + } + +Possible Output: + + 10 + +*/ set_global_seed :: proc(seed: u64) { init(&global_rand, seed) } +/* +Creates a new random number generator. + +Inputs: +- seed: The seed value to create the random number generator with + +Returns: +- res: The created random number generator + +Example: + import "core:math/rand" + import "core:fmt" + + create_example :: proc() { + my_rand := rand.create(1) + fmt.println(rand.uint64(&my_rand)) + } + +Possible Output: + + 10 + +*/ @(require_results) -create :: proc(seed: u64) -> Rand { +create :: proc(seed: u64) -> (res: Rand) { r: Rand init(&r, seed) return r } +/* +Initialises a random number generator. + +Inputs: +- r: The random number generator to initialise +- seed: The seed value to initialise this random number generator + +Example: + import "core:math/rand" + import "core:fmt" + + init_example :: proc() { + my_rand: rand.Rand + rand.init(&my_rand, 1) + fmt.println(rand.uint64(&my_rand)) + } + +Possible Output: + + 10 + +*/ init :: proc(r: ^Rand, seed: u64) { r.state = 0 r.inc = (seed << 1) | 1 @@ -31,6 +100,35 @@ init :: proc(r: ^Rand, seed: u64) { _random(r) } +/* +Initialises a random number generator to use the system random number generator. +The system random number generator is platform specific. +On `linux` refer to the `getrandom` syscall. +On `darwin` refer to `getentropy`. +On `windows` refer to `BCryptGenRandom`. + +All other platforms wi + +Inputs: +- r: The random number generator to use the system random number generator + +WARNING: Panics if the system is not either `windows`, `darwin` or `linux` + +Example: + import "core:math/rand" + import "core:fmt" + + init_as_system_example :: proc() { + my_rand: rand.Rand + rand.init_as_system(&my_rand) + fmt.println(rand.uint64(&my_rand)) + } + +Possible Output: + + 10 + +*/ init_as_system :: proc(r: ^Rand) { if !#defined(_system_random) { panic(#procedure + " is not supported on this platform yet") @@ -61,18 +159,99 @@ _random :: proc(r: ^Rand) -> u32 { return (xor_shifted >> rot) | (xor_shifted << ((-rot) & 31)) } -@(require_results) -uint32 :: proc(r: ^Rand = nil) -> u32 { return _random(r) } +/* +Generates a random 32 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random unsigned 32 bit value + +Example: + import "core:math/rand" + import "core:fmt" + + uint32_example :: proc() { + // Using the global random number generator + fmt.println(rand.uint32()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.uint32(&my_rand)) + } + +Possible Output: + + 10 + 389 + +*/ @(require_results) -uint64 :: proc(r: ^Rand = nil) -> u64 { +uint32 :: proc(r: ^Rand = nil) -> (val: u32) { return _random(r) } + +/* +Generates a random 64 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random unsigned 64 bit value + +Example: + import "core:math/rand" + import "core:fmt" + + uint64_example :: proc() { + // Using the global random number generator + fmt.println(rand.uint64()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.uint64(&my_rand)) + } + +Possible Output: + + 10 + 389 + +*/ +@(require_results) +uint64 :: proc(r: ^Rand = nil) -> (val: u64) { a := u64(_random(r)) b := u64(_random(r)) return (a<<32) | b } +/* +Generates a random 128 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random unsigned 128 bit value + +Example: + import "core:math/rand" + import "core:fmt" + + uint128_example :: proc() { + // Using the global random number generator + fmt.println(rand.uint128()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.uint128(&my_rand)) + } + +Possible Output: + + 10 + 389 + +*/ @(require_results) -uint128 :: proc(r: ^Rand = nil) -> u128 { +uint128 :: proc(r: ^Rand = nil) -> (val: u128) { a := u128(_random(r)) b := u128(_random(r)) c := u128(_random(r)) @@ -80,12 +259,126 @@ uint128 :: proc(r: ^Rand = nil) -> u128 { return (a<<96) | (b<<64) | (c<<32) | d } -@(require_results) int31 :: proc(r: ^Rand = nil) -> i32 { return i32(uint32(r) << 1 >> 1) } -@(require_results) int63 :: proc(r: ^Rand = nil) -> i64 { return i64(uint64(r) << 1 >> 1) } -@(require_results) int127 :: proc(r: ^Rand = nil) -> i128 { return i128(uint128(r) << 1 >> 1) } +/* +Generates a random 31 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +The sign bit will always be set to 0, thus all generated numbers will be positive. +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random 31 bit value + +Example: + import "core:math/rand" + import "core:fmt" + + int31_example :: proc() { + // Using the global random number generator + fmt.println(rand.int31()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int31(&my_rand)) + } + +Possible Output: + + 10 + 389 + +*/ +@(require_results) int31 :: proc(r: ^Rand = nil) -> (val: i32) { return i32(uint32(r) << 1 >> 1) } +/* +Generates a random 63 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +The sign bit will always be set to 0, thus all generated numbers will be positive. + +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random 63 bit value + +Example: + import "core:math/rand" + import "core:fmt" + + int63_example :: proc() { + // Using the global random number generator + fmt.println(rand.int63()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int63(&my_rand)) + } + +Possible Output: + + 10 + 389 + +*/ +@(require_results) int63 :: proc(r: ^Rand = nil) -> (val: i64) { return i64(uint64(r) << 1 >> 1) } +/* +Generates a random 127 bit value using the provided random number generator. If no generator is provided the global random number generator will be used. +The sign bit will always be set to 0, thus all generated numbers will be positive. + +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random 127 bit value + +Example: + import "core:math/rand" + import "core:fmt" + + int127_example :: proc() { + // Using the global random number generator + fmt.println(rand.int127()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int127(&my_rand)) + } + +Possible Output: + + 10 + 389 + +*/ +@(require_results) int127 :: proc(r: ^Rand = nil) -> (val: i128) { return i128(uint128(r) << 1 >> 1) } + +/* +Generates a random 31 bit value in the range `(0, n]` using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- n: The upper bound of the generated number, this value is exclusive +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random 31 bit value in the range `(0, n]` + +WARNING: Panics if n is less than 0 + +Example: + import "core:math/rand" + import "core:fmt" + + int31_max_example :: proc() { + // Using the global random number generator + fmt.println(rand.int31_max(16)) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int31_max(1024, &my_rand)) + } + +Possible Output: + + 6 + 500 + +*/ @(require_results) -int31_max :: proc(n: i32, r: ^Rand = nil) -> i32 { +int31_max :: proc(n: i32, r: ^Rand = nil) -> (val: i32) { if n <= 0 { panic("Invalid argument to int31_max") } @@ -99,9 +392,38 @@ int31_max :: proc(n: i32, r: ^Rand = nil) -> i32 { } return v % n } +/* +Generates a random 63 bit value in the range `(0, n]` using the provided random number generator. If no generator is provided the global random number generator will be used. +Inputs: +- n: The upper bound of the generated number, this value is exclusive +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random 63 bit value in the range `(0, n]` + +WARNING: Panics if n is less than 0 + +Example: + import "core:math/rand" + import "core:fmt" + + int63_max_example :: proc() { + // Using the global random number generator + fmt.println(rand.int63_max(16)) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int63_max(1024, &my_rand)) + } + +Possible Output: + + 6 + 500 + +*/ @(require_results) -int63_max :: proc(n: i64, r: ^Rand = nil) -> i64 { +int63_max :: proc(n: i64, r: ^Rand = nil) -> (val: i64) { if n <= 0 { panic("Invalid argument to int63_max") } @@ -115,9 +437,38 @@ int63_max :: proc(n: i64, r: ^Rand = nil) -> i64 { } return v % n } +/* +Generates a random 127 bit value in the range `(0, n]` using the provided random number generator. If no generator is provided the global random number generator will be used. +Inputs: +- n: The upper bound of the generated number, this value is exclusive +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random 127 bit value in the range `(0, n]` + +WARNING: Panics if n is less than 0 + +Example: + import "core:math/rand" + import "core:fmt" + + int127_max_example :: proc() { + // Using the global random number generator + fmt.println(rand.int127_max(16)) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int127_max(1024, &my_rand)) + } + +Possible Output: + + 6 + 500 + +*/ @(require_results) -int127_max :: proc(n: i128, r: ^Rand = nil) -> i128 { +int127_max :: proc(n: i128, r: ^Rand = nil) -> (val: i128) { if n <= 0 { panic("Invalid argument to int127_max") } @@ -131,9 +482,38 @@ int127_max :: proc(n: i128, r: ^Rand = nil) -> i128 { } return v % n } +/* +Generates a random integer value in the range `(0, n]` using the provided random number generator. If no generator is provided the global random number generator will be used. +Inputs: +- n: The upper bound of the generated number, this value is exclusive +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random integer value in the range `(0, n]` + +WARNING: Panics if n is less than 0 + +Example: + import "core:math/rand" + import "core:fmt" + + int_max_example :: proc() { + // Using the global random number generator + fmt.println(rand.int_max(16)) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.int_max(1024, &my_rand)) + } + +Possible Output: + + 6 + 500 + +*/ @(require_results) -int_max :: proc(n: int, r: ^Rand = nil) -> int { +int_max :: proc(n: int, r: ^Rand = nil) -> (val: int) { if n <= 0 { panic("Invalid argument to int_max") } @@ -144,14 +524,153 @@ int_max :: proc(n: int, r: ^Rand = nil) -> int { } } -// Uniform random distribution [0, 1) -@(require_results) float64 :: proc(r: ^Rand = nil) -> f64 { return f64(int63_max(1<<53, r)) / (1 << 53) } -// Uniform random distribution [0, 1) -@(require_results) float32 :: proc(r: ^Rand = nil) -> f32 { return f32(float64(r)) } +/* +Generates a random double floating point value in the range `(0, 1]` using the provided random number generator. If no generator is provided the global random number generator will be used. -@(require_results) float64_range :: proc(lo, hi: f64, r: ^Rand = nil) -> f64 { return (hi-lo)*float64(r) + lo } -@(require_results) float32_range :: proc(lo, hi: f32, r: ^Rand = nil) -> f32 { return (hi-lo)*float32(r) + lo } +Inputs: +- r: The random number generator to use, or nil for the global generator +Returns: +- val: A random double floating point value in the range `(0, 1]` + +Example: + import "core:math/rand" + import "core:fmt" + + float64_example :: proc() { + // Using the global random number generator + fmt.println(rand.float64()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.float64(&my_rand)) + } + +Possible Output: + + 0.043 + 0.511 + +*/ +@(require_results) float64 :: proc(r: ^Rand = nil) -> (val: f64) { return f64(int63_max(1<<53, r)) / (1 << 53) } + +/* +Generates a random single floating point value in the range `(0, 1]` using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random single floating point value in the range `(0, 1]` + +Example: + import "core:math/rand" + import "core:fmt" + + float32_example :: proc() { + // Using the global random number generator + fmt.println(rand.float32()) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.float32(&my_rand)) + } + +Possible Output: + + 0.043 + 0.511 + +*/ +@(require_results) float32 :: proc(r: ^Rand = nil) -> (val: f32) { return f32(float64(r)) } + +/* +Generates a random double floating point value in the range `(low, high]` using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- low: The lower bounds of the value, this value is inclusive +- high: The upper bounds of the value, this value is exclusive +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random double floating point value in the range `(low, high]` + +Example: + import "core:math/rand" + import "core:fmt" + + float64_range_example :: proc() { + // Using the global random number generator + fmt.println(rand.float64_range(-10, 300)) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.float64_range(600, 900, &my_rand)) + } + +Possible Output: + + 15.312 + 673.130 + +*/ +@(require_results) float64_range :: proc(low, high: f64, r: ^Rand = nil) -> (val: f64) { return (high-low)*float64(r) + low } +/* +Generates a random single floating point value in the range `(low, high]` using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- low: The lower bounds of the value, this value is inclusive +- high: The upper bounds of the value, this value is exclusive +- r: The random number generator to use, or nil for the global generator + +Returns: +- val: A random single floating point value in the range `(low, high]` + +Example: + import "core:math/rand" + import "core:fmt" + + float32_range_example :: proc() { + // Using the global random number generator + fmt.println(rand.float32_range(-10, 300)) + // Using local random number generator + my_rand := rand.create(1) + fmt.println(rand.float32_range(600, 900, &my_rand)) + } + +Possible Output: + + 15.312 + 673.130 + +*/ +@(require_results) float32_range :: proc(low, high: f32, r: ^Rand = nil) -> (val: f32) { return (high-low)*float32(r) + low } + +/* +Fills a byte slice with random values using the provided random number generator. If no generator is provided the global random number generator will be used. + +Inputs: +- p: The byte slice to fill +- r: The random number generator to use, or nil for the global generator + +Returns: +- n: The number of bytes generated + +Example: + import "core:math/rand" + import "core:fmt" + + read_example :: proc() { + // Using the global random number generator + data: [8]byte + n := rand.read(data[:]) + fmt.println(n) + fmt.println(data) + } + +Possible Output: + + 8 + [32, 4, 59, 7, 1, 2, 2, 119] + +*/ @(require_results) read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) { pos := i8(0) @@ -168,19 +687,81 @@ read :: proc(p: []byte, r: ^Rand = nil) -> (n: int) { return } -// perm returns a slice of n ints in a pseudo-random permutation of integers in the range [0, n) +/* +Creates a slice of `int` filled with random values using the provided random number generator. If no generator is provided the global random number generator will be used. + +*Allocates Using Provided Allocator* + +Inputs: +- n: The size of the created slice +- r: The random number generator to use, or nil for the global generator +- allocator: (default: context.allocator) + +Returns: +- res: A slice filled with random values +- err: An allocator error if one occured, `nil` otherwise + +Example: + import "core:math/rand" + import "core:mem" + import "core:fmt" + + perm_example :: proc() -> (err: mem.Allocator_Error) { + // Using the global random number generator and using the context allocator + data := rand.perm(4) or_return + fmt.println(data) + defer delete(data, context.allocator) + + // Using local random number generator and temp allocator + my_rand := rand.create(1) + data_tmp := rand.perm(4, &my_rand, context.temp_allocator) or_return + fmt.println(data_tmp) + + return + } + +Possible Output: + + [7201011, 3, 9123, 231131] + [19578, 910081, 131, 7] + +*/ @(require_results) -perm :: proc(n: int, r: ^Rand = nil, allocator := context.allocator) -> []int { - m := make([]int, n, allocator) +perm :: proc(n: int, r: ^Rand = nil, allocator := context.allocator) -> (res: []int, err: mem.Allocator_Error) #optional_allocator_error { + m := make([]int, n, allocator) or_return for i := 0; i < n; i += 1 { j := int_max(i+1, r) m[i] = m[j] m[j] = i } - return m + return m, {} } +/* +Randomizes the ordering of elements for the provided slice. If no generator is provided the global random number generator will be used. +Inputs: +- array: The slice to randomize +- r: The random number generator to use, or nil for the global generator + +Example: + import "core:math/rand" + import "core:fmt" + + shuffle_example :: proc() { + // Using the global random number generator + data: [4]int = { 1, 2, 3, 4 } + fmt.println(data) // the contents are in order + rand.shuffle(data[:]) + fmt.println(data) // the contents have been shuffled + } + +Possible Output: + + [1, 2, 3, 4] + [2, 4, 3, 1] + +*/ shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) { n := i64(len(array)) if n < 2 { @@ -193,7 +774,38 @@ shuffle :: proc(array: $T/[]$E, r: ^Rand = nil) { } } -// Returns a random element from the given slice +/* +Returns a random element from the provided slice. If no generator is provided the global random number generator will be used. + +Inputs: +- array: The slice to choose an element from +- r: The random number generator to use, or nil for the global generator + +Returns: +- res: A random element from `array` + +Example: + import "core:math/rand" + import "core:fmt" + + choice_example :: proc() { + // Using the global random number generator + data: [4]int = { 1, 2, 3, 4 } + fmt.println(rand.choice(data[:])) + fmt.println(rand.choice(data[:])) + fmt.println(rand.choice(data[:])) + fmt.println(rand.choice(data[:])) + } + + +Possible Output: + + 3 + 2 + 2 + 4 + +*/ @(require_results) choice :: proc(array: $T/[]$E, r: ^Rand = nil) -> (res: E) { n := i64(len(array)) @@ -201,4 +813,4 @@ choice :: proc(array: $T/[]$E, r: ^Rand = nil) -> (res: E) { return E{} } return array[int63_max(n, r)] -} \ No newline at end of file +} diff --git a/tests/documentation/documentation_tester.odin b/tests/documentation/documentation_tester.odin index efba63f88..086060000 100644 --- a/tests/documentation/documentation_tester.odin +++ b/tests/documentation/documentation_tester.odin @@ -14,6 +14,7 @@ Example_Test :: struct { package_name: string, example_code: []string, expected_output: []string, + skip_output_check: bool, } g_header: ^doc.Header @@ -145,6 +146,7 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s curr_block_kind := Block_Kind.Other start := 0 + found_possible_output: bool example_block: Block // when set the kind should be Example output_block: Block // when set the kind should be Output // rely on zii that the kinds have not been set @@ -178,10 +180,16 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s switch { case strings.has_prefix(line, "Example:"): next_block_kind = .Example case strings.has_prefix(line, "Output:"): next_block_kind = .Output + case strings.has_prefix(line, "Possible Output:"): + next_block_kind = .Output + found_possible_output = true } case .Example: switch { case strings.has_prefix(line, "Output:"): next_block_kind = .Output + case strings.has_prefix(line, "Possible Output:"): + next_block_kind = .Output + found_possible_output = true case ! (text == "" || strings.has_prefix(line, "\t")): next_block_kind = .Other } case .Output: @@ -219,8 +227,9 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s { // Output block starts with // `Output:` and a number of white spaces, + // `Possible Output:` and a number of white spaces, lines := &output_block.lines - for len(lines) > 0 && (strings.trim_space(lines[0]) == "" || strings.has_prefix(lines[0], "Output:")) { + for len(lines) > 0 && (strings.trim_space(lines[0]) == "" || strings.has_prefix(lines[0], "Output:") || strings.has_prefix(lines[0], "Possible Output:")) { lines^ = lines[1:] } // Additionally we need to strip all empty lines at the end of output to not include those in the expected output @@ -240,6 +249,7 @@ find_and_add_examples :: proc(docs: string, package_name: string, entity_name: s package_name = package_name, example_code = example_block.lines, expected_output = output_block.lines, + skip_output_check = found_possible_output, }) } } @@ -404,25 +414,29 @@ main :: proc() { continue } - fmt.sbprintf(&test_runner, "\t%v_%v()\n", test.package_name, code_test_name) - fmt.sbprintf(&test_runner, "\t_check(%q, `", code_test_name) - had_line_error: bool - for line in test.expected_output { - // NOTE: this will escape the multiline string. Even with a backslash it still escapes due to the semantics of ` - // I don't think any examples would really need this specific character so let's just make it forbidden and change - // in the future if we really need to - if strings.contains_rune(line, '`') { - fmt.eprintf("The line %q in the output for \"%s.%s\" contains a ` which is not allowed\n", line, test.package_name, test.entity_name) - g_bad_doc = true - had_line_error = true + // NOTE: packages like 'rand' are random by nature, in these cases we cannot verify against the output string + // in these cases we just mark the output as 'Possible Output' and we simply skip checking against the output + if ! test.skip_output_check { + fmt.sbprintf(&test_runner, "\t%v_%v()\n", test.package_name, code_test_name) + fmt.sbprintf(&test_runner, "\t_check(%q, `", code_test_name) + had_line_error: bool + for line in test.expected_output { + // NOTE: this will escape the multiline string. Even with a backslash it still escapes due to the semantics of ` + // I don't think any examples would really need this specific character so let's just make it forbidden and change + // in the future if we really need to + if strings.contains_rune(line, '`') { + fmt.eprintf("The line %q in the output for \"%s.%s\" contains a ` which is not allowed\n", line, test.package_name, test.entity_name) + g_bad_doc = true + had_line_error = true + } + strings.write_string(&test_runner, line) + strings.write_string(&test_runner, "\n") } - strings.write_string(&test_runner, line) - strings.write_string(&test_runner, "\n") + if had_line_error { + continue + } + strings.write_string(&test_runner, "`)\n") } - if had_line_error { - continue - } - strings.write_string(&test_runner, "`)\n") save_path := fmt.tprintf("verify/test_%v_%v.odin", test.package_name, code_test_name) test_file_handle, err := os.open(save_path, os.O_WRONLY | os.O_CREATE); if err != 0 {