digitalmars.D - Formatting -0.0 as 0.0
- Bastiaan Veelo (48/48) Oct 15 2021 `std.format` maintains the minus sign when formatting a negative
- Steven Schveighoffer (10/13) Oct 15 2021 The only way I can think of that isn't really intrusive (like your
- Bastiaan Veelo (21/35) Oct 15 2021 Thank you Steve.
- Steven Schveighoffer (7/25) Oct 15 2021 Well, it's based on C's implementation, so you are definitely not the
- Elronnd (13/16) Oct 15 2021 I disagree. With -ffast-math you're explicitly saying 'please
- Steven Schveighoffer (4/13) Oct 16 2021 Yeah you are probably right. I didn't realize that was a special
- Bastiaan Veelo (70/74) Oct 18 2021 I have settled on a wrapper. @kinke has helped me to deal with
`std.format` maintains the minus sign when formatting a negative zero floating point value ([by design](https://forum.dlang.org/post/drkiaf$259l$1 digitaldaemon.com)): ```d double r = -0.0; ``` I am looking for a low-impact way to output negative zero as positive zero to prevent our users from raising their eyebrows. There is a trick that would be somewhat acceptable if it worked consistently: ```d double r = -0.0; ``` However, we have found that in some places with some ldc options the `+0` gets optimized out! I have not reduced the format used above where this happens, but here is another comparable example: ```d // ldc options: -ffast-math -enable-inlining import std; void main() { double r = -0.0; writeln(__LINE__, ":\t", r.format!"% 4.3f"); // -0.000 writeln(__LINE__, ":\t", (r + 0).format!"% 4.3f"); // 0.000 writeln(__LINE__, ":\t", r.posZero.format!"% 4.3f"); // -0.000 SURPRISE! } T posZero(T)(T value) if (isFloatingPoint!T) { return value + 0; } ``` https://run.dlang.io/is/XmAhLM The alternative to `(r+0)` that does work consistently is `(r){return r == -0.0 ? 0.0 : r;}(r)` but that's just too much noise, and so the best I can come up with is inserting a call to ```d T posZero(T)(T value) if (isFloatingPoint!T) { return value == -0.0 ? 0.0 : value; } ``` What I would like best is if there were a format specifier flag that does this conversion automatically, like "%> 4.3f". Does it make sense to add this? Is there a better way? -- Bastiaan.
Oct 15 2021
On 10/15/21 8:37 AM, Bastiaan Veelo wrote:What I would like best is if there were a format specifier flag that does this conversion automatically, like "%> 4.3f". Does it make sense to add this? Is there a better way?The only way I can think of that isn't really intrusive (like your `posZero` thing) is to define your own `writef`/`format` wrappers (what I'd do is check any doubles, and replace them when necessary). Another thing to do is to catch it at the source. That is, when you store a value somewhere where it might be printed, if it's `-0.0`, change it to `0.0`. Note that if ldc is optimizing out an addition with 0, and that actually changes the observable results, that technically is an invalid optimization. -Steve
Oct 15 2021
Thank you Steve. On Friday, 15 October 2021 at 14:09:32 UTC, Steven Schveighoffer wrote:On 10/15/21 8:37 AM, Bastiaan Veelo wrote:The thing that is holding me back is that it feels backwards to wrap `std` to fix what I perceive as a usability issue, contra fixing `std` itself. Hence my proposal of extending the format specification -- although I find it complicated enough as it is. I am surprised that I am the first to have this problem. I think I'm going to follow your suggestion nonetheless, as seeing `posZero` all over the place looks ridiculous...What I would like best is if there were a format specifier flag that does this conversion automatically, like "%> 4.3f". Does it make sense to add this? Is there a better way?The only way I can think of that isn't really intrusive (like your `posZero` thing) is to define your own `writef`/`format` wrappers (what I'd do is check any doubles, and replace them when necessary).Another thing to do is to catch it at the source. That is, when you store a value somewhere where it might be printed, if it's `-0.0`, change it to `0.0`.Is not an option in my situation, as it must work on machine translated code. For new code it would work, but I doubt our programmers will be conscious of the issue at all times. As we work with a left-handed coordinate system, multiplying Y with -1 happens frequently, and 0 is a common value for Y. Easy to miss a spot.Note that if ldc is optimizing out an addition with 0, and that actually changes the observable results, that technically is an invalid optimization.Issue filed: https://github.com/ldc-developers/ldc/issues/3851 Fun fact: I checked two other Pascal compilers: gpc prints `-0.0` as `0.0` like our old compiler does, fpc is like D and prints it as `-0.0`. --Bastiaan.
Oct 15 2021
On 10/15/21 11:18 AM, Bastiaan Veelo wrote:Thank you Steve. On Friday, 15 October 2021 at 14:09:32 UTC, Steven Schveighoffer wrote:Well, it's based on C's implementation, so you are definitely not the first ;) https://stackoverflow.com/questions/9657993/how-to-convert-negative-zero-to-positive-zero-in-c That also has a good suggestion instead of using * 0, you can check if it's 0 and just assign 0 in that case. -SteveOn 10/15/21 8:37 AM, Bastiaan Veelo wrote:The thing that is holding me back is that it feels backwards to wrap `std` to fix what I perceive as a usability issue, contra fixing `std` itself. Hence my proposal of extending the format specification -- although I find it complicated enough as it is. I am surprised that I am the first to have this problem.What I would like best is if there were a format specifier flag that does this conversion automatically, like "%> 4.3f". Does it make sense to add this? Is there a better way?The only way I can think of that isn't really intrusive (like your `posZero` thing) is to define your own `writef`/`format` wrappers (what I'd do is check any doubles, and replace them when necessary).
Oct 15 2021
On Friday, 15 October 2021 at 14:09:32 UTC, Steven Schveighoffer wrote:Note that if ldc is optimizing out an addition with 0, and that actually changes the observable results, that technically is an invalid optimization.I disagree. With -ffast-math you're explicitly saying 'please feel free to make changes to expressions involving floating-point numbers which maintain all observable results only under the assumption that floating-point numbers have properties that they actually don't'. Most usually, these changes are things like turning (x+y)+z into x+(y+z) because it schedules better, turning or x+y*z into an FMA. Both of these can affect observable results because FP math is not associative, and because FMA is computed at high precision without an intermediate rounding stage. Elimination of x+0.0 definitely falls under that umbrella.
Oct 15 2021
On 10/15/21 6:26 PM, Elronnd wrote:On Friday, 15 October 2021 at 14:09:32 UTC, Steven Schveighoffer wrote:Yeah you are probably right. I didn't realize that was a special optimization flag. -SteveNote that if ldc is optimizing out an addition with 0, and that actually changes the observable results, that technically is an invalid optimization.I disagree. With -ffast-math you're explicitly saying 'please feel free to make changes to expressions involving floating-point numbers which maintain all observable results only under the assumption that floating-point numbers have properties that they actually don't'.
Oct 16 2021
On Friday, 15 October 2021 at 14:09:32 UTC, Steven Schveighoffer wrote:The only way I can think of that isn't really intrusive (like your `posZero` thing) is to define your own `writef`/`format` wrappers (what I'd do is check any doubles, and replace them when necessary).I have settled on a wrapper. kinke has helped me to deal with fast-math, so this can be branchless. ```d import std.traits : isSomeChar, isSomeString; /** Wraps std.format.format converting any negative zero floating point to positive. */ immutable(Char)[] fmt(Char, Args...)(in Char[] spec, Args args) if (isSomeChar!Char) { import std.format : format; return format(spec, PosZero!args); } /** idem */ typeof(spec) fmt(alias spec, Args...)(Args args) if (isSomeString!(typeof(spec))) { import std.format : format; return format!spec(PosZero!args); } // Maps args, adding 0.0 if floating point. private template PosZero(args...) { import std.meta : staticMap; template posZero(alias arg) { import std.traits : isFloatingPoint; static if (isFloatingPoint!(typeof(arg))) { version (LDC) { import ldc.attributes : llvmFastMathFlag; llvmFastMathFlag("clear") auto posZero() {return arg + 0.0;} } else auto posZero() {return arg + 0.0;} } else alias posZero = arg; } alias PosZero = staticMap!(posZero, args); } unittest { import std.format : format; //assert((-0.0).format!"%3.1f" == "-0.0"); assert((-0.0).fmt!"%3.1f" == "0.0"); //assert(format("%3.1f", -0.0) == "-0.0"); assert(fmt("%3.1f", -0.0) == "0.0"); string foo(immutable double d) { return d.fmt!"%3.1f"; } assert(foo(-0.0) == "0.0"); //assert(format!"%d;%3.1f;%3.1f;%3.1f;%s"(42, -0.0, 3.14, -0.0, "!") == "42;-0.0;3.1;-0.0;!"); assert(fmt!"%d;%3.1f;%3.1f;%3.1f;%s"(42, -0.0, 3.14, -0.0f, "!") == "42;0.0;3.1;0.0;!"); //assert(format("%d;%3.1f;%3.1f;%3.1f;%s", 42, -0.0, 3.14, -0.0, "!") == "42;-0.0;3.1;-0.0;!"); assert(fmt("%d;%3.1f;%3.1f;%3.1f;%s", 42, -0.0, 3.14, -0.0f, "!") == "42;0.0;3.1;0.0;!"); assert(fmt!""() == format!""()); assert(fmt("") == format("")); } ``` Love this language! -- Bastiaan.
Oct 18 2021