Skip to content

Instantly share code, notes, and snippets.

@RX14
Created September 9, 2017 16:33
Show Gist options
  • Save RX14/9e6d3733b7c074fded9a8735718b7d7c to your computer and use it in GitHub Desktop.
Save RX14/9e6d3733b7c074fded9a8735718b7d7c to your computer and use it in GitHub Desktop.
require "big_decimal"
require "benchmark"
def new(str : String) : BigDecimal
reader = Char::Reader.new(str)
value = BigInt.new(0)
scale = 0_u64
if reader.current_char == '-'
negative = true
reader.next_char
end
has_decimal = false
until (char = reader.current_char) == '\0'
if char == '.'
raise ArgumentError.new("Multiple decimal places") if has_decimal
has_decimal = true
scale = 0_u64
reader.next_char
next
end
int = char.to_i?
raise ArgumentError.new unless int
value *= 10
value += int
scale += 1
reader.next_char
end
value = -value if negative
scale = 0_u64 unless has_decimal
BigDecimal.new(value, scale)
end
def old(str : String) : BigDecimal
# disallow every non-valid string
if str !~ /^-?[0-9]+(\.[0-9]+)?$/
raise InvalidBigDecimalException.new(str, "")
end
if str.includes?('.')
v1, v2 = str.split('.')
value = "#{v1}#{v2}".to_big_i
scale = v2.size.to_u64
else
value = str.to_big_i
scale = 0_u64
end
BigDecimal.new(value, scale)
end
def new2(str : String) : BigDecimal
raise InvalidBigDecimalException.new(str, "Zero size") if str.bytesize == 0
# Check str's validity and find index of .
decimal_index = nil
negative = false
str.each_char_with_index do |char, index|
case char
when '-'
if index != 0
raise InvalidBigDecimalException.new(str, "Unexpected '-' character")
end
negative = true
when '.'
if decimal_index || index == 0 || (negative && index == 1)
raise InvalidBigDecimalException.new(str, "Unexpected '.' character")
end
decimal_index = index
when '0'..'9'
# Pass
else
raise InvalidBigDecimalException.new(str, "Unexpected #{char.inspect} character")
end
end
if decimal_index
value_str = String.build do |builder|
# We know this is ASCII, so we can slice by index
builder.write(str.to_slice[0, decimal_index])
builder.write(str.to_slice[decimal_index + 1, str.bytesize - decimal_index - 1])
end
value = value_str.to_big_i
scale = (str.bytesize - decimal_index - 1).to_u64
else
value = str.to_big_i
scale = 0_u64
end
BigDecimal.new(value, scale)
end
test_strings = ["123.456", "123", "0.12389127391827094872903841729038471290347808", "12387192378912738917128930.11729038471290347808"]
test_strings.each do |string|
puts "Testing: #{string}"
Benchmark.ips do |x|
x.report("new2") { new2(string) }
x.report("new") { new(string) }
x.report("old") { old(string) }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment