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)
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.
F128 is something we've discussed but it's a lot of work in practice and hasn't been implemented so far.
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.
So the conclusion is that adding more fractional number types would take time and effort that could be better spent elsewhere.
That could change if real use cases came up of course! Did you have some in mind?
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?
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.
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.
If someone wants speed, the should generally use FSomething instead of Dec.
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.
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.
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
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
Ah, that should be updated, thanks for the note
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.
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.
Also, anything in registers will always fit in the same number of registers.
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.
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.
I was just thinking we went with a different implementation of fixed point decimal, brain failed. Your range is correct.
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.
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
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: .
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.
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).
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
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)
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.
so I think there's actually significant value to calling it Dec and planning never to have D32 and D64!
given the particular purpose of having it in the language, which is to be a good default choice for decimal calculations :big_smile:
Makes sense, I guess having a go to value does outweigh more options.
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
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)
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.
So seems very doable to make this type fully in userland with it being efficient.
Last updated: Jun 16 2026 at 16:19 UTC