5

I'm working in a math library, and due to the inherent troubles of working with double I am coding all equality comparisons type a == b as Math.Abs(a - b) <= epsilon.

Also, by default, I want my formatted strings to be generated with the maximum considered precision. That is, if epsilon is 0.001 I want my default format to be N3.

Happily I did the following:

public static class Math3D
{
     internal const int ROUND = 3;
     public const double Epsilon = 1e-ROUND;
}

...and I got a compilation error. Apparently this is not allowed.

With this limitation I see no way I can define both interdependant constants as consts. Obviously I can define Epsilon as a readonly field but I feel doing so is somehow conceptually wrong. Am I missing an obvious way of how to do this?

rae1
  • 6,066
  • 4
  • 27
  • 48
InBetween
  • 32,319
  • 3
  • 50
  • 90
  • What's the literal error? – rae1 Jun 17 '13 at 15:51
  • You are actually doing math for `Epsilon`, not creating a value of 0.001. – Daniel A. White Jun 17 '13 at 15:51
  • 2
    What's wrong with using a `readonly` field? – xxbbcc Jun 17 '13 at 15:52
  • @Daniel A. White: Well, `const int i = 1 + 3;` is math too and it compiles just fine. – InBetween Jun 17 '13 at 15:53
  • @xxbbcc: As pointed out in answer below, *readonly* implies "this value might change in future versions". `const` on the other hand is a stronger contract which theoretically should convey the idea that "this value will never change". – InBetween Jun 17 '13 at 16:00
  • @InBetween `const` causes the compiler to actually make a copy of the literal value and insert it. Something that you use for rounding and formatting is very likely to change - in fact I'd suggest to make it writable if this is going to be a library. `readonly` for the time being allows you for future changes (if any) without having to recompile clients using this code. (This is not an issue if you always recompile everything.) – xxbbcc Jun 17 '13 at 16:08
  • Be careful when using "epsilon" in this context. Many people expect "epsilon" to mean either "the smallest representable number greater than zero" or "the smallest representable number greater than one". That is confusing enough and now you are adding a third meaning. – Eric Lippert Jun 17 '13 at 17:04
  • @Eric Lippert: I will change the name to avoid confusion. `Accuracy` is a better term. – InBetween Jun 17 '13 at 17:10

4 Answers4

10

If you're going to possibly be changing it, you should use readonly here. const should really be used for things that will never change, like π. The reason for this is because of a subtle difference between const and readonly.

The main issue is that if you change the value of the const, you must recompile all dependent clients that use the const, otherwise you can shoot yourself in the foot, badly. So for values that might change, don't use const, use readonly.

So, if the value is never going to change, just use const and then don't worry about defining Epsilon in terms of ROUND, just say:

internal const int ROUND = 3;
public const double Epsilon = 1e-3;

If you really want to make sure you don't accidentally change one without changing the other, you could add a small check in the constructor:

if (Epsilon != Math.Pow(10, -ROUND)) {
    throw new YouForgotToChangeBothConstVariablesException();
}

You could even add conditional compilation so that only gets compiled in debug releases.

If it is going to change, use static readonly:

internal readonly int ROUND = 3;
public static readonly double Epsilon = Math.Pow(10, -ROUND);

With this limitation I see no way I can define both interdependant constants as consts. [...] Am I missing an obvious way of how to do this?

No, you need to do some kind of math using Math.Pow or Math.Log to go between ROUND and Epsilon and those are not acceptable for compile-time usage with const. You could write a miniature code generator to spit out these two lines of code based on a single input value, but I really question the value of investing time into that.

Community
  • 1
  • 1
