Stream: ideas

Topic: Additional base 10 fixed point numbers


view this post on Zulip J.Teeuwissen (Sep 10 2022 at 09:39):

Currently, (as described in the tutorial, https://github.com/roc-lang/roc/blob/main/TUTORIAL.md#numeric-types)

Roc has three fractional types:
F32, a 32-bit floating-point number
F64, a 64-bit floating-point number
Dec, a 128-bit decimal fixed-point number

why don't F128 (128-bit floating-point number) and
Dec16/D16 (2 decimal places, fits U8 with -327.68 to 327.67)
Dec32/D32 (4 decimal places, fits U16 with -214_748.3648 to 214_748.3647)
Dec64/D64 (9 decimal places, fits U32 with -9_223_372_036.854775808 to 9_223_372_036.854775807)
exist for completeness? (if added, perhaps rename Dec to Dec128 or D128)

view this post on Zulip Brian Carroll (Sep 10 2022 at 10:41):

I guess because completeness isn't an explicit goal. We're focused on real use cases and these types cover most of them. Dec16 would rarely be the best choice in practice.

view this post on Zulip Brian Carroll (Sep 10 2022 at 10:42):

F128 is something we've discussed but it's a lot of work in practice and hasn't been implemented so far.

view this post on Zulip Brian Carroll (Sep 10 2022 at 10:54):

Dec is much slower than float because it's not supported directly in common hardware. But it has nice predictable properties like huge range and nice handling of decimal fractions for currency etc.
F128 isn't really widely supported either so would also be slow but doesn't have those nicer properties.
If you want 32 bit range then it makes sense to use a hardware supported type for speed too. If you don't care about speed then why not use Dec128.

view this post on Zulip Brian Carroll (Sep 10 2022 at 10:58):

So the conclusion is that adding more fractional number types would take time and effort that could be better spent elsewhere.

view this post on Zulip Brian Carroll (Sep 10 2022 at 11:00):

That could change if real use cases came up of course! Did you have some in mind?

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 12:42):

Thank you for the answer,
It makes sense that completeness is not an explicit goal, but I do think that there is some merit to renaming Dec to D128. Albeit for backwards compatibility if other versions were to be added later and it would be more consistent with the single letter prefix from Integer, Unsiged and Float. This added 128 (even though not necessary whilst no other sizes exist) would also communicate any memory impacts to developers.
And wouldn't Dec not being supported in common hardware be all the more reason to enable developers using e.g. D32 in scenarios where the values fit and base-10 rounding is required, to optimize for performance?

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:07):

So with Dec, it isn't for performance, it is for accuracy. As such, it is trying to have enough decimal places that in many cases you won't hit rounding related issues. Currently Dec has 18 decimal place + about U64 worth of integer places.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:08):

If we were to make D32, it would not have many decimal places and would greatly increase the chances of a user accidentally running into issues.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:09):

If someone wants speed, the should generally use FSomething instead of Dec.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:10):

Not saying it would be potentially worth adding, but i think it would be much more likely to add complexity and confusion, especially cause people will always need to look up ranges and many of the types aren't that big.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:11):

As an aside, i don't think D32 would have any advantages over D64 on a 64bit system except for using half the storage space.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:15):

Also, your ranges all contain 1 too many decimal places, I think...it doesn't work to have a max decimal value that isn't a multiple of 10 minus 1. So D32 for example would actually have a range from -214_748.999 to 214_748.999

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 14:23):

I tried to match the values with the one on the tutorial page.

Dec -> -170_141_183_460_469_231_731.687303715884105728 to 170_141_183_460_469_231_731.687303715884105727

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:26):

Ah, that should be updated, thanks for the note

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 14:33):

As an aside, i don't think D32 would have any advantages over D64 on a 64bit system except for using half the storage space.

half the memory allocation will probably have a performance impact as well.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:38):

In a list sure (Data Oriented Design for wins), I just mean that the actual calculations should take the same amount of time being single instruction multiplications either way.

EDIT: just look at the implementation again to double check this, actually we have to do a multiplication that is sized up by 1. So 128 bit multiplication for D64. As such, D32 would definitely be faster than D64, but D16 would not really be faster than D32.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:38):

Also, anything in registers will always fit in the same number of registers.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:45):

Ok, ignore my last comments on decimal ranges, I was just confused and forgot how we implemented fix point despite being the main implementor :oh_no: . it was a long time ago.

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 14:46):

it doesn't work to have a max decimal value that isn't a multiple of 10 minus 1

