Created
September 9, 2017 16:33
-
-
Save RX14/9e6d3733b7c074fded9a8735718b7d7c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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