!standard 3.3.2(3) 20-09-03 AI12-0394-1/01 !standard 4.2.1(4/5) !standard 4.2.1(8/5) !standard 4.2.1(12/5) !standard 4.2.1(13/5) !class Amendment 20-09-03 !status work item 20-09-03 !status received 20-06-09 !priority Low !difficulty Easy !subject Named Numbers and User-Defined Numeric Literals !summary Integer named numbers may be used with types that have an Integer_Literal. Real named numbers may be used with types that have a Real_Literal aspect with an overloading that takes two strings. !problem If a user specifies that a type should be usable with numeric literals, it is a surprise that the type cannot be used with a corresponding named number. In general it is not recommended to sprinkle unnamed numeric literals throughout code, as these "magic" numbers have no obvious meaning. It is natural to use named numbers to avoid this problem. But the current mechanism for supporting the use of numeric literals with a user-defined type does not allow substituting a numeric literal with a corresponding named number. !proposal An integer named number may be used with a type that has an Integer_Literal aspect. The value of the named number is represented as a String using the usual notation for an integer literal, and then passed to the function denoted by the Integer_Literal aspect. A real named number may be used with a type that has a Real_Literal aspect, provided there is an overloading of the function named by the Real_Literal aspect that takes two Strings, one representing the value of the numerator and ane representing the value of the denominator, in the notation of an integer literal. !wording Add after 3.3.2(3): A name that denotes a number_declaration is interpreted as a value of a universal type, unless the expected type for the name is a non-numeric type with an Integer_Literal or Real_Literal aspect, in which case it is interpreted to be of its expected type. Modify 4.2.1(4/5): Real_Literal This aspect is specified by a function_name that statically denotes a function with a result type of T and one IN parameter that is of type String and is not explictly aliased{, and optionally a second function with a result type of T and two IN parameters of type String that are not explicitly aliased}. Add after 4.2.1(8/5): When a named number that denotes a value of type universal_integer is interpreted as a value of a non-numeric type T, it is equivalent to a call to the function denoted by the Integer_Literal aspect of T. The actual parameter of this notional call is a String having a textual representation of a decimal integer literal optionally preceded by a minus sign, representing the same value as the named number. When a named number that denotes a value of type universal_real is interpreted as a value of a non-numeric type T, it is equivalent to a call to the two-parameter function denoted by the Real_Literal aspect of T, if any. The actual parameters of this notional call are each being a Stirng with the textual representation of a decimal integer literal, with the first optionally preceded by a minus sign, where the first String represents the same value as the numerator, and the second the same value as the denominator, of the named number when represented as a rational number in lowest terms, with a positive denominator. Modify 4.2.1(12/5): If a nonabstract tagged type inherits any user-defined literal aspect, then each inherited aspect shall be directly specified as a nonabstract function for the type unless the inherited aspect denotes a nonabstract function{, or functions,} and the type is a null extension. Add after 4.2.1(13/5): If a named number that denotes a value of type universal_integer is interpreted as a value of a non-numeric type T, T shall have an Integer_Literal aspect. If a named number that denotes a value of type universal_real is interpreted as a value of a non-numeric type T, T shall have a Real_Literal aspect, and the aspect shall denote a function that has two IN parameters, both of type String, with result of type T. Modify 4.2.1(14/5): It is a bounded error if the evaluation of a literal {or named number} that has an expected type with a specified user-defined literal aspect propagates an exception. Either Program_Error or the exception propagated by the evaluation is raised at the point of use of the value of the literal {or named number}. If it is recognized prior to run time that evaluation of such a literal {or named number} will inevitably (if executed) result in such a bounded error, then this may be reported as an error prior to run time. !discussion As mentioned in the problem, if a type supports numeric literals, then it would be strange if such a numeric literal could not be replaced by a named number with the same value. Such a situaiton might create an incentive to use literals in places where named numbers would be more meaningful. We considered not requiring a second overloading for named numbers of the function denoted by the Real_Literal aspect, and using, for example, "/" to separate the numerator and denominator, but it was felt that would be error prone. By requiring an explicit overloading of the function to use for the rational representation of a real named number, it would both simplify parsing and ensure that the support for real named numbers was intentional. We used the same technique as was used for literals, where the interpretation of a name that denoted a number_declaration was interpreted to be of its expected type if the expected type had an Integer_Literal or Real_Literal aspect. At Name Resolution, we don't distinguish between integers and reals, to keep the resolution simple. The distinction is made in the legality rules, where better error messages can be provided if the wrong sort of named number is used with a type that supports only one kind of numeric literal. !ASIS No ASIS effect. !ACATS test ACATS B- and C-Tests are needed to check that the new capabilities are supported. !appendix !topic How to get named numbers into Big_Reals !reference Ada 202x RM A.5.7 !from Christoph Grein 20-06-08 !discussion I do not see a way to get named numbers into Big_Reals. This does not work: P: Big_Real := 0.0 + Ada.Numerics.Pi; P: Big_Real := Ada.Numerics.Pi / 1.0; The only way I found is to copy the literal. What a nuisance! Must this deficiency be corrected? **************************************************************** From: Christoph Grein Sent: Wednesday, June 10, 2020 4:12 AM Ah, and the reverse. If I have computed a big number (the problem also applies to big integers), can I transform it into a named number. I do not know whether this is important enough. Here, true magic is requested of our beloved lady. **************************************************************** From: Randy Brukardt Sent: Wednesday, June 10, 2020 6:35 PM > The only way I found is to copy the literal. What a nuisance! Moral: untyped things are bad. That's sort of the Ada philosophy anyway. > Must this deficiency be corrected? How? These are ordinary objects, not some piece of syntax that it is easy to apply some (additional) special rules to. Having some sort of implicit conversion for static expressions seems to violate strong typing. Moreover, for values like PI you most likely need more precision than the named number has for use in Big_Reals. (Indeed, you really need to analyze your usages to know how much precision you need - it's definitely not one-size-fits-all.) For small values, you can just convert to Long_Float and load that. The need for workarounds is not ideal, but a library is never going to be a perfect replacement for built-in types - there will always be something you can't do. (I suspect that's why many newer languages are moving away from having any truly built-in types.) **************************************************************** From: Randy Brukardt Sent: Wednesday, June 10, 2020 6:47 PM > Ah, and the reverse. If I have computed a big number (the problem also > applies to big integers), can I transform it into a named number. Surely not. A named number requires a static expression, while a Big_Integer or Big_Real can never be static. I had suggested a scheme for supporting user-defined static expressions, but the need was not considered important enough. The argument is that the only *need* for static expressions is in type declarations, specifically the rules requiring various parts and aspect_specifications for types to be static. No Legality Rules do or likely could depend on user-defined static expressions, ergo the need is arguably non-existent. Moreover, pretty much any static expression that could be evaluated in a Big_Num could also be directly evaluated using an Ada expression. (Remember that Ada compilers are required to use Big_Nums to evaluate static expressions.) So the need seems really tiny. > I do not know whether this is important enough. Here, true magic is > requested of our beloved lady. I personally think that named numbers are silly as they violate Ada's strong typing tenets. There are a few fundamental constants like PI that make sense to be untyped, but that's about it, and as I noted in my previous message for those one may need a lot more precision for PI than 50 digits when using Big_Reals. Declarations for named numbers should be used very sparingly to begin with, and the need to use Big_Reals rather than a proper static expression seems nonexistent given that the evaluation of both use unlimited precision. **************************************************************** From: Tucker Taft Sent: Wednesday, June 10, 2020 8:02 PM >> The only way I found is to copy the literal. What a nuisance! > > Moral: untyped things are bad. That's sort of the Ada philosophy anyway. Be that as it may, I am somewhat sympathetic. If 'Image were defined for named numbers, the following could work: P : Big_Real := From_String (Ada.Numerics.Pi'Image); but alas, even if it were legal, it would probably end up dependent on the number of decimal places produced by Pi'Image, which would generally be determined by the longest float type supported. It is a bit frustrating that we have compile-time big numbers, i.e. named numbers, and now run-time big numbers, i.e. Big_Integer and Big_Real, but no way to bridge the gap. We could probably define some syntactic sugar that connected them, by saying that a named number may be used wherever a numeric literal of a corresponding kind is permitted, including in contexts where the literal would be passed, as a string, to a user-defined literal-conversion function. Doesn't sound like *too* much of a leap... Syntactic sugar can be your friend. ;-) >> Must this deficiency be corrected? See above with an idea... **************************************************************** From: Randy Brukardt Sent: Tuesday, June 16, 2020 8:43 PM ... >>(Remember that Ada compilers are required to use Big_Nums to evaluate >>static expressions.) >So there is indeed some magic built-in in Ada compilers. Why not make >this usable for users? Because it's complicated and not very useful. The whole Big_Num requirement is dubious (at least some numeric people consider the extra precision kept a problem), and certainly a compiler Big_Num library has very different performance requirements (to do a few hundred operations per compile) than a general Big_Num library. There's little commonality. It's complicated because one would have to have a way to write a real named number as a ratio of integers in order to not lose any precision (think Third : constant 1.0/3.0;). Any mechanism would need to have rules to ensure that a particular number provides the same numerator and denominator for any Ada implementation, and in any case would have to be separate from the mechanism for real literals (which take a string in real notation). It seems that a library would have to be purpose-written to support such a mechanism; that adds more work to implementing Big_Reals and probably means that most user-written libraries using real literals wouldn't support it anyway. All in all, it seems like overkill to me. **************************************************************** From: Tucker Taft Sent: Wednesday, June 17, 2020 8:21 AM Various opinions here, but as I mentioned, I think the better angle is to allow types that accept numeric literals to also accept named numbers. I have suggested for integer literals, the compiler can convert the value of the named number into a string. For real literals, the user would have to overload the Real_Literal function with one that took *two* strings, one for the numerator and one for the denominator. If the user provides such an overloading, then named numbers of univ-real type would be usable with the type. If we provide this capability, then big-ints/reals could also use named numbers, since they permit integer and real literals. So bottom line is I do sympathize with the view that if a type allows numeric literals, it should accept equivalent named numbers. **************************************************************** From: Christoph Grein Sent: Wednesday, June 17, 2020 10:23 AM > I have suggested for integer literals, the compiler can convert the value of > the named number into a string. Brilliant idea. > For real literals, the user would have to overload the Real_Literal function > with one that took *two* strings, one for the numerator and one for the > denominator. If the user provides such an overloading, then named numbers > of univ-real type would be usable with the type. But there is already such a function: 17/5 function From_Quotient_String (Arg: String) return Valid_Big_Real; with Num and Den separated by '/'. Why not take this? function From_String (Num, Den: String) return Valid_Big_Real is (From_Quotient_String (Num & '/' & Den)); **************************************************************** From: Randy Brukardt Sent: Wednesday, June 17, 2020 11:17 PM Tucker is talking about the basic user-defined literal functions, not specifically about the Big_Number packages. We're not interested in defining magic for only a single use! Note that anyone using user-defined real literals would either have to do extra work to support named numbers, or would have to forget about supporting them. Neither of those seem very attractive to me. Note that the rational number representation may not have a reasonable approximation for some user abstractions, especially if Big_Numbers (with their implicit dynamic allocation) are not acceptable for that abstraction. It's clear that supporting such numbers is *possible*, but the question is whether the complication and inconsistencies inherent in such support is worth the gain. I don't think so, but YMMV. ****************************************************************