10 Commits
4 changed files with 91 additions and 26 deletions
+25 -7
View File
@@ -2,20 +2,27 @@
This package was adapted from [romannumeral](https://github.com/brandenc40/romannumeral) to fit my specific minimal use-case. This package was adapted from [romannumeral](https://github.com/brandenc40/romannumeral) to fit my specific minimal use-case.
All functionality except for the ability to convert integers to **lowercase** roman numerals has been removed. All functionality except for the ability to convert integers to roman numerals has been removed.
Since I have less functionality to worry about, I was also able to implement vinculum notation support to extend the range of supported integers to 3,999,999.
I also added the ability to choose between uppercase and lowercase roman numerals (optimized for lowercase due to my personal use-case).
If you need any additional functionality or further documentation, please see the original package. If you need any additional functionality or further documentation, please see the original package.
### Benchmark Results ### Benchmark Results
Note that the benchmark results of this fork are slower than upstream due to the added vinculum notation support.
This benchmark tests the highest supported integer (3,999,999) while upstream tests with 3,999.
```sh ```sh
goos: linux goos: linux
goarch: amd64 goarch: amd64
pkg: github.com/rwinkhart/convertroman pkg: github.com/rwinkhart/convertroman
cpu: AMD Ryzen 9 3900X 12-Core Processor cpu: AMD Ryzen 9 3900X 12-Core Processor
BenchmarkFromInt-24 61993154 20.82 ns/op 0 B/op 0 allocs/op BenchmarkFromInt-24 5372720 237.0 ns/op 24 B/op 1 allocs/op
BenchmarkFromIntCapital-24 2117424 570.9 ns/op 64 B/op 3 allocs/op
PASS PASS
ok github.com/rwinkhart/convertroman 1.827s ok github.com/rwinkhart/convertroman 4.098s
``` ```
### Example ### Example
@@ -28,11 +35,22 @@ import (
rom "github.com/rwinkhart/convertroman" rom "github.com/rwinkhart/convertroman"
) )
func ExampleFromInt() { func main() {
roman := FromInt(4) var roman string
// lowercase
roman = rom.FromInt(4)
if roman == "OOB" { if roman == "OOB" {
panic("Input integer is out of bounds") panic("Input integer is out of bounds (greater than 3,999,999)")
} }
fmt.Println(roman == "IV") // True fmt.Println(roman == "iv") // True
// capital (slightly slower)
// this example uses a number in vinculum notation to show that ANSI escape codes are used to provide the overline
roman = rom.FromIntCapital(4065)
if roman == "OOB" {
panic("Input integer is out of bounds (greater than 3,999,999)")
}
fmt.Println(roman == "\033[53mIV\033[0mLXV") // True
} }
``` ```
+40 -7
View File
@@ -1,8 +1,6 @@
package convertroman package convertroman
// Currently only supports Roman Numerals without viniculum (1-3999; "OOB" is returned for values outside this range) import "strings"
// See here for details on viniculum:
// https://en.wikipedia.org/wiki/Roman_numerals#Large_numbers
var ( var (
// lookup arrays used for converting from an int to a roman numeral extremely quickly. // lookup arrays used for converting from an int to a roman numeral extremely quickly.
@@ -14,13 +12,48 @@ var (
) )
// FromInt converts an integer value to a roman numeral string. // FromInt converts an integer value to a roman numeral string.
// "OOB" is returned if the integer is not between 1 and 3999. // The function will return "0" if the input integer is less than 1.
// The function will return "OOB" if the input integer is greater than
// or equal to 4,000,000 (the roman numeral vinculum limit).
func FromInt(input int) string { func FromInt(input int) string {
// ensure provided integer is within the valid range if input < 1 {
if input < 1 || input > 3999 { return "0"
} else if input < 4000 {
return baseRange(input)
} else if input < 4000000 {
thousands, remainder := separateThousands(input)
return vinculumRange(thousands) + baseRange(remainder)
} else {
return "OOB" return "OOB"
} }
}
// convert the integer to a roman numeral string and return it // FromIntCapital very inefficiently converts an integer value to a CAPITAL roman numeral string.
// Note that this package is optimized for lowercase roman numerals. As such, this function is a slow afterthought.
func FromIntCapital(input int) string {
if input < 4000 {
// if below vinculum range, return the translated result
return strings.ToUpper(FromInt(input))
} else {
// if in vinculum range, remove the ANSI escape codes, capitalize the result, and reapply the ANSI escape codes
noPrefix := strings.TrimPrefix(FromInt(input), "\033[53m")
secondIndex := strings.Index(noPrefix, "\033[0m")
capitalPreANSI := strings.ToUpper(noPrefix[:secondIndex] + noPrefix[secondIndex+4:])
return "\033[53m" + capitalPreANSI[:secondIndex] + "\033[0m" + capitalPreANSI[secondIndex:]
}
}
// separateThousands separates an integer into two parts: the thousands and the remainder.
func separateThousands(num int) (int, int) {
return num / 1000, num % 1000
}
// vinculumRange returns a roman numeral string for an integer greater than 3999.
func vinculumRange(input int) string {
return "\033[53m" + baseRange(input) + "\033[0m"
}
// baseRange returns a roman numeral string for an integer between 1 and 3999.
func baseRange(input int) string {
return r3[input%1e4/1e3] + r2[input%1e3/1e2] + r1[input%100/10] + r0[input%10] return r3[input%1e4/1e3] + r2[input%1e3/1e2] + r1[input%100/10] + r0[input%10]
} }
+25 -11
View File
@@ -1,7 +1,6 @@
package convertroman package convertroman
import ( import (
"fmt"
"testing" "testing"
) )
@@ -22,6 +21,22 @@ var testCases = map[string]int{
"dcccxlix": 849, "cmiv": 904, "m": 1000, "mvii": 1007, "mlxvi": 1066, "dcccxlix": 849, "cmiv": 904, "m": 1000, "mvii": 1007, "mlxvi": 1066,
"mccxxxiv": 1234, "mdcclxxvi": 1776, "mmxxi": 2021, "mmdcccvi": 2806, "mccxxxiv": 1234, "mdcclxxvi": 1776, "mmxxi": 2021, "mmdcccvi": 2806,
"mmcmxcix": 2999, "mmm": 3000, "mmmcmlxxix": 3979, "mmmcmxcix": 3999, "mmcmxcix": 2999, "mmm": 3000, "mmmcmlxxix": 3979, "mmmcmxcix": 3999,
"\033[53miv\033[0m": 4000, "\033[53mv\033[0m": 5000, "\033[53mvi\033[0m": 6000,
"\033[53mvii\033[0m": 7000, "\033[53mviii\033[0m": 8000, "\033[53mix\033[0m": 9000, "\033[53mx\033[0m": 10000, "\033[53mxi\033[0m": 11000, "\033[53mxii\033[0m": 12000,
"\033[53mxiii\033[0m": 13000, "\033[53mxiv\033[0m": 14000, "\033[53mxv\033[0mxcix": 15099, "\033[53mxvi\033[0m": 16000, "\033[53mxvii\033[0m": 17000,
"\033[53mxviii\033[0mi": 18001, "\033[53mxix\033[0m": 19000, "\033[53mxx\033[0m": 20000, "\033[53mxxxi\033[0m": 31000, "\033[53mxxxii\033[0m": 32000,
"\033[53mxxxiii\033[0m": 33000, "\033[53mxxxiv\033[0m": 34000, "\033[53mxxxv\033[0m": 35000, "\033[53mxxxvi\033[0m": 36000, "\033[53mxxxvii\033[0m": 37000,
"\033[53mxxxviii\033[0m": 38000, "\033[53mxxxix\033[0m": 39000, "\033[53mxl\033[0m": 40000, "\033[53mxli\033[0m": 41000, "\033[53mxlii\033[0m": 42000,
"\033[53mxliii\033[0m": 43000, "\033[53mxliv\033[0m": 44000, "\033[53mxlv\033[0mlvi": 45056, "\033[53mxlvi\033[0m": 46000, "\033[53mxlvii\033[0m": 47000,
"\033[53mxlviii\033[0m": 48000, "\033[53mxlix\033[0m": 49000, "\033[53ml\033[0m": 50000, "\033[53mlxxxix\033[0mc": 89100, "\033[53mxc\033[0m": 90000,
"\033[53mxci\033[0m": 91000, "\033[53mxcii\033[0m": 92000, "\033[53mxciii\033[0m": 93000, "\033[53mxciv\033[0m": 94000, "\033[53mxcv\033[0m": 95000,
"\033[53mxcvi\033[0m": 96000, "\033[53mxcvii\033[0m": 97000, "\033[53mxcviii\033[0m": 98000, "\033[53mxcix\033[0m": 99000, "\033[53mc\033[0m": 100000,
"\033[53mci\033[0m": 101000, "\033[53mcii\033[0mcxxvii": 102127, "\033[53mciii\033[0m": 103000, "\033[53mciv\033[0m": 104000, "\033[53mcv\033[0m": 105000,
"\033[53mcvi\033[0m": 106000, "\033[53mcvii\033[0m": 107000, "\033[53mcviii\033[0m": 108000, "\033[53mcix\033[0m": 109000, "\033[53mcxlix\033[0m": 149000,
"\033[53mcccxlix\033[0m": 349000, "\033[53mcdlvi\033[0m": 456000, "\033[53md\033[0m": 500000, "\033[53mdciv\033[0m": 604000, "\033[53mdcclxxxix\033[0m": 789000,
"\033[53mdcccxlix\033[0m": 849000, "\033[53mcmiv\033[0m": 904000, "\033[53mm\033[0m": 1000000, "\033[53mmvii\033[0m": 1007000, "\033[53mmlxvi\033[0m": 1066000,
"\033[53mmccxxxiv\033[0m": 1234000, "\033[53mmdcclxxvi\033[0m": 1776000, "\033[53mmmxxi\033[0m": 2021000, "\033[53mmmdcccvi\033[0mcdxlii": 2806442,
"\033[53mmmcmxcix\033[0m": 2999000, "\033[53mmmm\033[0m": 3000000, "\033[53mmmmcmlxxix\033[0m": 3979000, "\033[53mmmmcmxcix\033[0m": 3999000,
} }
func TestFromInt(t *testing.T) { func TestFromInt(t *testing.T) {
@@ -33,27 +48,26 @@ func TestFromInt(t *testing.T) {
t.Errorf("FromInt(%d) = %s; want %s", input, out, expected) t.Errorf("FromInt(%d) = %s; want %s", input, out, expected)
} }
} }
out := FromInt(100000) out := FromInt(4000000)
if out != "OOB" { if out != "OOB" {
t.Errorf("FromInt(%d) expected an error", 100000) t.Errorf("FromInt(%d) expected an error", 4000000)
} }
out = FromInt(0) out = FromInt(0)
if out != "OOB" { if out != "0" {
t.Errorf("FromInt(%d) expected an error", 0) t.Errorf("FromInt(%d) something went wrong (0 should resolve to 0)", 0)
} }
} }
func BenchmarkFromInt(b *testing.B) { func BenchmarkFromInt(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_ = FromInt(3999) _ = FromInt(3999999)
} }
} }
func ExampleFromInt() { func BenchmarkFromIntCapital(b *testing.B) {
roman := FromInt(4) b.ReportAllocs()
if roman == "OOB" { for i := 0; i < b.N; i++ {
panic("Input integer is out of bounds") _ = FromIntCapital(3999999)
} }
fmt.Println(roman == "IV") // True
} }
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/rwinkhart/convertroman module github.com/rwinkhart/convertroman
go 1.15 go 1.23