Search
Why is to(T) pure but roundTo(T) impure?
Jun 09, 2012
Chris Saunders
Jun 09, 2012
Jonathan M Davis
Jun 10, 2012
Chris Saunders
Jun 10, 2012
Jonathan M Davis
```Hi --

I've been trying to learn more about D's purity features after reading David Nadlinger's interesting post on this topic. While 'purifying' some existing code I discovered that I can't use roundTo in a pure function, and I don't understand why. Is this a general problem with most floating-point library calls? (e.g. I noticed std.math.floor() is impure as well).

"""
import std.conv;

int func(double d) pure {  // compiles...
}

int func2(double d) pure {  //doesn't compile!?!
return roundTo!int(d);
}
"""

Thanks for any pointers!

-Chris
```
```On Saturday, June 09, 2012 20:43:42 Chris Saunders wrote:
> Hi --
>
> I've been trying to learn more about D's purity features after reading David Nadlinger's interesting post on this topic. While 'purifying' some existing code I discovered that I can't use roundTo in a pure function, and I don't understand why. Is this a general problem with most floating-point library calls? (e.g. I noticed std.math.floor() is impure as well).
>
> """
> import std.conv;
>
> int func(double d) pure {  // compiles...
> }
>
> int func2(double d) pure {  //doesn't compile!?!
> 	return roundTo!int(d);
> }
> """
>
> Thanks for any pointers!

One of them ends up calling an impure function and the other doesn't. All it takes is using one low-level function which isn't pure yet, and _boom_, it can't be pure. This currently happens with pretty much any and all string conversions, for instance, primarily because the low-level array stuff (like Appender) can't be pure yet.

In the case of to!int, this overload is called

------------
T toImpl(T, S)(S value)
if (!isImplicitlyConvertible!(S, T) &&
(isNumeric!S || isSomeChar!S) &&
(isNumeric!T || isSomeChar!T))
{
enum sSmallest = mostNegative!S;
enum tSmallest = mostNegative!T;
static if (sSmallest < 0)
{
// possible underflow converting from a signed
static if (tSmallest == 0)
{
immutable good = value >= 0;
}
else
{
static assert(tSmallest < 0);
immutable good = value >= tSmallest;
}
if (!good)
throw new ConvOverflowException("Conversion negative overflow");
}
static if (S.max > T.max)
{
// possible overflow
if (value > T.max)
throw new ConvOverflowException("Conversion positive overflow");
}
return cast(T) value;
}
------------

Notice the lack of functions being called. The only one is ConvOverflowException's constructor, but apparently that works in a pure function (even though the constructor isn't marked as pure - maybe it's because it's an exception which as being thrown).

Whereas, this is roundTo's definition

------------
template roundTo(Target)
{
Target roundTo(Source)(Source value)
{
static assert(isFloatingPoint!Source);
static assert(isIntegral!Target);
}
}
------------

Note the call to std.math.trunc. It isn't pure, so voila, roundTo can't be pure. Now, it looks like trunc can't be pure because it's calling a C function, and there's a good chance that the declaration for that C function (core.stdc.math.truncl) can be marked as pure, and then trunc could be marked as pure, and then roundTo could be pure, but that obviously hasn't happened yet.

In general, it takes very little for a function to be unable to be pure, especially if it involves low level stuff and/or C stuff in any way.

- Jonathan M Davis
```
```On Saturday, 9 June 2012 at 19:33:55 UTC, Jonathan M Davis wrote:
> On Saturday, June 09, 2012 20:43:42 Chris Saunders wrote:
>> Hi --
>>
>> 'purifying' some existing code I discovered that I can't use
>> roundTo in a pure function, and I don't understand why. Is this a
>> general problem with most floating-point library calls? (e.g. I
>> noticed std.math.floor() is impure as well).
>>
>> """
>> import std.conv;
>>
>> int func(double d) pure {  // compiles...
>> }
>>
>> int func2(double d) pure {  //doesn't compile!?!
>> 	return roundTo!int(d);
>> }
>> """
>>
>> Thanks for any pointers!
>
> One of them ends up calling an impure function and the other doesn't. All it
> takes is using one low-level function which isn't pure yet, and _boom_, it
> can't be pure. This currently happens with pretty much any and all string
> conversions, for instance, primarily because the low-level array stuff (like
> Appender) can't be pure yet.
>
> In the case of to!int, this overload is called
>
> ------------
> T toImpl(T, S)(S value)
>     if (!isImplicitlyConvertible!(S, T) &&
>         (isNumeric!S || isSomeChar!S) &&
>         (isNumeric!T || isSomeChar!T))
> {
>     enum sSmallest = mostNegative!S;
>     enum tSmallest = mostNegative!T;
>     static if (sSmallest < 0)
>     {
>         // possible underflow converting from a signed
>         static if (tSmallest == 0)
>         {
>             immutable good = value >= 0;
>         }
>         else
>         {
>             static assert(tSmallest < 0);
>             immutable good = value >= tSmallest;
>         }
>         if (!good)
>             throw new ConvOverflowException("Conversion negative overflow");
>     }
>     static if (S.max > T.max)
>     {
>         // possible overflow
>         if (value > T.max)
>             throw new ConvOverflowException("Conversion positive overflow");
>     }
>     return cast(T) value;
> }
> ------------
>
> Notice the lack of functions being called. The only one is
> ConvOverflowException's constructor, but apparently that works in a pure
> function (even though the constructor isn't marked as pure - maybe it's
> because it's an exception which as being thrown).
>
> Whereas, this is roundTo's definition
>
> ------------
> template roundTo(Target)
> {
>     Target roundTo(Source)(Source value)
>     {
>         static assert(isFloatingPoint!Source);
>         static assert(isIntegral!Target);
>     }
> }
> ------------
>
> Note the call to std.math.trunc. It isn't pure, so voila, roundTo can't be
> pure. Now, it looks like trunc can't be pure because it's calling a C
> function, and there's a good chance that the declaration for that C function
> (core.stdc.math.truncl) can be marked as pure, and then trunc could be marked
> as pure, and then roundTo could be pure, but that obviously hasn't happened
> yet.
>
> In general, it takes very little for a function to be unable to be pure,
> especially if it involves low level stuff and/or C stuff in any way.
>
> - Jonathan M Davis

Thanks Jonathan. Sounds like a practical issue rather than some theoretical problem -- good to know.
```
```On Sunday, June 10, 2012 04:06:03 Chris Saunders wrote:
> Thanks Jonathan. Sounds like a practical issue rather than some theoretical problem -- good to know.

The vast majority of purity issues with Phobos are purely an implementation issue and not any kind of limit in the language. Obviously some stuff can never be pure (e.g. Clock.currTime or writeln), but for conversions and the like, it's virtually a guarantee that it's an issue with not all of the lower level stuff or C stuff being pure like it needs to be. That's slowly getting fixed, but we still have quite a ways to go. Probably the biggest problem with that from the users perspective is format and to!string, because that makes it almost impossible to make toString pure or to have formatted error messages in assertions in pure functions. We'll get there though.

- Jonathan M Davis
```