Skip to content

v3.0.0

Latest

Choose a tag to compare

@deliciousNesquik deliciousNesquik released this 24 Aug 08:21

A correctness release. Five defects let the library return wrong answers silently — most
importantly, the range guard that is the entire point of the type could be bypassed by writing
roman + 1. Fixing them required breaking changes, hence the major version.

Nothing about the numeral system itself changed: the range is still 1–3999, canonical output is
identical, and every valid canonical string parses to the same value as before.

Upgrading from 2.x

2.x 3.0.0
int x = roman; int x = (int)roman; or roman.ToInt()
new Roman(3999) + 14000 throws OverflowException
roman == 42int comparison, disagreed with roman.Equals(42) Roman/int operator; the two now agree
Parse("IIX", RomanStyle.Lenient)10 8
Parse(s, (RomanStyle)7) → parsed leniently throws ArgumentOutOfRangeException
new Roman((string)null)ArgumentException ArgumentNullException

Existing catch (ArgumentException) handlers keep working — ArgumentNullException derives from it.
The compiler will point you at every site that needs the (int) cast.

Breaking changes

  • Romanint is now an explicit conversion. An implicit one made Roman's own operators
    inapplicable in any mixed expression: because intRoman is explicit, they were not
    candidates, so overload resolution fell back to the predefined int operators. `new Roman(3999)
    • 1evaluated to4000with no exception, andnew Roman(10) + 5produced theint 15rather thanXV. Use (int)romanorroman.ToInt()`.
  • Mixed Roman/int arithmetic is range-checked. + - * / now validate the result against
    1–3999 and throw OverflowException, exactly as the all-Roman operators always did. Operands
    widen to long first, so a large int operand cannot overflow the intermediate value.
  • Lenient parsing of multi-symbol subtractive runs is corrected. A symbol is subtractive when
    it is smaller than the largest symbol anywhere to its right, not merely smaller than its
    immediate neighbour. "IIX" was 10 and is now 8; "XIIX" was 20, now 18; "IIC" was
    100, now 98. Strict parsing is unaffected — it rejected these forms before and still does.
  • An undefined RomanStyle is rejected. Any value outside the declared members — from a cast
    or an uninitialized field — silently selected lenient parsing, skipping the canonical check
    entirely. It now throws ArgumentOutOfRangeException. TryParse throws as well rather than
    returning false, matching int.TryParse with an invalid NumberStyles. default(RomanStyle)
    is Strict and still parses strictly.
  • A null string throws ArgumentNullException. string.IsNullOrWhiteSpace collapsed null and
    empty into one branch, so a missing argument was reported as ArgumentException: Roman numeral cannot be empty. Empty and whitespace-only strings still throw ArgumentException.
  • AssemblyVersion now tracks the package version. Every release up to and including 2.0.1
    shipped an assembly stamped 1.0.0.0, because only PackageVersion was set and that feeds the
    nuspec alone. typeof(Roman).Assembly.GetName().Version now returns 3.0.0.
  • ArgumentException for an invalid character now names its parameter. ParamName was null;
    it is now "roman", and the message gains the usual (Parameter 'roman') suffix.

Added

  • Operators + - * /, < > <= >= and == != for (Roman, int) and (int, Roman), in both
    operand orders.

  • Equals(int) and CompareTo(int), plus IEquatable<int> and IComparable<int>, so
    roman.Equals(42) agrees with roman == 42.

  • Comparison against an int never constructs a Roman, so an unrepresentable bound is a fair
    question: new Roman(3999) < 5000 is true.

  • roman / 0 reaches DivideByZeroException, which was unreachable while both operands were
    Roman.

    operand orders.

  • Equals(int) and CompareTo(int), plus IEquatable<int> and IComparable<int>, so
    roman.Equals(42) agrees with roman == 42.

  • Comparison against an int never constructs a Roman, so an unrepresentable bound is a fair
    question: new Roman(3999) < 5000 is true.

  • roman / 0 reaches DivideByZeroException, which was unreachable while both operands were
    Roman.

Fixed

  • The leading-minus check used a culture-sensitive StartsWith(string). Under ICU, characters such
    as U+200B and U+00AD are collation-ignorable, so "\u200B-X" "started with" - and was reported
    as a sign problem rather than an invalid character — and the answer changed with the consuming
    application's globalization mode. Now ordinal (CA1310), and 11× cheaper on a check that runs once
    per parse.
  • Dead links and a mismatched table-of-contents anchor in both READMEs. README.md ships inside
    the package, so these were visible on the nuget.org page.

Compatibility

.NET 9, no dependencies. 220 unit tests.