jason
  • 236,483
  • 35
  • 423
  • 525
  • Well that is precisely the point. Both consts will never change and that is what I want the code to "say". In development we might fine tune the value a few times and I was just wondering if we could chain both somehow. Obviously if there is no solution the final code will ship with two independant consts. – InBetween Jun 17 '13 at 15:59
  • @InBetween: You can't do the kind of math at compile time that you want to do with `const` to solve the problem that you're trying to solve. – jason Jun 17 '13 at 16:04
  • @Jason: Ok thanks, that is what I wanted to know. It just suprised me because I can do maths with a `const` to define another `const`: `const I = 10; const J = 2 * I;` so I thought this should be possible too. Still I get the feeling that this is not a semantic issue but more of a lexical one. – InBetween Jun 17 '13 at 16:09
  • That's because the compiler can figure out the result of `2 * I` at compile time. It can not figure out the value of `Math.Pow(10, -ROUND)` at compile time. And `1e-ROUND` is just not legal syntax, ever. – jason Jun 17 '13 at 16:10
  • @Jason: That last reasoning I dont agree with. The compiler can figure out 1e-3, as `const Epsilon = 1e-3` compiles just fine. So if `Round` is a `const` I dont understand why `const Epsilon = 1e-Round` wont compile unless, as I stated above, the expression is not *lexically* valid. – InBetween Jun 17 '13 at 16:11
  • `1e-3` is a valid `double` literal, `1e-ROUND` is not. That's why I said it's *not legal syntax*. – jason Jun 17 '13 at 16:12
  • Ok, that is what suprised me. That `1e-Round` is not legal syntax. I thought it should be but obviously its not. – InBetween Jun 17 '13 at 16:15
  • 5
    @InBetween: What you are expecting is that `e` is an *exponentiation operator* and that the syntax is *expression* `e` *expression*. But `e` is not an operator at all; it's part of the literal. Think of `e` as a special kind of decimal point. You wouldn't expect `x = 1; y = 2; z = x.y;` to mean `z = 1.2;` -- the same is true for the `e` notation; it basically means "here's how much to move the decimal point". – Eric Lippert Jun 17 '13 at 17:07
  • @Eric Lippert: 100% accurate and clear. That was exactly how I was understanding `e` and its obviously a very wrong interpretation. All straightened out. Thanks! – InBetween Jun 17 '13 at 17:14
2

1e-ROUND, specifically 1e is not a valid literal integer. You would have to do something like,

public static readonly double Epsilon = 
    decimal.Parse(
        string.Format("1E-{0}", ROUND), 
        System.Globalization.NumberStyles.Float);

Also, note the static readonly since you cannot use a const when the expression won't be known until runtime. The static readonly will work similarly to a const in this scenario.

If you prefer not dealing with strings, you can always do,

public static readonly double Epsilon = Math.Pow(10, -ROUND);
rae1
  • 6,066
  • 4
  • 27
  • 48
  • 2
    … you “would have to” format the number into a string and then parse it? Call me old-fashioned but I find that a huge conceptual detour. Use a simple arithmetic calculation to get the number. – Konrad Rudolph Jun 17 '13 at 15:57
  • I don't think this is very good advice. Using `Math.Pow()` would be just fine and avoids a string parsing operation. – xxbbcc Jun 17 '13 at 16:00
1

You could always just hard code the 3. Seeing as you are using constants, then have no intention of ever changing the value to anything other than 3 right? So you don't need to worry too much about DRY.

public static class Math3D
{
    internal const int ROUND = 3;
    public const double Epsilon = 1e-3;
}

If you are thinking you might want to change the 3, then const is not for you and your question becomes moot.

Buh Buh
  • 7,443
  • 1
  • 34
  • 61
1

Edit:

This is not a direct answer to your question but have you considered changing Round and Epsilon into writable fields? If you use these for formatting / rounding, it's almost guaranteed they'll need to change on occasion - neither a const nor a readonly field will work for that.

public static class Math3D
{
    internal static int s_Round;
    internal static double s_Epsilon;

    static Math3D ()
    {
        Round = 3;
    }

    public static double Epsilon
    {
        get
        {
            return ( s_Epsilon );
        }
    }

    public static int Round
    {
        get
        {
            return ( s_Round );
        }
        set
        {
            // TODO validate
            s_Round = value;
            s_Epsilon = Math.Pow ( 10, -s_Round );
        }
    }
}

It's a clearly readable solution that won't break when you change things in the future.

xxbbcc
  • 16,930
  • 5
  • 50
  • 83
  • +1 If OP as working on a math library it makes sense to customize the formatting behavior so a property to get and set the rounding limit is a good idea. –  Jun 17 '13 at 16:28
  • Thanks for the sample. I've basically ended up doing something similar but I really don't see the reason why I would want consumers to change the *accuracy* of the library and the default "rounding" can always be customized with format strings so that isn't an issue either. So the solution will be two `readonly` fields. – InBetween Jun 17 '13 at 17:19
  • @InBetween I've used several 3D modelers where accuracy could be changed on the fly. This is a very useful feature in some circumstances, that's why I suggested it. – xxbbcc Jun 17 '13 at 17:21
  • @xxbbcc: I would guess that the reason in 3D modelers is due to performance versus quality concerns. My library is in an alltogether different league when it comes to performance requirements so it's really a non issue. Thanks for the advice though. – InBetween Jun 17 '13 at 17:23