From 5a5bc9ee37801a0f690254ddda82f5eb4ac7dd6d Mon Sep 17 00:00:00 2001 From: Connor Shea <2977353+connorshea@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:47:16 -0600 Subject: [PATCH] Optimize Number generators with arithmetic instead of string building - number: generate the whole value with a single ranged rand instead of concatenating per-digit strings and re-parsing with to_i. - leading_zero_number: one rand plus rjust instead of one digit call per position; also handle digits < 2 explicitly. - decimal: build the right-hand side arithmetically (rand * 10 + non_zero_digit) instead of joining a per-digit array. - hexadecimal/binary: append with << into a pre-sized String instead of += which reallocates the string on every iteration. Benchmark (Ruby 3.4.9, arm64-darwin25, benchmark-ips): require 'benchmark/ips' require 'faker' Benchmark.ips do |x| x.config(warmup: 1, time: 2) x.report('number(digits: 10)') { Faker::Number.number(digits: 10) } x.report('leading_zero_number(digits: 10)') { Faker::Number.leading_zero_number(digits: 10) } x.report('decimal(5, 5)') { Faker::Number.decimal(l_digits: 5, r_digits: 5) } x.report('hexadecimal(digits: 32)') { Faker::Number.hexadecimal(digits: 32) } x.report('binary(digits: 32)') { Faker::Number.binary(digits: 32) } end Results: main: number(digits: 10) 516.487k (+/- 1.8%) i/s leading_zero_number(digits: 10) 532.109k (+/- 4.2%) i/s decimal(5, 5) 439.388k (+/- 2.0%) i/s hexadecimal(digits: 32) 177.646k (+/- 0.9%) i/s binary(digits: 32) 170.770k (+/- 0.8%) i/s this commit: number(digits: 10) 4.823M (+/- 3.4%) i/s (~9.3x) leading_zero_number(digits: 10) 4.551M (+/- 1.4%) i/s (~8.6x) decimal(5, 5) 1.663M (+/- 1.4%) i/s (~3.8x) hexadecimal(digits: 32) 230.063k (+/- 1.2%) i/s (~1.3x) binary(digits: 32) 220.621k (+/- 1.1%) i/s (~1.3x) --- lib/faker/default/number.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/faker/default/number.rb b/lib/faker/default/number.rb index d6e3423f55..bfced91905 100644 --- a/lib/faker/default/number.rb +++ b/lib/faker/default/number.rb @@ -18,7 +18,7 @@ def number(digits: 10) return rand(0..9).round if digits == 1 # Ensure the first digit is not zero - ([non_zero_digit] + generate(digits - 1)).join.to_i + rand((10**(digits - 1))..(10**digits - 1)) end ## @@ -32,7 +32,9 @@ def number(digits: 10) # # @faker.version 1.0.0 def leading_zero_number(digits: 10) - "0#{(2..digits).collect { digit }.join}" + return '0' if digits < 2 + + "0#{rand(10**(digits - 1)).to_s.rjust(digits - 1, '0')}" end ## @@ -71,7 +73,7 @@ def decimal(l_digits: 5, r_digits: 2) # Ensure the last digit is not zero # so it does not get truncated on converting to float - r_d = generate(r_digits - 1).join + non_zero_digit.to_s + r_d = ((rand(10**(r_digits - 1)) * 10) + non_zero_digit).to_s.rjust(r_digits, '0') "#{l_d}.#{r_d}".to_f end @@ -113,8 +115,8 @@ def digit # # @faker.version 1.0.0 def hexadecimal(digits: 6) - hex = '' - digits.times { hex += rand(16).to_s(16) } + hex = ::String.new('', capacity: digits) + digits.times { hex << rand(16).to_s(16) } hex end @@ -128,8 +130,8 @@ def hexadecimal(digits: 6) # # @faker.version next def binary(digits: 4) - bin = '' - digits.times { bin += rand(2).to_s(2) } + bin = ::String.new('', capacity: digits) + digits.times { bin << rand(2).to_s(2) } bin end