Skip to content

Measured trailing zeros count as insignificant: Parse("1.20") has 2 sig figs, so 1.20 × 1.23456 = 1.5 and 9.81 × 10.0 = 100 #120

Description

@matt-edmondson

What's wrong

SignificantNumber stores its value as a normalized PreciseNumber, which moves trailing zeros into the exponent (README "Trailing zero removal", README.md:283-290). SignificantDigits is then read straight from that value (SignificantNumber/SignificantNumber.cs:98). Multiplication, division, Pow/Exp and comparison all take the result's precision from LowestSignificantDigits (line ~185, used at ~240, ~253, ~385, ~625, ~677).

Trailing zeros are significant in a measurement, so the type loses precision the user actually has. 1.20 is treated as 2 significant figures, and 10.0, 2.0 and 4.00 as 1. The type has no way to represent a value such as 2.50 or 10.0 with its real precision.

Failure scenarios

Observed on HEAD with ktsu.PreciseNumber 2.6.4:

1.20 * 1.23456   = 1.5      (expected 1.48)
2.50 * 4.00      = 10       (expected 10.0)
12.0 / 4.00      = 3        (expected 3.00)
9.81 * 10.0      = 100      (expected 98.1)
100.0 / 3.00     = 30       (expected 33.3)
3.14159 * 2.0    = 6        (2.0 from double)

The collapse spreads: once one operand ends in 0, every later * and / in the chain drops the digits the user supplied. The migration guide's own example, ToMeters(10.ToSignificantNumber()), returns 3 rather than the 3.048 its comment implies, for the same reason.

For a library whose purpose is significant-figure arithmetic, this is the most common way real measured inputs are written.

Suggested fix

Track precision separately from the normalized value:

  • Add a field for significant digits, or for the least-significant decimal place, next to Value. Parse and string input set it from the literal ("1.20" → 3 s.f.); numeric conversions set it from the source's natural digits, or from an explicit overload.
  • Have the arithmetic rules read that field instead of Value.SignificantDigits. The decimal-place rule for +, - and % (Add/Subtract never round to tens or hundreds: 1.2E3 + 34 = 1234 instead of 1.2E3 #107) would use the same field.
  • Have ToString pad with the significant trailing zeros (10.0, 2.50).

This changes the struct's equality and layout, so it is likely v3 material. A smaller first step is an opt-in Parse/constructor overload that keeps precision from the literal.

Acceptance criteria

  • SignificantNumber.Parse("1.20").SignificantDigits == 3, and Parse("10.0") reports 3.
  • Parse("1.20") * Parse("1.23456") equals 1.48, and Parse("9.81") * Parse("10.0") equals 98.1.
  • Parse("2.50").ToString() gives 2.50.

Related, not duplicates: #107 (decimal-place rule for addition), #1 (closed, integer exponent).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions