Skip to content

DegreesToRadians and RadiansToDegrees overflow and inaccuracy #131930

Description

@ed0d2b2ce19451f2

The following methods have issues with overflow and inaccuracy:

The current implementations for Single appear to be equivalent to:

float DegreesToRadians(float degrees) {
    return (degrees * Pi) / 180.0f;
}
float RadiansToDegrees(float radians) {
    return (radians * 180.0f) / Pi;
}

Both return infinity when the multiplication yields infinity, even if the end result would otherwise be finite. The Double versions have the same issue.

Overflow aside, these implementations seem unnecessarily inaccurate.

Here is a table comparing the existing and two alternative binary32 implementations of each conversion for the 2^23 = 8388608 values in [1, 2) or any other binade not involving overflow or subnormals. In the implementation column, operators and constants are exact and f(...) indicates rounding to the nearest value representable in the floating point format.

|             |                   |      distance from correctly rounded      |
|             |                   |     2 ULP     |      1 ULP      |  0 ULP  |
|  direction  | implementation    | count |   %   |  count  |   %   |         |
| ----------- | ----------------- | ----- | ----- | ------- | ----- | ------- |
| deg --> rad | f(f(x*f(pi))/180) | 20895 | 0.249 | 3147446 | 37.52 | balance |
| deg --> rad | f(x*f(pi/180))    |     0 | 0     |  792445 |  9.44 | balance |
| deg --> rad | f(x/f(180/pi))    |     0 | 0     | 1194111 | 14.23 | balance |
| rad --> deg | f(f(x*180)/f(pi)) |  3502 | 0.041 | 3183331 | 37.94 | balance |
| rad --> deg | f(x/f(pi/180))    |     0 | 0     |  792441 |  9.44 | balance |
| rad --> deg | f(x*f(180/pi))    |     0 | 0     | 1194116 | 14.23 | balance |

Here is a similar table comparing binary64 implementations for 1e7 samples chosen uniformly at random from [1, 2).

|             |                   | distance from correctly rounded |
|             |                   |   2 ULP   |   1 ULP   |  0 ULP  |
|  direction  | implementation    |     %     |     %     |         |
| ----------- | ----------------- | --------- | --------- | ------- |
| deg --> rad | f(f(x*f(pi))/180) | 0.0001357 | 33.20333  | balance |
| deg --> rad | f(x*f(pi/180))    | 0         | 11.05166  | balance |
| deg --> rad | f(x/f(180/pi))    | 0         | 22.69309  | balance |
| rad --> deg | f(f(x*180)/f(pi)) | 0         | 33.23503  | balance |
| rad --> deg | f(x/f(pi/180))    | 0         | 11.04806  | balance |
| rad --> deg | f(x*f(180/pi))    | 0         | 22.70538  | balance |

For both Single and Double, multiplying (for DegreesToRadians) or dividing (for RadiansToDegrees) by the nearest floating point value to pi/180 is the most accurate of the options compared, and the current implementation is the least accurate. The more accurate implementations also avoid the overflow issue.

Of course it's possible to improve accuracy even further, but going beyond this level will likely require sacrificing performance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions