pseudo_random 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +297 -15
- data/CHANGELOG.md +29 -6
- data/INSTALLATION.md +70 -0
- data/README.md +22 -9
- data/README_CPP.md +169 -0
- data/Rakefile +30 -1
- data/ext/pseudo_random_native/extconf.rb +26 -0
- data/ext/pseudo_random_native/pseudo_random_native.cpp +216 -0
- data/lib/pseudo_random/seed.rb +110 -0
- data/lib/pseudo_random/version.rb +1 -1
- data/lib/pseudo_random.rb +14 -100
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea6d1ceeeffde9f6428fbc99f74a8a3531c654aa42406592112da203b8c4aafb
|
|
4
|
+
data.tar.gz: ca518011da7918a0526ea200e3dd6873b98ffae7c770fcc55b199323e0763e26
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33d3304a9b75f9107fab384a3aed3d7cb21d26733fbb72a1ac0323e40604dbc2948bf7be208bbe4bf95ce174d7a759fb2ed54d9f7451ae4469e8b61849d4e4e9
|
|
7
|
+
data.tar.gz: 7332ebea107f056678cae26fb02f0520f42d4e47d866fc4432af1e4083ef72d11829c22406016a40bc4bc00351927b0ac37cf6148f25f33e59c6ba2edf66bdfd
|
data/.rubocop.yml
CHANGED
|
@@ -1,50 +1,332 @@
|
|
|
1
1
|
AllCops:
|
|
2
2
|
TargetRubyVersion: 3.1
|
|
3
|
+
Include:
|
|
4
|
+
- 'lib/**/*'
|
|
5
|
+
- 'test/**/*'
|
|
3
6
|
Exclude:
|
|
4
7
|
- 'pseudo_random.gemspec'
|
|
8
|
+
- 'vendor/**/*'
|
|
5
9
|
|
|
6
|
-
##################### Layout
|
|
10
|
+
##################### Layout #################################
|
|
7
11
|
Layout/LineLength:
|
|
8
12
|
Max: 120
|
|
9
|
-
|
|
10
13
|
Layout/EndOfLine:
|
|
11
14
|
EnforcedStyle: lf
|
|
12
|
-
|
|
13
15
|
Layout/EmptyLinesAroundAttributeAccessor:
|
|
14
16
|
Enabled: true
|
|
15
|
-
|
|
16
17
|
Layout/SpaceAroundMethodCallOperator:
|
|
17
18
|
Enabled: true
|
|
19
|
+
Layout/EmptyLinesAfterModuleInclusion:
|
|
20
|
+
Enabled: true
|
|
21
|
+
Layout/LineContinuationLeadingSpace:
|
|
22
|
+
Enabled: true
|
|
23
|
+
Layout/LineContinuationSpacing:
|
|
24
|
+
Enabled: true
|
|
25
|
+
Layout/LineEndStringConcatenationIndentation:
|
|
26
|
+
Enabled: true
|
|
27
|
+
Layout/SpaceBeforeBrackets:
|
|
28
|
+
Enabled: true
|
|
18
29
|
|
|
19
|
-
##################### Style
|
|
30
|
+
##################### Style ##################################
|
|
20
31
|
Style/Documentation:
|
|
21
32
|
Enabled: false
|
|
22
|
-
|
|
23
33
|
Style/StringLiterals:
|
|
24
34
|
EnforcedStyle: single_quotes
|
|
25
|
-
|
|
26
35
|
Style/StringLiteralsInInterpolation:
|
|
27
36
|
EnforcedStyle: single_quotes
|
|
28
|
-
|
|
29
37
|
Style/NumericPredicate:
|
|
30
38
|
Enabled: false
|
|
31
39
|
|
|
32
40
|
##################### Naming #################################
|
|
33
41
|
Naming/VariableNumber:
|
|
34
42
|
Enabled: false
|
|
43
|
+
Naming/BlockForwarding:
|
|
44
|
+
Enabled: true
|
|
45
|
+
Naming/PredicateMethod:
|
|
46
|
+
Enabled: true
|
|
35
47
|
|
|
36
|
-
##################### Metrics
|
|
48
|
+
##################### Metrics ################################
|
|
37
49
|
Metrics/ClassLength:
|
|
38
50
|
Max: 200
|
|
39
|
-
|
|
40
51
|
Metrics/MethodLength:
|
|
41
52
|
Max: 80
|
|
42
|
-
|
|
43
53
|
Metrics/AbcSize:
|
|
44
|
-
Max:
|
|
45
|
-
|
|
54
|
+
Max: 80
|
|
46
55
|
Metrics/CyclomaticComplexity:
|
|
47
56
|
Max: 20
|
|
48
|
-
|
|
49
57
|
Metrics/PerceivedComplexity:
|
|
50
|
-
Max: 15
|
|
58
|
+
Max: 15
|
|
59
|
+
Metrics/CollectionLiteralLength:
|
|
60
|
+
Enabled: true
|
|
61
|
+
|
|
62
|
+
##################### Gemspec ################################
|
|
63
|
+
Gemspec/AddRuntimeDependency:
|
|
64
|
+
Enabled: true
|
|
65
|
+
Gemspec/AttributeAssignment:
|
|
66
|
+
Enabled: true
|
|
67
|
+
Gemspec/DeprecatedAttributeAssignment:
|
|
68
|
+
Enabled: true
|
|
69
|
+
Gemspec/DevelopmentDependencies:
|
|
70
|
+
Enabled: true
|
|
71
|
+
Gemspec/RequireMFA:
|
|
72
|
+
Enabled: true
|
|
73
|
+
|
|
74
|
+
##################### Lint ###################################
|
|
75
|
+
Lint/AmbiguousAssignment:
|
|
76
|
+
Enabled: true
|
|
77
|
+
Lint/AmbiguousOperatorPrecedence:
|
|
78
|
+
Enabled: true
|
|
79
|
+
Lint/AmbiguousRange:
|
|
80
|
+
Enabled: true
|
|
81
|
+
Lint/ArrayLiteralInRegexp:
|
|
82
|
+
Enabled: true
|
|
83
|
+
Lint/ConstantOverwrittenInRescue:
|
|
84
|
+
Enabled: true
|
|
85
|
+
Lint/ConstantReassignment:
|
|
86
|
+
Enabled: true
|
|
87
|
+
Lint/CopDirectiveSyntax:
|
|
88
|
+
Enabled: true
|
|
89
|
+
Lint/DeprecatedConstants:
|
|
90
|
+
Enabled: true
|
|
91
|
+
Lint/DuplicateBranch:
|
|
92
|
+
Enabled: true
|
|
93
|
+
Lint/DuplicateMagicComment:
|
|
94
|
+
Enabled: true
|
|
95
|
+
Lint/DuplicateMatchPattern:
|
|
96
|
+
Enabled: true
|
|
97
|
+
Lint/DuplicateRegexpCharacterClassElement:
|
|
98
|
+
Enabled: true
|
|
99
|
+
Lint/DuplicateSetElement:
|
|
100
|
+
Enabled: true
|
|
101
|
+
Lint/EmptyBlock:
|
|
102
|
+
Enabled: true
|
|
103
|
+
Lint/EmptyClass:
|
|
104
|
+
Enabled: true
|
|
105
|
+
Lint/EmptyInPattern:
|
|
106
|
+
Enabled: true
|
|
107
|
+
Lint/HashNewWithKeywordArgumentsAsDefault:
|
|
108
|
+
Enabled: true
|
|
109
|
+
Lint/IncompatibleIoSelectWithFiberScheduler:
|
|
110
|
+
Enabled: true
|
|
111
|
+
Lint/ItWithoutArgumentsInBlock:
|
|
112
|
+
Enabled: true
|
|
113
|
+
Lint/LambdaWithoutLiteralBlock:
|
|
114
|
+
Enabled: true
|
|
115
|
+
Lint/LiteralAssignmentInCondition:
|
|
116
|
+
Enabled: true
|
|
117
|
+
Lint/MixedCaseRange:
|
|
118
|
+
Enabled: true
|
|
119
|
+
Lint/NoReturnInBeginEndBlocks:
|
|
120
|
+
Enabled: true
|
|
121
|
+
Lint/NonAtomicFileOperation:
|
|
122
|
+
Enabled: true
|
|
123
|
+
Lint/NumberedParameterAssignment:
|
|
124
|
+
Enabled: true
|
|
125
|
+
Lint/NumericOperationWithConstantResult:
|
|
126
|
+
Enabled: true
|
|
127
|
+
Lint/OrAssignmentToConstant:
|
|
128
|
+
Enabled: true
|
|
129
|
+
Lint/RedundantDirGlobSort:
|
|
130
|
+
Enabled: true
|
|
131
|
+
Lint/RedundantRegexpQuantifiers:
|
|
132
|
+
Enabled: true
|
|
133
|
+
Lint/RedundantTypeConversion:
|
|
134
|
+
Enabled: true
|
|
135
|
+
Lint/RefinementImportMethods:
|
|
136
|
+
Enabled: true
|
|
137
|
+
Lint/RequireRangeParentheses:
|
|
138
|
+
Enabled: true
|
|
139
|
+
Lint/RequireRelativeSelfPath:
|
|
140
|
+
Enabled: true
|
|
141
|
+
Lint/SharedMutableDefault:
|
|
142
|
+
Enabled: true
|
|
143
|
+
Lint/SuppressedExceptionInNumberConversion:
|
|
144
|
+
Enabled: true
|
|
145
|
+
Lint/SymbolConversion:
|
|
146
|
+
Enabled: true
|
|
147
|
+
Lint/ToEnumArguments:
|
|
148
|
+
Enabled: true
|
|
149
|
+
Lint/TripleQuotes:
|
|
150
|
+
Enabled: true
|
|
151
|
+
Lint/UnescapedBracketInRegexp:
|
|
152
|
+
Enabled: true
|
|
153
|
+
Lint/UnexpectedBlockArity:
|
|
154
|
+
Enabled: true
|
|
155
|
+
Lint/UnmodifiedReduceAccumulator:
|
|
156
|
+
Enabled: true
|
|
157
|
+
Lint/UselessConstantScoping:
|
|
158
|
+
Enabled: true
|
|
159
|
+
Lint/UselessDefaultValueArgument:
|
|
160
|
+
Enabled: true
|
|
161
|
+
Lint/UselessDefined:
|
|
162
|
+
Enabled: true
|
|
163
|
+
Lint/UselessNumericOperation:
|
|
164
|
+
Enabled: true
|
|
165
|
+
Lint/UselessOr:
|
|
166
|
+
Enabled: true
|
|
167
|
+
Lint/UselessRescue:
|
|
168
|
+
Enabled: true
|
|
169
|
+
Lint/UselessRuby2Keywords:
|
|
170
|
+
Enabled: true
|
|
171
|
+
|
|
172
|
+
##################### Security ###############################
|
|
173
|
+
Security/CompoundHash:
|
|
174
|
+
Enabled: true
|
|
175
|
+
Security/IoMethods:
|
|
176
|
+
Enabled: true
|
|
177
|
+
|
|
178
|
+
##################### Style ##################################
|
|
179
|
+
Style/AmbiguousEndlessMethodDefinition:
|
|
180
|
+
Enabled: true
|
|
181
|
+
Style/ArgumentsForwarding:
|
|
182
|
+
Enabled: true
|
|
183
|
+
Style/ArrayIntersect:
|
|
184
|
+
Enabled: true
|
|
185
|
+
Style/BitwisePredicate:
|
|
186
|
+
Enabled: true
|
|
187
|
+
Style/CollectionCompact:
|
|
188
|
+
Enabled: true
|
|
189
|
+
Style/CollectionQuerying:
|
|
190
|
+
Enabled: true
|
|
191
|
+
Style/CombinableDefined:
|
|
192
|
+
Enabled: true
|
|
193
|
+
Style/ComparableBetween:
|
|
194
|
+
Enabled: true
|
|
195
|
+
Style/ComparableClamp:
|
|
196
|
+
Enabled: true
|
|
197
|
+
Style/ConcatArrayLiterals:
|
|
198
|
+
Enabled: true
|
|
199
|
+
Style/DataInheritance:
|
|
200
|
+
Enabled: true
|
|
201
|
+
Style/DigChain:
|
|
202
|
+
Enabled: true
|
|
203
|
+
Style/DirEmpty:
|
|
204
|
+
Enabled: true
|
|
205
|
+
Style/DocumentDynamicEvalDefinition:
|
|
206
|
+
Enabled: true
|
|
207
|
+
Style/EmptyHeredoc:
|
|
208
|
+
Enabled: true
|
|
209
|
+
Style/EmptyStringInsideInterpolation:
|
|
210
|
+
Enabled: true
|
|
211
|
+
Style/EndlessMethod:
|
|
212
|
+
Enabled: true
|
|
213
|
+
Style/EnvHome:
|
|
214
|
+
Enabled: true
|
|
215
|
+
Style/ExactRegexpMatch:
|
|
216
|
+
Enabled: true
|
|
217
|
+
Style/FetchEnvVar:
|
|
218
|
+
Enabled: true
|
|
219
|
+
Style/FileEmpty:
|
|
220
|
+
Enabled: true
|
|
221
|
+
Style/FileNull:
|
|
222
|
+
Enabled: true
|
|
223
|
+
Style/FileRead:
|
|
224
|
+
Enabled: true
|
|
225
|
+
Style/FileTouch:
|
|
226
|
+
Enabled: true
|
|
227
|
+
Style/FileWrite:
|
|
228
|
+
Enabled: true
|
|
229
|
+
Style/HashConversion:
|
|
230
|
+
Enabled: true
|
|
231
|
+
Style/HashExcept:
|
|
232
|
+
Enabled: true
|
|
233
|
+
Style/HashFetchChain:
|
|
234
|
+
Enabled: true
|
|
235
|
+
Style/HashSlice:
|
|
236
|
+
Enabled: true
|
|
237
|
+
Style/IfWithBooleanLiteralBranches:
|
|
238
|
+
Enabled: true
|
|
239
|
+
Style/InPatternThen:
|
|
240
|
+
Enabled: true
|
|
241
|
+
Style/ItAssignment:
|
|
242
|
+
Enabled: true
|
|
243
|
+
Style/ItBlockParameter:
|
|
244
|
+
Enabled: true
|
|
245
|
+
Style/KeywordArgumentsMerging:
|
|
246
|
+
Enabled: true
|
|
247
|
+
Style/MagicCommentFormat:
|
|
248
|
+
Enabled: true
|
|
249
|
+
Style/MapCompactWithConditionalBlock:
|
|
250
|
+
Enabled: true
|
|
251
|
+
Style/MapIntoArray:
|
|
252
|
+
Enabled: true
|
|
253
|
+
Style/MapToHash:
|
|
254
|
+
Enabled: true
|
|
255
|
+
Style/MapToSet:
|
|
256
|
+
Enabled: true
|
|
257
|
+
Style/MinMaxComparison:
|
|
258
|
+
Enabled: true
|
|
259
|
+
Style/MultilineInPatternThen:
|
|
260
|
+
Enabled: true
|
|
261
|
+
Style/NegatedIfElseCondition:
|
|
262
|
+
Enabled: true
|
|
263
|
+
Style/NestedFileDirname:
|
|
264
|
+
Enabled: true
|
|
265
|
+
Style/NilLambda:
|
|
266
|
+
Enabled: true
|
|
267
|
+
Style/NumberedParameters:
|
|
268
|
+
Enabled: true
|
|
269
|
+
Style/NumberedParametersLimit:
|
|
270
|
+
Enabled: true
|
|
271
|
+
Style/ObjectThen:
|
|
272
|
+
Enabled: true
|
|
273
|
+
Style/OpenStructUse:
|
|
274
|
+
Enabled: true
|
|
275
|
+
Style/OperatorMethodCall:
|
|
276
|
+
Enabled: true
|
|
277
|
+
Style/QuotedSymbols:
|
|
278
|
+
Enabled: true
|
|
279
|
+
Style/RedundantArgument:
|
|
280
|
+
Enabled: true
|
|
281
|
+
Style/RedundantArrayConstructor:
|
|
282
|
+
Enabled: true
|
|
283
|
+
Style/RedundantArrayFlatten:
|
|
284
|
+
Enabled: true
|
|
285
|
+
Style/RedundantConstantBase:
|
|
286
|
+
Enabled: true
|
|
287
|
+
Style/RedundantCurrentDirectoryInPath:
|
|
288
|
+
Enabled: true
|
|
289
|
+
Style/RedundantDoubleSplatHashBraces:
|
|
290
|
+
Enabled: true
|
|
291
|
+
Style/RedundantEach:
|
|
292
|
+
Enabled: true
|
|
293
|
+
Style/RedundantFilterChain:
|
|
294
|
+
Enabled: true
|
|
295
|
+
Style/RedundantFormat:
|
|
296
|
+
Enabled: true
|
|
297
|
+
Style/RedundantHeredocDelimiterQuotes:
|
|
298
|
+
Enabled: true
|
|
299
|
+
Style/RedundantInitialize:
|
|
300
|
+
Enabled: true
|
|
301
|
+
Style/RedundantInterpolationUnfreeze:
|
|
302
|
+
Enabled: true
|
|
303
|
+
Style/RedundantLineContinuation:
|
|
304
|
+
Enabled: true
|
|
305
|
+
Style/RedundantRegexpArgument:
|
|
306
|
+
Enabled: true
|
|
307
|
+
Style/RedundantRegexpConstructor:
|
|
308
|
+
Enabled: true
|
|
309
|
+
Style/RedundantSelfAssignmentBranch:
|
|
310
|
+
Enabled: true
|
|
311
|
+
Style/RedundantStringEscape:
|
|
312
|
+
Enabled: true
|
|
313
|
+
Style/ReturnNilInPredicateMethodDefinition:
|
|
314
|
+
Enabled: true
|
|
315
|
+
Style/SafeNavigationChainLength:
|
|
316
|
+
Enabled: true
|
|
317
|
+
Style/SelectByRegexp:
|
|
318
|
+
Enabled: true
|
|
319
|
+
Style/SendWithLiteralMethodName:
|
|
320
|
+
Enabled: true
|
|
321
|
+
Style/SingleLineDoEndBlock:
|
|
322
|
+
Enabled: true
|
|
323
|
+
Style/StringChars:
|
|
324
|
+
Enabled: true
|
|
325
|
+
Style/SuperArguments:
|
|
326
|
+
Enabled: true
|
|
327
|
+
Style/SuperWithArgsParentheses:
|
|
328
|
+
Enabled: true
|
|
329
|
+
Style/SwapValues:
|
|
330
|
+
Enabled: true
|
|
331
|
+
Style/YAMLFileRead:
|
|
332
|
+
Enabled: true
|
data/CHANGELOG.md
CHANGED
|
@@ -16,16 +16,39 @@ Deprecations: A deprecated feature will remain for at least one MINOR release af
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
## [1.0.1] - 2025-09-06
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **C++ Native Extension**: High-performance C++ implementation for Seed module
|
|
24
|
+
- Achieves 20-50x speedup over Ruby implementation
|
|
25
|
+
- Automatic fallback functionality ensures Ruby implementation works even without C++ compiler
|
|
26
|
+
- Maintains complete compatibility with existing API
|
|
27
|
+
- Added Rake tasks for building and testing C++ extension
|
|
28
|
+
- Added installation guide (INSTALLATION.md) and C++ extension documentation (README_CPP.md)
|
|
29
|
+
|
|
30
|
+
### Improved
|
|
31
|
+
|
|
32
|
+
- Enhanced error handling during gem installation
|
|
33
|
+
- Gem installation continues even if C++ extension compilation fails
|
|
34
|
+
- Safe fallback functionality ensures operation in any environment
|
|
35
|
+
- Significant performance improvements
|
|
36
|
+
- Reduced Seed calculation overhead from 60-65% to 13-16% of total execution time
|
|
37
|
+
|
|
38
|
+
### Technical Details
|
|
39
|
+
|
|
40
|
+
- C++ optimized implementation of FNV-1a 64-bit hash algorithm
|
|
41
|
+
- Cross-platform support compliant with C++17 standard
|
|
42
|
+
- Seamless integration using Ruby C API
|
|
43
|
+
- Guaranteed complete compatibility of deterministic output (identical results to Ruby implementation)
|
|
22
44
|
|
|
23
45
|
## [1.0.0] - 2025-08-14
|
|
24
46
|
|
|
25
|
-
###
|
|
47
|
+
### Added
|
|
26
48
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
49
|
+
- Initial release: Deterministic pseudo-random generator (numbers / hex / alphabetic / alphanumeric string generation)
|
|
50
|
+
- Support for arbitrary object seeds
|
|
29
51
|
|
|
30
|
-
[Unreleased]: https://github.com/aYosukeMakita/pseudo_random/compare/v1.0.
|
|
52
|
+
[Unreleased]: https://github.com/aYosukeMakita/pseudo_random/compare/v1.0.1...HEAD
|
|
53
|
+
[1.0.1]: https://github.com/aYosukeMakita/pseudo_random/compare/v1.0.0...v1.0.1
|
|
31
54
|
[1.0.0]: https://github.com/aYosukeMakita/pseudo_random/releases/tag/v1.0.0
|
data/INSTALLATION.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Gem Installation and Usage
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
### Standard Installation (with C++ Extension)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Prepare development environment (first time only)
|
|
9
|
+
# Ubuntu/Debian:
|
|
10
|
+
sudo apt-get install build-essential ruby-dev
|
|
11
|
+
|
|
12
|
+
# CentOS/RHEL:
|
|
13
|
+
sudo yum install gcc-c++ ruby-devel
|
|
14
|
+
|
|
15
|
+
# macOS:
|
|
16
|
+
xcode-select --install
|
|
17
|
+
|
|
18
|
+
# Install the gem
|
|
19
|
+
gem install pseudo_random
|
|
20
|
+
# or
|
|
21
|
+
bundle add pseudo_random
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Troubleshooting
|
|
25
|
+
|
|
26
|
+
If C++ extension compilation fails:
|
|
27
|
+
|
|
28
|
+
1. **When compilation errors occur**:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Display detailed error information
|
|
32
|
+
gem install pseudo_random --verbose
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Avoiding environment-dependent issues**:
|
|
36
|
+
```bash
|
|
37
|
+
# Install with Ruby implementation only (reduced performance)
|
|
38
|
+
PSEUDO_RANDOM_DISABLE_NATIVE=1 gem install pseudo_random
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
require 'pseudo_random'
|
|
45
|
+
|
|
46
|
+
# Basic usage (C++ extension is used automatically)
|
|
47
|
+
generator = PseudoRandom.new("my_seed")
|
|
48
|
+
puts generator.rand # => 0.123456789
|
|
49
|
+
puts generator.hex(8) # => "1a2b3c4d"
|
|
50
|
+
puts generator.alphabetic(10) # => "AbCdEfGhIj"
|
|
51
|
+
|
|
52
|
+
# Arrays and hashes can also be used as seeds
|
|
53
|
+
complex_seed = { user: "alice", timestamp: 1234567890 }
|
|
54
|
+
generator2 = PseudoRandom.new(complex_seed)
|
|
55
|
+
puts generator2.rand
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Performance
|
|
59
|
+
|
|
60
|
+
- **With C++ extension**: High speed (20-50x faster than Ruby implementation)
|
|
61
|
+
- **Ruby implementation only**: Standard speed
|
|
62
|
+
|
|
63
|
+
When the C++ extension is not available, it automatically falls back to the Ruby implementation,
|
|
64
|
+
ensuring it works in any environment.
|
|
65
|
+
|
|
66
|
+
## Compatibility
|
|
67
|
+
|
|
68
|
+
- Ruby 3.1.0 or higher
|
|
69
|
+
- C++17 compatible compiler (only when using C++ extension)
|
|
70
|
+
- Linux, macOS, Windows supported
|
data/README.md
CHANGED
|
@@ -28,21 +28,26 @@ This section documents the specs, boundary conditions, and determinism guarantee
|
|
|
28
28
|
|
|
29
29
|
### Per-method specifics
|
|
30
30
|
|
|
31
|
-
| Method | Character set
|
|
32
|
-
| ---------------------- | ----------------------------------------- |
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
31
|
+
| Method | Character set / Output | Notes |
|
|
32
|
+
| ---------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `rand([limit])` | Float [0.0, 1.0), Integer, Range, etc. | No argument: float in [0.0, 1.0). Integer: integer in [0, n). Float: float in [0.0, n). Range (integer or float): value in the given range. Supports both integer and floating-point ranges, e.g., `rand(1..10)` or `rand(1.0..2.0)`. Compatible with Ruby's Random. Deterministic. |
|
|
34
|
+
| `hex(length)` | `0-9a-f` (16 chars, lowercase) | Uses 32‑bit integers -> 8 hex chars at a time; remainder (1–7) from another 32‑bit block prefix. Uniform over all hex strings of the requested length. |
|
|
35
|
+
| `alphabetic(length)` | `A-Z` (26) + `a-z` (26) = 52 | 3-char chunks (since 52^3 < 2^32) plus remainder (1–2 chars). Uniform. |
|
|
36
|
+
| `alphanumeric(length)` | `A-Z` (26) + `a-z` (26) + `0-9` (10) = 62 | 5-char chunks (62^5 < 2^32) plus remainder (1–4 chars). Uniform. |
|
|
36
37
|
|
|
37
38
|
### Uniformity
|
|
38
39
|
|
|
39
40
|
Each chunk uses `Random#rand(base^k)` for an integer in `0...(base^k)` which is then expanded via repeated mod/div to `k` characters. This yields a uniform distribution over all `base^k` length-`k` strings. Remainder segments use the same approach. Thus (subject to the statistical quality of Ruby's underlying PRNG) each character position is unbiased and independent across chunks.
|
|
40
41
|
|
|
42
|
+
For `rand`, the output is uniformly distributed over the specified range or interval, matching the behavior of Ruby's built-in `Random`. The statistical quality depends on the underlying PRNG.
|
|
43
|
+
|
|
41
44
|
### Performance / limits
|
|
42
45
|
|
|
43
46
|
- Time complexity: O(length). Memory: O(length) for the resulting string.
|
|
44
47
|
- Very large values (millions of characters) imply higher allocation cost; consider generating in smaller segments if required.
|
|
45
48
|
|
|
49
|
+
For `rand`, each call is O(1) in time and memory. Extremely large integer ranges or high-precision floats may be limited by Ruby's internal implementation.
|
|
50
|
+
|
|
46
51
|
### Determinism Policy
|
|
47
52
|
|
|
48
53
|
As stated in Versioning: the mapping `(seed, call order) -> output sequence` is a public contract.
|
|
@@ -53,15 +58,21 @@ As stated in Versioning: the mapping `(seed, call order) -> output sequence` is
|
|
|
53
58
|
|
|
54
59
|
### Exceptions (current)
|
|
55
60
|
|
|
56
|
-
| Condition
|
|
57
|
-
|
|
|
58
|
-
| `length < 0`
|
|
59
|
-
| `length` not an Integer
|
|
61
|
+
| Condition | Exception |
|
|
62
|
+
| -------------------------- | --------------- |
|
|
63
|
+
| `length < 0` | `ArgumentError` |
|
|
64
|
+
| `length` not an Integer | `ArgumentError` |
|
|
65
|
+
| invalid argument to `rand` | `ArgumentError` |
|
|
60
66
|
|
|
61
67
|
### Examples
|
|
62
68
|
|
|
63
69
|
```ruby
|
|
64
70
|
g = PseudoRandom.new("seed")
|
|
71
|
+
g.rand # => float in [0.0, 1.0)
|
|
72
|
+
g.rand(10) # => integer in [0, 10)
|
|
73
|
+
g.rand(1..100) # => integer in [1, 100]
|
|
74
|
+
g.rand(10.0) # => float in [0.0, 10.0)
|
|
75
|
+
g.rand(1.0..2.0) # => float in [1.0, 2.0)
|
|
65
76
|
g.hex(10) # => 10 hex chars (0-9a-f)
|
|
66
77
|
g.alphabetic(12) # => 12 alphabetic chars (A-Za-z)
|
|
67
78
|
g.alphanumeric(8) # => 8 alphanumeric chars (A-Za-z0-9)
|
|
@@ -149,6 +160,8 @@ generator = PseudoRandom.new(42)
|
|
|
149
160
|
|
|
150
161
|
### Diverse seed types
|
|
151
162
|
|
|
163
|
+
You can pass any Ruby object as a seed to `PseudoRandom.new`. The object will be normalized into a deterministic hash value using a canonicalization algorithm (based on FNV-1a). This ensures that objects with the same content (even if of different types, e.g., a string `"42"` and an integer `42`) will produce different random sequences, while identical objects always yield the same sequence. Supported seed types include numbers, strings, arrays, hashes, symbols, Time objects, and any other Ruby object.
|
|
164
|
+
|
|
152
165
|
```ruby
|
|
153
166
|
# String seed
|
|
154
167
|
generator1 = PseudoRandom.new("hello")
|
data/README_CPP.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Performance Enhancement with C++ Extension
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The PseudoRandom library includes a C++ native extension for the Seed module. This extension can significantly improve the performance of seed calculations.
|
|
6
|
+
|
|
7
|
+
## Performance Improvements
|
|
8
|
+
|
|
9
|
+
Benchmark results show the following performance improvements with the C++ extension:
|
|
10
|
+
|
|
11
|
+
- **5-20x speedup** (depending on data complexity)
|
|
12
|
+
- **Reduced memory usage**
|
|
13
|
+
- **Optimized CPU cost**
|
|
14
|
+
|
|
15
|
+
## Installation and Usage
|
|
16
|
+
|
|
17
|
+
### 1. Development Environment Setup
|
|
18
|
+
|
|
19
|
+
#### Ubuntu/Debian:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
sudo apt-get install build-essential ruby-dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### CentOS/RHEL:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
sudo yum install gcc-c++ ruby-devel
|
|
29
|
+
# or
|
|
30
|
+
sudo dnf install gcc-c++ ruby-devel
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
#### macOS:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Xcode Command Line Tools required
|
|
37
|
+
xcode-select --install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Building the C++ Extension
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Automatic build (recommended)
|
|
44
|
+
rake compile
|
|
45
|
+
|
|
46
|
+
# or manual build
|
|
47
|
+
cd ext/pseudo_random_native
|
|
48
|
+
ruby extconf.rb
|
|
49
|
+
make
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Usage
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
require 'pseudo_random'
|
|
56
|
+
|
|
57
|
+
# C++ extension is automatically used when available
|
|
58
|
+
generator = PseudoRandom.new("my_seed")
|
|
59
|
+
puts generator.rand
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Fallback
|
|
63
|
+
|
|
64
|
+
In environments where the C++ extension cannot be built, it automatically falls back to the Ruby implementation:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# Ruby implementation is automatically used when C++ extension is unavailable
|
|
68
|
+
generator = PseudoRandom.new("my_seed")
|
|
69
|
+
puts generator.rand
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Technical Details
|
|
73
|
+
|
|
74
|
+
### C++ Implementation Features
|
|
75
|
+
|
|
76
|
+
1. **FNV-1a 64-bit hash**: Same algorithm as Ruby implementation
|
|
77
|
+
2. **Optimized memory management**: Vector-based byte arrays
|
|
78
|
+
3. **Type safety**: Leveraging C++ type system
|
|
79
|
+
4. **Error handling**: Integration with Ruby exceptions
|
|
80
|
+
|
|
81
|
+
### Compatibility
|
|
82
|
+
|
|
83
|
+
- **Full backward compatibility**: Guarantees same results as Ruby implementation
|
|
84
|
+
- **Automatic switching**: Auto-detection of extension availability
|
|
85
|
+
- **Debug support**: Ruby implementation also available in parallel
|
|
86
|
+
|
|
87
|
+
### Supported Platforms
|
|
88
|
+
|
|
89
|
+
- Linux (x86_64, ARM64)
|
|
90
|
+
- macOS (x86_64, ARM64/M1)
|
|
91
|
+
- Windows (MinGW, Visual Studio)
|
|
92
|
+
|
|
93
|
+
## Troubleshooting
|
|
94
|
+
|
|
95
|
+
### Build Errors
|
|
96
|
+
|
|
97
|
+
1. **Compiler not found**:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Ubuntu/Debian
|
|
101
|
+
sudo apt-get install build-essential
|
|
102
|
+
|
|
103
|
+
# CentOS/RHEL
|
|
104
|
+
sudo yum install gcc-c++
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. **Ruby development headers not found**:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Ubuntu/Debian
|
|
111
|
+
sudo apt-get install ruby-dev
|
|
112
|
+
|
|
113
|
+
# CentOS/RHEL
|
|
114
|
+
sudo yum install ruby-devel
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
3. **Manual cleanup**:
|
|
118
|
+
```bash
|
|
119
|
+
rake clean
|
|
120
|
+
rake compile
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Runtime Errors
|
|
124
|
+
|
|
125
|
+
If problems occur with the C++ extension, you can force the use of pure Ruby implementation:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
# Use Ruby implementation directly
|
|
129
|
+
result = PseudoRandom::SeedRuby.to_seed_int(my_data)
|
|
130
|
+
|
|
131
|
+
# or control with environment variable
|
|
132
|
+
ENV['PSEUDO_RANDOM_DISABLE_NATIVE'] = '1'
|
|
133
|
+
require 'pseudo_random'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Developer Information
|
|
137
|
+
|
|
138
|
+
### Source File Structure
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
ext/pseudo_random_native/
|
|
142
|
+
├── pseudo_random_native.cpp # Main C++ implementation
|
|
143
|
+
├── extconf.rb # Build configuration
|
|
144
|
+
└── Makefile # Generated Makefile
|
|
145
|
+
|
|
146
|
+
lib/
|
|
147
|
+
├── pseudo_random.rb # Original Ruby implementation
|
|
148
|
+
└── pseudo_random_native.rb # C++ extension wrapper
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Debug Build
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Build with debug information
|
|
155
|
+
CPPFLAGS="-g -O0" rake compile
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Profiling
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
require 'ruby-prof'
|
|
162
|
+
|
|
163
|
+
RubyProf.start
|
|
164
|
+
1000.times { PseudoRandom::Seed.to_seed_int("test") }
|
|
165
|
+
result = RubyProf.stop
|
|
166
|
+
|
|
167
|
+
printer = RubyProf::FlatPrinter.new(result)
|
|
168
|
+
printer.print(STDOUT)
|
|
169
|
+
```
|
data/Rakefile
CHANGED
|
@@ -9,4 +9,33 @@ require 'rubocop/rake_task'
|
|
|
9
9
|
|
|
10
10
|
RuboCop::RakeTask.new
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# C++ extension build task
|
|
13
|
+
begin
|
|
14
|
+
require 'rake/extensiontask'
|
|
15
|
+
|
|
16
|
+
Rake::ExtensionTask.new('pseudo_random_native') do |ext|
|
|
17
|
+
ext.lib_dir = 'lib'
|
|
18
|
+
ext.source_pattern = '*.{c,cpp}'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
task test: :compile
|
|
22
|
+
task default: %i[compile test rubocop]
|
|
23
|
+
rescue LoadError
|
|
24
|
+
# Use normal tasks only when rake-compiler-dock is not available
|
|
25
|
+
task default: %i[test rubocop]
|
|
26
|
+
|
|
27
|
+
# Manual build task
|
|
28
|
+
task :compile do
|
|
29
|
+
Dir.chdir('ext/pseudo_random_native') do
|
|
30
|
+
sh 'ruby extconf.rb'
|
|
31
|
+
sh 'make'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
task :clean do
|
|
36
|
+
Dir.chdir('ext/pseudo_random_native') do
|
|
37
|
+
sh 'make clean' if File.exist?('Makefile')
|
|
38
|
+
FileUtils.rm_f(['Makefile', 'mkmf.log'])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'mkmf'
|
|
2
|
+
|
|
3
|
+
# Use C++ compiler
|
|
4
|
+
$CPPFLAGS += ' -std=c++17'
|
|
5
|
+
$CXXFLAGS += ' -std=c++17'
|
|
6
|
+
|
|
7
|
+
# Extension name
|
|
8
|
+
extension_name = 'pseudo_random_native'
|
|
9
|
+
|
|
10
|
+
# Source file specification
|
|
11
|
+
$srcs = ['pseudo_random_native.cpp']
|
|
12
|
+
|
|
13
|
+
# Add debug information (for development)
|
|
14
|
+
# $CPPFLAGS += " -g -O0"
|
|
15
|
+
|
|
16
|
+
# Release optimization
|
|
17
|
+
$CPPFLAGS += ' -O3 -DNDEBUG'
|
|
18
|
+
|
|
19
|
+
# Compiler warning level
|
|
20
|
+
$CPPFLAGS += ' -Wall -Wextra'
|
|
21
|
+
|
|
22
|
+
# Link C++ standard library
|
|
23
|
+
$LIBS += ' -lstdc++'
|
|
24
|
+
|
|
25
|
+
# Create Makefile
|
|
26
|
+
create_makefile(extension_name)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <ruby/encoding.h>
|
|
3
|
+
#include <cstdint>
|
|
4
|
+
#include <cstring>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <vector>
|
|
7
|
+
#include <sstream>
|
|
8
|
+
#include <algorithm>
|
|
9
|
+
|
|
10
|
+
// FNV-1a 64-bit constants
|
|
11
|
+
constexpr uint64_t FNV_OFFSET = 0xcbf29ce484222325ULL;
|
|
12
|
+
constexpr uint64_t FNV_PRIME = 0x100000001b3ULL;
|
|
13
|
+
constexpr uint64_t MASK64 = 0xffffffffffffffffULL;
|
|
14
|
+
|
|
15
|
+
class SeedCalculator {
|
|
16
|
+
private:
|
|
17
|
+
std::vector<uint8_t> bytes;
|
|
18
|
+
|
|
19
|
+
// ZigZag encode signed -> unsigned integer
|
|
20
|
+
uint64_t zigzag(int64_t num) const {
|
|
21
|
+
return num >= 0 ? (static_cast<uint64_t>(num) << 1) :
|
|
22
|
+
((static_cast<uint64_t>(-num) << 1) - 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Varint (7-bit continuation) encoding
|
|
26
|
+
void encode_varint(uint64_t num) {
|
|
27
|
+
do {
|
|
28
|
+
uint8_t byte = num & 0x7f;
|
|
29
|
+
num >>= 7;
|
|
30
|
+
if (num == 0) {
|
|
31
|
+
bytes.push_back(byte);
|
|
32
|
+
break;
|
|
33
|
+
} else {
|
|
34
|
+
bytes.push_back(byte | 0x80);
|
|
35
|
+
}
|
|
36
|
+
} while (true);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Canonical serialization of Ruby objects
|
|
40
|
+
void canonical_serialize(VALUE obj) {
|
|
41
|
+
switch (TYPE(obj)) {
|
|
42
|
+
case T_NIL:
|
|
43
|
+
bytes.push_back('n');
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case T_TRUE:
|
|
47
|
+
bytes.push_back('t');
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case T_FALSE:
|
|
51
|
+
bytes.push_back('f');
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case T_FIXNUM:
|
|
55
|
+
case T_BIGNUM:
|
|
56
|
+
bytes.push_back('i');
|
|
57
|
+
encode_varint(zigzag(NUM2LL(obj)));
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case T_FLOAT:
|
|
61
|
+
bytes.push_back('d');
|
|
62
|
+
{
|
|
63
|
+
double d = RFLOAT_VALUE(obj);
|
|
64
|
+
uint64_t bits;
|
|
65
|
+
std::memcpy(&bits, &d, sizeof(double));
|
|
66
|
+
// Convert to big-endian (network byte order)
|
|
67
|
+
for (int i = 7; i >= 0; i--) {
|
|
68
|
+
bytes.push_back((bits >> (i * 8)) & 0xff);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
|
|
73
|
+
case T_STRING:
|
|
74
|
+
bytes.push_back('s');
|
|
75
|
+
{
|
|
76
|
+
// Explicitly encode the string to UTF-8 to match Ruby implementation
|
|
77
|
+
VALUE utf8_str = rb_str_export_to_enc(obj, rb_utf8_encoding());
|
|
78
|
+
const char* str_ptr = RSTRING_PTR(utf8_str);
|
|
79
|
+
long str_len = RSTRING_LEN(utf8_str);
|
|
80
|
+
encode_varint(str_len);
|
|
81
|
+
for (long i = 0; i < str_len; i++) {
|
|
82
|
+
bytes.push_back(static_cast<uint8_t>(str_ptr[i]));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
case T_SYMBOL:
|
|
88
|
+
bytes.push_back('y');
|
|
89
|
+
{
|
|
90
|
+
VALUE str = rb_sym2str(obj);
|
|
91
|
+
VALUE utf8_str = rb_str_export_to_enc(str, rb_utf8_encoding());
|
|
92
|
+
const char* str_ptr = RSTRING_PTR(utf8_str);
|
|
93
|
+
long str_len = RSTRING_LEN(utf8_str);
|
|
94
|
+
encode_varint(str_len);
|
|
95
|
+
for (long i = 0; i < str_len; i++) {
|
|
96
|
+
bytes.push_back(static_cast<uint8_t>(str_ptr[i]));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case T_ARRAY:
|
|
102
|
+
bytes.push_back('a');
|
|
103
|
+
{
|
|
104
|
+
long len = RARRAY_LEN(obj);
|
|
105
|
+
encode_varint(len);
|
|
106
|
+
for (long i = 0; i < len; i++) {
|
|
107
|
+
canonical_serialize(RARRAY_AREF(obj, i));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case T_HASH:
|
|
113
|
+
bytes.push_back('h');
|
|
114
|
+
{
|
|
115
|
+
VALUE keys = rb_funcall(obj, rb_intern("keys"), 0);
|
|
116
|
+
long len = RARRAY_LEN(keys);
|
|
117
|
+
encode_varint(len);
|
|
118
|
+
|
|
119
|
+
// Sort keys by string representation for canonical order
|
|
120
|
+
std::vector<std::pair<std::string, VALUE>> sorted_keys;
|
|
121
|
+
for (long i = 0; i < len; i++) {
|
|
122
|
+
VALUE key = RARRAY_AREF(keys, i);
|
|
123
|
+
VALUE key_str = rb_funcall(key, rb_intern("to_s"), 0);
|
|
124
|
+
std::string key_string(RSTRING_PTR(key_str), RSTRING_LEN(key_str));
|
|
125
|
+
sorted_keys.push_back({key_string, key});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
std::sort(sorted_keys.begin(), sorted_keys.end(),
|
|
129
|
+
[](const auto& a, const auto& b) { return a.first < b.first; });
|
|
130
|
+
|
|
131
|
+
for (const auto& key_pair : sorted_keys) {
|
|
132
|
+
canonical_serialize(rb_str_new(key_pair.first.c_str(), key_pair.first.length()));
|
|
133
|
+
VALUE original_key = key_pair.second;
|
|
134
|
+
VALUE value = rb_hash_aref(obj, original_key);
|
|
135
|
+
canonical_serialize(value);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
|
|
140
|
+
case T_DATA:
|
|
141
|
+
// Check if it's a Time object
|
|
142
|
+
if (rb_obj_is_kind_of(obj, rb_cTime)) {
|
|
143
|
+
bytes.push_back('T');
|
|
144
|
+
VALUE to_i = rb_funcall(obj, rb_intern("to_i"), 0);
|
|
145
|
+
VALUE nsec = rb_funcall(obj, rb_intern("nsec"), 0);
|
|
146
|
+
encode_varint(NUM2ULL(to_i));
|
|
147
|
+
encode_varint(NUM2ULL(nsec));
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
// fall through to default case to handle non-Time T_DATA objects as generic objects
|
|
151
|
+
[[fallthrough]];
|
|
152
|
+
default:
|
|
153
|
+
// Fallback: class name + ':' + to_s
|
|
154
|
+
bytes.push_back('o');
|
|
155
|
+
{
|
|
156
|
+
VALUE klass = rb_obj_class(obj);
|
|
157
|
+
VALUE class_name = rb_funcall(klass, rb_intern("name"), 0);
|
|
158
|
+
VALUE obj_str = rb_funcall(obj, rb_intern("to_s"), 0);
|
|
159
|
+
|
|
160
|
+
std::string rep = std::string(RSTRING_PTR(class_name), RSTRING_LEN(class_name)) +
|
|
161
|
+
":" +
|
|
162
|
+
std::string(RSTRING_PTR(obj_str), RSTRING_LEN(obj_str));
|
|
163
|
+
|
|
164
|
+
// Encode the representation string to UTF-8 to match Ruby implementation
|
|
165
|
+
VALUE rep_str = rb_str_new(rep.c_str(), rep.length());
|
|
166
|
+
VALUE utf8_rep = rb_str_export_to_enc(rep_str, rb_utf8_encoding());
|
|
167
|
+
const char* utf8_ptr = RSTRING_PTR(utf8_rep);
|
|
168
|
+
long utf8_len = RSTRING_LEN(utf8_rep);
|
|
169
|
+
|
|
170
|
+
encode_varint(utf8_len);
|
|
171
|
+
for (long i = 0; i < utf8_len; i++) {
|
|
172
|
+
bytes.push_back(static_cast<uint8_t>(utf8_ptr[i]));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public:
|
|
180
|
+
// Convert arbitrary Ruby object to a deterministic 31-bit Integer
|
|
181
|
+
uint32_t to_seed_int(VALUE obj) {
|
|
182
|
+
bytes.clear();
|
|
183
|
+
canonical_serialize(obj);
|
|
184
|
+
|
|
185
|
+
// FNV-1a hash calculation
|
|
186
|
+
uint64_t h = FNV_OFFSET;
|
|
187
|
+
for (uint8_t byte : bytes) {
|
|
188
|
+
h ^= byte;
|
|
189
|
+
h = (h * FNV_PRIME) & MASK64;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
uint32_t s = static_cast<uint32_t>(h ^ (h >> 32));
|
|
193
|
+
return s & 0x7fffffff; // 31-bit mask
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Ruby C API wrapper functions
|
|
198
|
+
extern "C" {
|
|
199
|
+
static VALUE seed_to_seed_int(VALUE /* self */, VALUE obj) {
|
|
200
|
+
try {
|
|
201
|
+
SeedCalculator calculator;
|
|
202
|
+
uint32_t result = calculator.to_seed_int(obj);
|
|
203
|
+
return UINT2NUM(result);
|
|
204
|
+
} catch (const std::exception& e) {
|
|
205
|
+
rb_raise(rb_eRuntimeError, "Error in seed calculation: %s", e.what());
|
|
206
|
+
return Qnil;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
void Init_pseudo_random_native() {
|
|
211
|
+
VALUE mPseudoRandom = rb_define_module("PseudoRandom");
|
|
212
|
+
VALUE mSeedNative = rb_define_module_under(mPseudoRandom, "SeedNative");
|
|
213
|
+
|
|
214
|
+
rb_define_module_function(mSeedNative, "to_seed_int", RUBY_METHOD_FUNC(seed_to_seed_int), 1);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PseudoRandom
|
|
4
|
+
# Internal seed canonicalization & hashing (FNV-1a 64-bit)
|
|
5
|
+
# Uses C++ implementation if available, otherwise pure Ruby
|
|
6
|
+
module Seed
|
|
7
|
+
FNV_OFFSET = 0xcbf29ce484222325
|
|
8
|
+
FNV_PRIME = 0x100000001b3
|
|
9
|
+
MASK64 = 0xffff_ffff_ffff_ffff
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# Public: Convert arbitrary Ruby object to a deterministic 31-bit Integer for Random.new
|
|
14
|
+
def to_seed_int(obj)
|
|
15
|
+
if PseudoRandom.native_extension_loaded?
|
|
16
|
+
# Use C++ implementation for better performance
|
|
17
|
+
PseudoRandom::SeedNative.to_seed_int(obj)
|
|
18
|
+
else
|
|
19
|
+
# Fall back to Ruby implementation
|
|
20
|
+
h = FNV_OFFSET
|
|
21
|
+
canonical_each_byte(obj) do |byte|
|
|
22
|
+
h ^= byte
|
|
23
|
+
h = (h * FNV_PRIME) & MASK64
|
|
24
|
+
end
|
|
25
|
+
s = h ^ (h >> 32)
|
|
26
|
+
s & 0x7fff_ffff
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Depth-first canonical serialization streamed as bytes
|
|
31
|
+
def canonical_each_byte(obj, ...)
|
|
32
|
+
case obj
|
|
33
|
+
when NilClass
|
|
34
|
+
yield 'n'.ord
|
|
35
|
+
when TrueClass
|
|
36
|
+
yield 't'.ord
|
|
37
|
+
when FalseClass
|
|
38
|
+
yield 'f'.ord
|
|
39
|
+
when Integer
|
|
40
|
+
yield 'i'.ord
|
|
41
|
+
encode_varint(zigzag(obj), ...)
|
|
42
|
+
when Float
|
|
43
|
+
yield 'd'.ord
|
|
44
|
+
[obj].pack('G').each_byte(...)
|
|
45
|
+
when String
|
|
46
|
+
str = obj.encode(Encoding::UTF_8)
|
|
47
|
+
yield 's'.ord
|
|
48
|
+
encode_varint(str.bytesize, ...)
|
|
49
|
+
str.each_byte(...)
|
|
50
|
+
when Symbol
|
|
51
|
+
str = obj.to_s.encode(Encoding::UTF_8)
|
|
52
|
+
yield 'y'.ord
|
|
53
|
+
encode_varint(str.bytesize, ...)
|
|
54
|
+
str.each_byte(...)
|
|
55
|
+
when Array
|
|
56
|
+
yield 'a'.ord
|
|
57
|
+
encode_varint(obj.length, ...)
|
|
58
|
+
obj.each { |e| canonical_each_byte(e, ...) }
|
|
59
|
+
when Hash
|
|
60
|
+
yield 'h'.ord
|
|
61
|
+
encode_varint(obj.length, ...)
|
|
62
|
+
# Canonical order by key string representation to avoid insertion order dependence
|
|
63
|
+
obj.keys.map(&:to_s).sort.each do |ks|
|
|
64
|
+
canonical_each_byte(ks, ...)
|
|
65
|
+
original_key = if obj.key?(ks)
|
|
66
|
+
ks
|
|
67
|
+
elsif obj.key?(ks.to_sym)
|
|
68
|
+
ks.to_sym
|
|
69
|
+
else
|
|
70
|
+
# Fallback (should not usually happen)
|
|
71
|
+
obj.keys.find { |k| k.to_s == ks }
|
|
72
|
+
end
|
|
73
|
+
canonical_each_byte(obj[original_key], ...)
|
|
74
|
+
end
|
|
75
|
+
when Time
|
|
76
|
+
yield 'T'.ord
|
|
77
|
+
encode_varint(obj.to_i, ...)
|
|
78
|
+
encode_varint(obj.nsec, ...)
|
|
79
|
+
else
|
|
80
|
+
# Fallback: class name + ':' + to_s (could cause collisions if to_s not stable)
|
|
81
|
+
rep = "#{obj.class.name}:#{obj}"
|
|
82
|
+
rep = rep.encode(Encoding::UTF_8)
|
|
83
|
+
yield 'o'.ord
|
|
84
|
+
encode_varint(rep.bytesize, ...)
|
|
85
|
+
rep.each_byte(...)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# ZigZag encode signed -> unsigned integer
|
|
90
|
+
def zigzag(num)
|
|
91
|
+
num >= 0 ? (num << 1) : ((-num << 1) - 1)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Varint (7-bit continuation) encoding
|
|
95
|
+
def encode_varint(num)
|
|
96
|
+
raise ArgumentError, 'negative varint' if num < 0
|
|
97
|
+
|
|
98
|
+
loop do
|
|
99
|
+
byte = num & 0x7f
|
|
100
|
+
num >>= 7
|
|
101
|
+
if num.zero?
|
|
102
|
+
yield byte
|
|
103
|
+
break
|
|
104
|
+
else
|
|
105
|
+
yield(byte | 0x80)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/pseudo_random.rb
CHANGED
|
@@ -1,110 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'pseudo_random/version'
|
|
4
|
+
require_relative 'pseudo_random/seed'
|
|
5
|
+
|
|
6
|
+
# Try to load native C++ extension first, fall back to Ruby implementation
|
|
7
|
+
begin
|
|
8
|
+
require 'pseudo_random_native'
|
|
9
|
+
NATIVE_EXTENSION_LOADED = true
|
|
10
|
+
rescue LoadError
|
|
11
|
+
NATIVE_EXTENSION_LOADED = false
|
|
12
|
+
end
|
|
4
13
|
|
|
5
14
|
module PseudoRandom
|
|
6
15
|
class Error < StandardError; end
|
|
7
16
|
|
|
8
|
-
# Internal seed canonicalization & hashing (FNV-1a 64-bit, pure Ruby)
|
|
9
|
-
module Seed
|
|
10
|
-
FNV_OFFSET = 0xcbf29ce484222325
|
|
11
|
-
FNV_PRIME = 0x100000001b3
|
|
12
|
-
MASK64 = 0xffff_ffff_ffff_ffff
|
|
13
|
-
|
|
14
|
-
module_function
|
|
15
|
-
|
|
16
|
-
# Public: Convert arbitrary Ruby object to a deterministic 31-bit Integer for Random.new
|
|
17
|
-
def to_seed_int(obj)
|
|
18
|
-
h = FNV_OFFSET
|
|
19
|
-
canonical_each_byte(obj) do |byte|
|
|
20
|
-
h ^= byte
|
|
21
|
-
h = (h * FNV_PRIME) & MASK64
|
|
22
|
-
end
|
|
23
|
-
s = h ^ (h >> 32)
|
|
24
|
-
s & 0x7fff_ffff
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Depth-first canonical serialization streamed as bytes
|
|
28
|
-
def canonical_each_byte(obj, &blk)
|
|
29
|
-
case obj
|
|
30
|
-
when NilClass
|
|
31
|
-
yield 'n'.ord
|
|
32
|
-
when TrueClass
|
|
33
|
-
yield 't'.ord
|
|
34
|
-
when FalseClass
|
|
35
|
-
yield 'f'.ord
|
|
36
|
-
when Integer
|
|
37
|
-
yield 'i'.ord
|
|
38
|
-
encode_varint(zigzag(obj), &blk)
|
|
39
|
-
when Float
|
|
40
|
-
yield 'd'.ord
|
|
41
|
-
[obj].pack('G').each_byte(&blk) # big-endian IEEE 754
|
|
42
|
-
when String
|
|
43
|
-
str = obj.encode(Encoding::UTF_8)
|
|
44
|
-
yield 's'.ord
|
|
45
|
-
encode_varint(str.bytesize, &blk)
|
|
46
|
-
str.each_byte(&blk)
|
|
47
|
-
when Symbol
|
|
48
|
-
str = obj.to_s.encode(Encoding::UTF_8)
|
|
49
|
-
yield 'y'.ord
|
|
50
|
-
encode_varint(str.bytesize, &blk)
|
|
51
|
-
str.each_byte(&blk)
|
|
52
|
-
when Array
|
|
53
|
-
yield 'a'.ord
|
|
54
|
-
encode_varint(obj.length, &blk)
|
|
55
|
-
obj.each { |e| canonical_each_byte(e, &blk) }
|
|
56
|
-
when Hash
|
|
57
|
-
yield 'h'.ord
|
|
58
|
-
encode_varint(obj.length, &blk)
|
|
59
|
-
# Canonical order by key string representation to avoid insertion order dependence
|
|
60
|
-
obj.keys.map(&:to_s).sort.each do |ks|
|
|
61
|
-
canonical_each_byte(ks, &blk)
|
|
62
|
-
original_key = if obj.key?(ks)
|
|
63
|
-
ks
|
|
64
|
-
elsif obj.key?(ks.to_sym)
|
|
65
|
-
ks.to_sym
|
|
66
|
-
else
|
|
67
|
-
# Fallback (should not usually happen)
|
|
68
|
-
obj.keys.find { |k| k.to_s == ks }
|
|
69
|
-
end
|
|
70
|
-
canonical_each_byte(obj[original_key], &blk)
|
|
71
|
-
end
|
|
72
|
-
when Time
|
|
73
|
-
yield 'T'.ord
|
|
74
|
-
encode_varint(obj.to_i, &blk)
|
|
75
|
-
encode_varint(obj.nsec, &blk)
|
|
76
|
-
else
|
|
77
|
-
# Fallback: class name + ':' + to_s (could cause collisions if to_s not stable)
|
|
78
|
-
rep = "#{obj.class.name}:#{obj}"
|
|
79
|
-
rep = rep.encode(Encoding::UTF_8)
|
|
80
|
-
yield 'o'.ord
|
|
81
|
-
encode_varint(rep.bytesize, &blk)
|
|
82
|
-
rep.each_byte(&blk)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# ZigZag encode signed -> unsigned integer
|
|
87
|
-
def zigzag(num)
|
|
88
|
-
num >= 0 ? (num << 1) : ((-num << 1) - 1)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Varint (7-bit continuation) encoding
|
|
92
|
-
def encode_varint(num)
|
|
93
|
-
raise ArgumentError, 'negative varint' if num < 0
|
|
94
|
-
|
|
95
|
-
loop do
|
|
96
|
-
byte = num & 0x7f
|
|
97
|
-
num >>= 7
|
|
98
|
-
if num.zero?
|
|
99
|
-
yield byte
|
|
100
|
-
break
|
|
101
|
-
else
|
|
102
|
-
yield(byte | 0x80)
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
17
|
class Generator
|
|
109
18
|
# Character set for alphabetic generation: A-Z (26) + a-z (26) = 52 characters
|
|
110
19
|
ALPHABETIC_CHARS = ('A'..'Z').to_a + ('a'..'z').to_a
|
|
@@ -265,4 +174,9 @@ module PseudoRandom
|
|
|
265
174
|
generator = new(seed)
|
|
266
175
|
generator.rand
|
|
267
176
|
end
|
|
177
|
+
|
|
178
|
+
# Returns true if native C++ extension is loaded and available
|
|
179
|
+
def self.native_extension_loaded?
|
|
180
|
+
NATIVE_EXTENSION_LOADED
|
|
181
|
+
end
|
|
268
182
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pseudo_random
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- aYosukeMakita
|
|
@@ -15,15 +15,21 @@ description: Generates reproducible (deterministic) pseudo-random numbers and st
|
|
|
15
15
|
email:
|
|
16
16
|
- yosuke.makita@access-company.com
|
|
17
17
|
executables: []
|
|
18
|
-
extensions:
|
|
18
|
+
extensions:
|
|
19
|
+
- ext/pseudo_random_native/extconf.rb
|
|
19
20
|
extra_rdoc_files: []
|
|
20
21
|
files:
|
|
21
22
|
- ".rubocop.yml"
|
|
22
23
|
- CHANGELOG.md
|
|
24
|
+
- INSTALLATION.md
|
|
23
25
|
- LICENSE.txt
|
|
24
26
|
- README.md
|
|
27
|
+
- README_CPP.md
|
|
25
28
|
- Rakefile
|
|
29
|
+
- ext/pseudo_random_native/extconf.rb
|
|
30
|
+
- ext/pseudo_random_native/pseudo_random_native.cpp
|
|
26
31
|
- lib/pseudo_random.rb
|
|
32
|
+
- lib/pseudo_random/seed.rb
|
|
27
33
|
- lib/pseudo_random/version.rb
|
|
28
34
|
- sig/pseudo_random.rbs
|
|
29
35
|
homepage: https://github.com/aYosukeMakita/pseudo_random
|