perhaps a bit off-stream. But could you explain why so? My reasoning was that for a I32 the maximum value is 2^(32-1)-1=2_147_483_647 and that a simple scaling factor of (1/1000) would give a maximum value of 214_748.3647.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:47):

I was just thinking we went with a different implementation of fixed point decimal, brain failed. Your range is correct.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:50):

Also, I am not sure about good naming exactly, but if we change the name, instead of bits, maybe places before and after the decimal place would work better. Though it wouldn't quite be accurate due to hitting the edge of the range.

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 14:55):

Like maybe call Dec as D20_18 because it can represent all values between +/- 99999999999999999999.999999999999999999. Of course this ignores part of the representable values, becuase the full range with all decimal places is +/- 170141183460469231730.999999999999999999

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 15:46):

I see your point, but e.g. U8 is called U8, not U256. So naming it by the memory size and having a table with the min/max value would be more consistent with other numbers (and potentially less). And less characters :rolling_on_the_floor_laughing: .

view this post on Zulip Brendan Hansknecht (Sep 10 2022 at 15:52):

Thats fair, they just aren't clear unlike U8/I8 which have simple rules, you just have to know the number of decimal places, and it could be any number between 0 and 38 technically. We picked 18.

view this post on Zulip Richard Feldman (Sep 10 2022 at 16:03):

my first instinct was to call it D128, but I changed to Dec.

One of the main goals for introducing a fixed-point decimal type was to give people a good default numeric type to use when accuracy is a bigger concern than performance. In SQL Server, they have 3 decimal types you could use for storing currency: MONEY, SMALLMONEY, and the configurable DECIMAL. It seems to be popular to ignore MONEY and SMALLMONEY and instead use DECIMAL(19,4).

In contrast, C# just has one 128-bit Decimal type, which is simpler and doesn't seem to have big downsides in practice. C# is 22 years old and a big language, and I couldn't find examples of people making feature requests for alternative decimal sizes. In general it seems like people are happier with the way C# does it (one decimal type) than the way SQL Server does it (several competing alternatives).

view this post on Zulip Richard Feldman (Sep 10 2022 at 16:04):

another factor is that D128 is longer than F64, which gives a (very small, but still nonzero) conciceness advantage to the thing I don't think should be what people reach for by default

view this post on Zulip Richard Feldman (Sep 10 2022 at 16:05):

I'd rather people treat Dec as the default when you want a decimal value (kinda like Str is for strings), and only reach for F32 or F64 if they have a specific use case where it won't be a good fit (e.g. graphics or high-performance physics simulations)

view this post on Zulip Richard Feldman (Sep 10 2022 at 16:07):

in contrast, if there's D128, D64, and D32, the incentives are different. D64 and D32 are more concise to write out, and also look more similar to commonly used bit ranges for other numeric types (e.g. I64 and I32 are much more common than I128, so you don't automatically know that reaching for D128 is the right default choice. That has to be taught and reinforced as community lore, similar to the community lore in SQL Server that although MONEY and SMALLMONEY exist, the recommendation is not to use them by default.

view this post on Zulip Richard Feldman (Sep 10 2022 at 16:07):

so I think there's actually significant value to calling it Dec and planning never to have D32 and D64!

view this post on Zulip Richard Feldman (Sep 10 2022 at 16:07):

given the particular purpose of having it in the language, which is to be a good default choice for decimal calculations :big_smile:

view this post on Zulip J.Teeuwissen (Sep 10 2022 at 16:20):

Makes sense, I guess having a go to value does outweigh more options.

view this post on Zulip Eric Rogstad (Jun 06 2024 at 21:33):

Coming at this from the opposite direction, a D256 or Dec256 type would be useful for supporting crypto (Ethereum) applications:
https://github.com/tc39/proposal-decimal/issues/66

view this post on Zulip Brendan Hansknecht (Jun 06 2024 at 22:08):

I would guess that is something that should be left to userland. Especially given there is no guarantee that a D256 we make would match that of ethereum unless we decide to custom tailor it for ethereum (which would feel quite out of place in roc)

view this post on Zulip Brendan Hansknecht (Jun 06 2024 at 22:10):

In a lucky turn of event our Dec has the same number of decimal places as the ethereum D256, so it might be usable in many cases and a U128 + a Dec should be equivalent to a D256.

view this post on Zulip Brendan Hansknecht (Jun 06 2024 at 22:10):

So seems very doable to make this type fully in userland with it being efficient.


Last updated: Jun 16 2026 at 16:19 UTC